import math

from abc import ABCMeta
from typing import List

from ._OrderChangerProductionBox import OrderChangerProductionBox
from production.model import ProductionOrder


class OrderSplitterBox(OrderChangerProductionBox):
    __metaclass__ = ABCMeta

    def __init__(self, name, sons, order_changer, split_type, split_value, logger=None):
        super(OrderSplitterBox, self).__init__(name, sons, logger)
        self.order_changer = order_changer
        self.split_type = split_type
        self.split_value = split_value

    def change_order(self, order):
        # type: (ProductionOrder) -> List[ProductionOrder]

        item_qty = [x for x in order.items if x.qty > 0]
        if len(item_qty) <= 1 or self.split_type is None:
            return [order]

        if self.split_type.upper() == 'PREP_TIME':
            orders = self.split_prep_time(order)
        elif self.split_type.upper() == 'SALE_LINES':
            if self.split_value is None:
                return [order]
            orders = self.split_sale_line(order, float(self.split_value))
        elif self.split_type.upper() == 'FIRE_HOLD':
            orders = self.split_fire_hold(order)
        else:
            orders = [order]

        return orders

    @staticmethod
    def split_prep_time(order):
        # type: (ProductionOrder) -> List[ProductionOrder]

        orders = []
        split_index = 0

        for item in order.items:
            for temp_order in orders:
                if len(temp_order.items) > 0 and item.prep_time == temp_order.items[0].prep_time:
                    temp_order.items.append(item)
                    break
            else:
                new_order = order.clone([item], split_index)
                orders.append(new_order)
                split_index += 1

        return orders

    def split_sale_line(self, order, limit):
        # type: (ProductionOrder, float) -> List[ProductionOrder]

        temp_items = [x for x in order.items if not self._has_fire_or_hold(x)]
        if len(temp_items) < limit:
            return [order]

        orders = []
        split_index = 0
        box_limit = math.ceil(len(temp_items) / limit)
        items_per_box = math.ceil(len(temp_items) / box_limit)
        for item in temp_items:
            if len(orders) <= split_index:
                new_order = order.clone([], split_index)
                orders.append(new_order)

            orders[split_index].items.append(item)
            if len(orders[split_index].items) >= items_per_box:
                split_index += 1

        orders[0].items.extend([x for x in order.items if self._has_fire_or_hold(x)])
        return orders

    def split_fire_hold(self, order):
        # type: (ProductionOrder) -> List[ProductionOrder]

        active_lines = [x for x in order.items if x.qty > 0]
        if len(active_lines) <= 1:
            return [order]

        orders = []
        main_order_items = []
        split_index = 1
        for item in order.items:
            if not self._has_fire_or_hold(item):
                main_order_items.append(item)
            elif item.qty > 0:
                new_order = order.clone([item], split_index)
                clear_tag_time = item.get_last_tag_time("clear_hold")
                if clear_tag_time is not None:
                    new_order.display_time = clear_tag_time.strftime("%Y-%m-%dT%H:%M:%S")
                orders.append(new_order)
                split_index += 1

        self.order_changer.change_order(order.order_id, self._mark_items_as_split)

        new_order = order
        if len(orders) > 0:
            new_order = order.clone()
            new_order.order_id = str(order.order_id) + "-0"
        new_order.items = main_order_items
        orders.append(new_order)

        return orders

    @staticmethod
    def _mark_items_as_split(root_order):
        # type: (ProductionOrder) -> bool

        need_to_save_order = False
        for item in root_order.items:
            if "fire" in item.properties and "had_fire" not in item.tags:
                item.add_tag("had_fire")
                need_to_save_order = True

            if "hold" in item.properties and "had_hold" not in item.tags:
                item.add_tag("had_hold")
                need_to_save_order = True

            if "hold" not in item.properties and "had_hold" in item.tags and "clear_hold" not in item.tags:
                item.add_tag("clear_hold")
                need_to_save_order = True

        return need_to_save_order

    @staticmethod
    def _has_fire_or_hold(item):
        has_fire = "fire" in item.properties or "had_fire" in item.tags
        has_hold = "hold" in item.properties or "had_hold" in item.tags

        return has_fire or has_hold
