import threading
import time
import sys
from math import sqrt
from kelner.src.components.Table import Status
from kelner.src.algorithms.AStar.Finder import Finder
from kelner.src.algorithms.BFS.BFS import BFS
from kelner.src.components.Table import Status
from kelner.src.algorithms.CNN.ModelPrediction import Predictor


# creates new thread
class WaiterManager(threading.Thread):

    def __init__(self, drawableManager, waiters, kitchen_manager, menu_manager, table_manager):
        super().__init__()
        self.__drawableManager = drawableManager
        self.__waiters = waiters
        self.__runThread = True
        self._kitchen_manager = kitchen_manager
        self._menu_manager = menu_manager
        self._table_manager = table_manager

    def __getDistance(self, waiter, tupleXY):
        dx = waiter.getX() - tupleXY[0]
        dy = waiter.getY() - tupleXY[1]
        return sqrt(dx * dx + dy * dy)

    def __sortTargets(self, waiter, targets):
        return sorted(targets, key=lambda target: self.__getDistance(waiter, (target[0], target[1])))

    def __getTargets(self, waiter, finder):
        found = []
        tables = self.__drawableManager.getTables(Status.Ready)
        if tables:
            origin = (waiter.getX(), waiter.getY())
            for table in tables:
                if table.hasOrder():
                    targets = finder.getNeighbours((table.getX(), table.getY()), False)
                    for target in targets:
                        if target == origin:
                            return []
                        else:
                            found.append(target)
        return self.__sortTargets(waiter, found)

    def __getNearestTargetPath(self, waiter, targets=None):
        distance = sys.maxsize
        nearestTargetPath = None
        reservedPlaces = self.__drawableManager.getReservedPlaces(waiter)
        finder = Finder(reservedPlaces)
        origin = (waiter.getX(), waiter.getY())
        if targets is None:
            targets = self.__getTargets(waiter, finder)
        for target in targets:
            if distance > self.__getDistance(waiter, target):
                path = finder.getPath(origin, target, True)
                if path:
                    result = len(path)
                    if result < distance:
                        distance = result
                        nearestTargetPath = path

        return nearestTargetPath

    def get_specified_path(self, waiter, table):
        distance = sys.maxsize
        nearestTargetPath = None
        reserved_places = self.__drawableManager.getReservedPlaces(waiter)
        finder = Finder(reserved_places)
        origin = (waiter.getX(), waiter.getY())
        targets = finder.getNeighbours((table[0], table[1]), False)
        for target in targets:
            if distance > self.__getDistance(waiter, target):
                path = finder.getPath(origin, target, True)
                if path:
                    result = len(path)
                    if result < distance:
                        distance = result
                        nearestTargetPath = path

        return nearestTargetPath

    def __changeWaiterDirection(self, waiter, x, y):
        targetDirection = x - waiter.getX(), y - waiter.getY()
        originDirection = waiter.getDirection()
        while originDirection is not None:
            originDirection = waiter.getNextDirection(originDirection, targetDirection, True)
            if originDirection is not None:
                time.sleep(0.3)
                waiter.setDirection(originDirection[0], originDirection[1])
                self.__drawableManager.forceRepaint()

    def __moveWaiter(self, waiter, x, y):
        time.sleep(0.4)
        self.__drawableManager.moveWaiter(waiter, x, y)
        self.__drawableManager.forceRepaint()

    def __collectOrder(self, waiter):
        doCollectOrder = True
        while doCollectOrder and waiter.get_state():
            tables = self.__drawableManager.getNearestTables(waiter, Status.Ready)
            turns = sys.maxsize
            lessTurnsTable = None
            originDirection = waiter.getDirection()

            for table in tables:
                targetDirection = table.getX() - waiter.getX(), table.getY() - waiter.getY()
                result = Finder.getTurnsCount(originDirection, targetDirection, True)
                if result < turns:
                    turns = result
                    lessTurnsTable = table

            if lessTurnsTable is not None:
                tables.remove(lessTurnsTable)
                self.__changeWaiterDirection(waiter, lessTurnsTable.getX(), lessTurnsTable.getY())

                # order = lessTurnsTable.getOrder()
                order = lessTurnsTable.get_order()
                if order is not None and waiter.get_state:
                    waiter.addOrder(lessTurnsTable, order)
                    time.sleep(2)
                    lessTurnsTable.setStatus(Status.Waiting)
                    self.__drawableManager.forceRepaint()
            doCollectOrder = len(tables) > 0

    def pass_orders_to_kitchen(self, orders):
        kitchen = self.__drawableManager.get_kitchen()
        self._kitchen_manager.pass_orders(orders, kitchen)

    def receive_ready_orders(self, waiter):
        kitchen = self.__drawableManager.get_kitchen()
        try:
            ready_orders = self._kitchen_manager.get_ready_orders(kitchen)
            for dish in ready_orders:
                print("Orders ready to take", end=" ")
                print(dish[1])
        except IndexError as e:
            print("No orders")
        waiter.make_ready()
        waiter.clear_accepted_orders()
        print("Ready to go")
        return ready_orders

    def predict_order(self, order, kitchen):
        food_list = self._menu_manager.get_menu()
        image_predictor = Predictor(food_list)
        paths = self._kitchen_manager.draw_single_order(order, kitchen)
        predicted_dishes = []
        self._kitchen_manager.run()
        self._table_manager.pause()
        # print("Printed images paths: {}".format(paths))
        if paths is not None:
            for img_path in paths:
                predicted_dish = image_predictor.predict_class(img_path)
                predicted_dishes.append(predicted_dish)
                time.sleep(2)
        self.__drawableManager.forceRepaint()
        return predicted_dishes

    def match_predicted_orders(self, predicted_orders):
        all_tables = self.__drawableManager.getTables(Status.Waiting)
        located_orders = dict()

        for p_order in predicted_orders:
            p_order.sort()
            for table in all_tables:
                table_dishes = table.get_order()
                table_dishes.sort()

                if p_order == table_dishes:
                    located_orders[table] = p_order
        return located_orders

    def get_order_targets(self, orders):
        targets = dict()
        for table, dishes in orders.items():
            targets[table.get_posistion()] = dishes
        return targets

    def set_next_waiter_targets(self, waiter, matched_targets):
        for table in matched_targets.keys():
            path = self.get_specified_path(waiter, table)
            waiter.add_remaining_target(path)

    def set_next_waiter_positions(self, waiter, matched_positions):
        for position in matched_positions.keys():
            waiter.add_remaining_position(position)

    # changes the status of a random table from NotReady to Ready
    def run(self):
        while self.__runThread:
            if self.__waiters:
                for waiter in self.__waiters:

                    if len(waiter.getAcceptedOrders()) > 1:
                        waiter.set_target('kitchen')
                        path = self.__getNearestTargetPath(waiter, [(14, 1), (13, 0)])
                        waiter.make_busy()
                    else:
                        if waiter.get_target() == 'return_order' and waiter.isPathEmpty():
                            print("Order returned")
                            table = self._table_manager.get_table(waiter.get_remaining_positions()[0])
                            table.setStatus(Status.Served)
                            self.__changeWaiterDirection(waiter, table.getX(), table.getY())
                            waiter.get_remaining_positions().pop(0)
                            time.sleep(2)

                        if not waiter.get_remaining_positions() == []:
                            waiter.set_target('return_order')
                            path = self.get_specified_path(waiter, waiter.get_remaining_positions()[0])
                        else:
                            waiter.set_target('table')
                            path = self.__getNearestTargetPath(waiter)

                    waiter.setPath([] if path is None else path)

                    if not waiter.isPathEmpty():
                        step = waiter.popStepFromPath()
                        self.__changeWaiterDirection(waiter, step[0], step[1])
                        self.__moveWaiter(waiter, step[0], step[1])

                    # check if waiter is near kitchen
                    if (waiter.getX(), waiter.getY()) == (14, 1) or (waiter.getX(), waiter.getY()) == (13, 0):
                        if waiter.get_target() == 'kitchen':
                            self._table_manager.pause()
                            kitchen = self.__drawableManager.get_kitchen()
                            self.__changeWaiterDirection(waiter, kitchen.getX(), kitchen.getY())
                            waiter_orders = waiter.getAcceptedOrders()
                            # print("Waiter near kitchen. Collected orders: {}".format(waiter_orders))
                            kitchen.add_orders(waiter_orders)
                            received_orders = kitchen.get_ready_orders()
                            waiter.clear_accepted_orders()
                            if received_orders:
                                predicted_orders = []
                                for order in received_orders:
                                    # get predicted dishes
                                    predicted_dishes = self.predict_order(order, kitchen)
                                    predicted_orders.append(predicted_dishes)

                                # check if all predicted orders were really ordered
                                matched_targets = self.match_predicted_orders(predicted_orders)
                                # get positions to matched orders (X,Y): dish
                                order_positions = self.get_order_targets(matched_targets)
                                self.set_next_waiter_positions(waiter, order_positions)

                                # set new ready paths to waiter
                                # self.set_next_waiter_targets(waiter, matched_targets)

                                self._kitchen_manager.stop()
                                self._table_manager.resume()

                            kitchen.clear_orders()
                            waiter.make_ready()
                            waiter.set_target('table')
                    elif waiter.get_target() not in ['return_order', 'check']:
                        self.__collectOrder(waiter)

    def stop(self):
        self.__runThread = False

    def resume(self):
        self.__runThread = True