import random
from abc import ABC, abstractmethod
from copy import copy

from exceptions import DeadPlantError, NoMoreFruitsError
from fruit import Fruit
from root import Root


class Plant(ABC):
    def __init__(self, species: str, growth_factor: float):
        self.species = species
        self.growth_factor = growth_factor
        self._size = 1
        self.dead = False

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

    def __str__(self):
        return f"{self.species} of size {self.size:.1f}"

    @property
    def growth_factor(self):
        return self._growth_factor

    @growth_factor.setter
    def growth_factor(self, growth_factor: float):
        if growth_factor <= 1:
            raise ValueError(f"Invalid growth_factor: {growth_factor} (>1 required)")
        self._growth_factor = growth_factor

    @property
    def size(self):
        return self._size

    def grow(self) -> None:
        if self.dead:
            raise DeadPlantError("{self} is dead, it can't grow")
        self._size *= self.growth_factor

    @abstractmethod
    def harvest(self) -> list:
        raise NotImplementedError


class FruitBearingPlant(Plant):
    def __init__(self, species: str, growth_factor: float, fruit_template: Fruit):
        super().__init__(species, growth_factor)
        self.fruit_template = fruit_template
        self._fruits = []

    def __str__(self):
        return f"{self.species} of size {self.size:.1f} with {len(self.fruits)} fruits"

    @property
    def fruits(self):
        return self._fruits

    @fruits.deleter
    def fruits(self):
        self._fruits.clear()

    def grow(self) -> None:
        super().grow()
        if random.randrange(100) < self.size:
            self.fruits.append(copy(self.fruit_template))

    def pick(self) -> Fruit:
        if not self.fruits:
            raise NoMoreFruitsError(f"{self} is empty, you can't pick any more fruits :(")
        return self.fruits.pop()

    def harvest(self) -> list:
        harvested = []
        while True:
            try:
                harvested.append(self.pick())
            except NoMoreFruitsError:
                return harvested


class RootVegetable(Plant):
    def __init__(self,
                 species: str,
                 growth_factor: float,
                 calories_per_root_length: int,
                 plant_size_to_root_length_factor: float):
        super().__init__(species, growth_factor)
        self._root = Root(self.species, calories_per_root_length)
        self._plant_size_to_root_length_factor = plant_size_to_root_length_factor

    def grow(self):
        super().grow()
        self._root.length = self._size * self._plant_size_to_root_length_factor

    def harvest(self) -> list:
        self.dead = True
        if self._root is None:
            return []
        harvested = [self._root]
        self._root = None
        return harvested