from typing import Optional

import numpy as np

from overcooked_simulator.game_items import (
    CuttableItem,
    HoldableItem,
    ProgressibleItem,
    Plate,
    Tomato,
)


class Counter:
    """Simple class for a counter at a specified position (center of counter). Can hold things on top."""

    def __init__(self, pos: np.ndarray):
        self.pos: np.ndarray = pos
        self.occupied_by: Optional[HoldableItem] = None

    def pick_up(self):
        """Gets called upon a player performing the pickup action. If the counter can give something to
        the player, it does so. In the standard counter this is when an item is on the counter.

        Returns: The item which the counter is occupied by. None if nothing is there.

        """
        give_player = self.occupied_by
        self.occupied_by = None
        return give_player

    def can_drop_off(self, item: HoldableItem):
        """Checks whether an item by the player can be dropped of. More relevant for example with
        ingredient dispensers, which should always be occupied and cannot take an item.

        Args:
            item: The item for which to check, if it can be placed on the counter.

        Returns: True if the item can be placed on the counter, False if not.

        """
        return self.occupied_by is None or self.occupied_by.can_combine(item)

    def drop_off(self, item: HoldableItem):
        """Takes the thing dropped of by the player.

        Args:
            item: The item to be placed on the counter.

        Returns: TODO Return information, wether the score is affected (Serving Window?)

        """
        if self.occupied_by is None:
            self.occupied_by = item
        elif self.occupied_by.can_combine(item):
            self.occupied_by.combine(item)

    def interact_start(self):
        """Starts an interaction by the player. Nothing happens for the standard counter."""
        pass

    def interact_stop(self):
        """Stops an interaction by the player. Nothing happens for the standard counter."""
        pass

    def __repr__(self):
        return f"Counter(pos:{str(self.pos)},holds:{self.occupied_by})"


class CuttingBoard(Counter):
    def __init__(self, pos: np.ndarray):
        self.progressing = False
        super().__init__(pos)

    def progress(self):
        """Called by environment step function for time progression"""
        if self.progressing:
            if isinstance(self.occupied_by, CuttableItem):
                self.occupied_by.progress()

    def start_progress(self):
        """Starts the cutting process."""
        self.progressing = True

    def pause_progress(self):
        """Pauses the cutting process"""
        self.progressing = False

    def interact_start(self):
        """Handles player interaction, starting to hold key down."""
        self.start_progress()

    def interact_stop(self):
        """Handles player interaction, stopping to hold key down."""
        self.pause_progress()

    def __repr__(self):
        return f"CuttingBoard({self.occupied_by})"


class ServingWindow(Counter):
    def drop_off(self, item):
        return 5

    def can_score(self, item):
        if isinstance(item, Plate) and isinstance(item.holds, ProgressibleItem):
            return item.holds.finished

    def can_drop_off(self, item: HoldableItem):
        return self.can_score(item)

    def pick_up(self):
        return None

    def __repr__(self):
        return "ServingWindow"


class PlateReturn(Counter):
    def __init__(self, pos):
        super().__init__(pos)
        self.occupied_by = Plate()

    def pick_up(self):
        """Gets called upon a player performing the pickup action. Gives back a plate (possibly with ingredient.

        Returns: A plate possibly with an ingredient on it.

        """
        give_player = self.occupied_by
        self.occupied_by = Plate()
        return give_player

    def drop_off(self, item: HoldableItem):
        """Takes the ingredient dropped of by the player.

        Args:
            item: The ingredient to be placed on the counter.
        """
        if item is Plate() and self.occupied_by is Plate():
            self.occupied_by = None

    def can_drop_off(self, item: HoldableItem):
        """Checks whether an ingredient by the player can be dropped of.

        Args:
            item: The ingredient for which to check, if it can be placed on the counter.

        Returns: True if the ingredient can be placed on the counter, False if not.

        """
        # possibility to drop off empty plate on empty plate return
        return (
            isinstance(self.occupied_by, Plate) and isinstance(item, Plate)
        ) or self.occupied_by.can_combine(item)

    def __repr__(self):
        return "PlateReturn"


class TomatoDispenser(Counter):
    def __init__(self, pos):
        super().__init__(pos)

    def pick_up(self):
        return Tomato()

    def drop_off(self, item: HoldableItem):
        return 0

    def can_drop_off(self, item: HoldableItem):
        return False

    def __repr__(self):
        return f"{self.occupied_by}Dispenser"


class Trash(Counter):
    def pick_up(self):
        pass

    def drop_off(self, item: HoldableItem):
        if isinstance(item, Plate):
            item.holds = None
        return -1

    def can_drop_off(self, item: HoldableItem):
        return True

    def __repr__(self):
        return "Trash"