import pygame
import heapq
from order_data import OrderDataProvider
from waiter_data import WaiterDataProvider
import time

# states enum
WAITING_ORDER = 0
RECIEVED_ORDER = 1
PREPARING_ORDER = 2
ORDER_READY = 3
DELIVERING_ORDER = 4

class Waiter:
    def __init__(self, name, order_data, initial_position=(0, 0), speed_mult=1, offset=0):
        self.initial_position = initial_position
        self.pos = initial_position              # (x, y) in tile coordinates
        self.desired_pos = initial_position      # (x, y) in tile coordinates
        self.name = name
        self.load = 0      
        self.offset = offset                      # Total cost units assigned to this waiter
        self.path = []  
        self.speed_mult = speed_mult                         # List of (x, y) tiles to follow
        self.speed = 0.2 * max(1, int(self.speed_mult))          # tiles per update (min 1)
        self.order_data = order_data
        self.waiter_data = WaiterDataProvider()
        self.current_products = None
        self.current_order_id = None
        self.current_group = None
        self.state = WAITING_ORDER
        self.preparing_start_time = None
        self.preparing_duration = None
        self.waiting_time = 0

    def update(self, map):
        if self.state == WAITING_ORDER:
            self.waiting_time += self.speed_mult
            self.current_products, self.current_group, self.current_order_id = self.order_data.get_next_product_for_waiter(self.name)
            if self.current_products and self.current_group:
                # Nuevo pedido asignado, ir al grupo de productos
                self.move_to(map.get_group_position(self.current_group))
                for product in self.current_products:
                    self.order_data.change_product_status(self.current_order_id, product["name"], self.name, "preparing")
                self.state = RECIEVED_ORDER
        elif self.state == RECIEVED_ORDER:
            # Pedido recibido, ir al grupo de productos cuando se llegue cambiar de estado a preparar pedido
            if self.pos == self.desired_pos:
                self.state = PREPARING_ORDER
        elif self.state == PREPARING_ORDER:
            # Preparando el pedido: esperar el tiempo igual al cost del producto, ajustado por speed_mult
            if self.preparing_start_time is None:
                self.preparing_start_time = time.time()
                # Obtener el cost del producto actual
                product_cost = 0
                for i in range(len(self.current_products)):
                    product = self.current_products[i]
                    self.load += product["cost"] if isinstance(product, dict) else 1
                    if i == 0:
                        product_cost = product["cost"] + product["qty"] * product["cost"] // 2
                    else:
                        product_cost += product["cost"] // 2 if isinstance(product, dict) else 1
                # Duración en segundos, ajustada por speed_mult
                self.preparing_duration = max(0.1, product_cost / max(0.1, self.speed_mult))
            elapsed = time.time() - self.preparing_start_time
            if elapsed >= self.preparing_duration:
                self.state = ORDER_READY
                self.preparing_start_time = None
                self.preparing_duration = None
        elif self.state == ORDER_READY:
            # Pedido listo, ir a la zona de entrega
            deliver_pos = map.get_group_position("deliver")
            self.move_to(deliver_pos)
            if self.pos == self.desired_pos:
                self.state = DELIVERING_ORDER
                for product in self.current_products:
                    self.order_data.change_product_status(
                        self.current_order_id,
                        product["name"],
                        self.name,
                        "delivered"
                    )
        elif self.state == DELIVERING_ORDER:
            # Entregar el pedido, cuando se llegue cambiar a esperando pedido
            # Aquí podríamos añadir lógica para simular la entrega
            self.move_to(self.initial_position)
            if self.pos == self.initial_position:
                self.state = WAITING_ORDER
                self.current_products = None
                self.current_order_id = None
                self.current_group = None
        


        """Update waiter position along the precomputed path."""
        # Si ya está en destino, no hay nada que hacer
        if self.pos == self.desired_pos:
            self.path = []
            return

        # Puede avanzar una fracción de tile por frame, según la velocidad
        self._move_accum = getattr(self, '_move_accum', 0) + self.speed
        steps = int(self._move_accum)
        self._move_accum -= steps
        for _ in range(steps):
            if self.pos == self.desired_pos:
                self.path = []
                break

            if not self.path:
                self.path = self._compute_path_straight(map, self.pos, self.desired_pos)
                if not self.path:
                    # No hay camino posible
                    break

            # Avanzar un tile en el camino
            next_tile = self.path[0]
            self.pos = next_tile
            self.path.pop(0)

    def _compute_path_a_star(self, map, start, goal):
        import heapq
        def heuristic(a, b):
            # Manhattan distance
            return abs(a[0] - b[0]) + abs(a[1] - b[1])

        start = (int(start[0]), int(start[1]))
        goal = (int(goal[0]), int(goal[1]))
        if map.is_black_tile(goal[0], goal[1]):
            return []
        width, height = map.width, map.height
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        open_set = []
        heapq.heappush(open_set, (0 + heuristic(start, goal), 0, start))
        came_from = {}
        g_score = {start: 0}
        while open_set:
            _, current_g, current = heapq.heappop(open_set)
            if current == goal:
                break
            for dx, dy in directions:
                neighbor = (current[0] + dx, current[1] + dy)
                if (0 <= neighbor[0] < width and 0 <= neighbor[1] < height and not map.is_black_tile(neighbor[0], neighbor[1])):
                    tentative_g_score = g_score[current] + 1
                    if tentative_g_score < g_score.get(neighbor, float('inf')):
                        came_from[neighbor] = current
                        g_score[neighbor] = tentative_g_score
                        f_score = tentative_g_score + heuristic(neighbor, goal)
                        heapq.heappush(open_set, (f_score, tentative_g_score, neighbor))
        # Reconstruir el camino
        if goal not in came_from:
            return []
        path = []
        current = goal
        while current != start:
            path.append(current)
            current = came_from[current]
        path.reverse()
        return path

    def _compute_path_dijkstra(self, map, start, goal):
        start = (int(start[0]), int(start[1]))
        goal = (int(goal[0]), int(goal[1]))

        if map.is_black_tile(goal[0], goal[1]):
            return []

        width, height = map.width, map.height
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]

        dist = {start: 0}
        prev = {}
        heap = [(0, start)]

        while heap:
            current_cost, current = heapq.heappop(heap)

            if current == goal:
                break

            if current_cost > dist.get(current, float('inf')):
                continue

            cx, cy = current

            for dx, dy in directions:
                nx, ny = cx + dx, cy + dy

                if nx < 0 or ny < 0 or nx >= width or ny >= height:
                    continue

                if map.is_black_tile(nx, ny):
                    continue

                new_cost = current_cost + 1

                if new_cost < dist.get((nx, ny), float('inf')):
                    dist[(nx, ny)] = new_cost
                    prev[(nx, ny)] = current
                    heapq.heappush(heap, (new_cost, (nx, ny)))

        if goal not in dist:
            return []

        path = []
        current = goal
        while current != start:
            path.append(current)
            current = prev[current]
        path.reverse()
        return path

    def _compute_path_straight(self, map, start, goal):
        """
        Camino directo: primero en x, luego en y. No evita obstáculos.
        """
        start = (int(start[0]), int(start[1]))
        goal = (int(goal[0]), int(goal[1]))
        path = []
        x, y = start
        gx, gy = goal
        # Moverse en x
        while x != gx:
            x += 1 if gx > x else -1
            path.append((x, y))
        # Moverse en y
        while y != gy:
            y += 1 if gy > y else -1
            path.append((x, y))
        return path

    def draw(self, screen):
        font = pygame.font.Font(None, 16*3)
        text = font.render(self.name[0], True, (0, 0, 0))
        screen.blit(text, (self.pos[0] * 16 + self.offset * 16, self.pos[1] * 16))
    
    def move_to(self, new_position):
        self.desired_pos = (int(new_position[0]), int(new_position[1]))
        self.path = []



