from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
from uuid import uuid4

from sampo.schemas.resources import Material
from sampo.schemas.serializable import AutoJSONSerializable
from sampo.schemas.time import Time
from sampo.schemas.zones import Zone

# Used for max_count in the demand, if it is not specified during initialization WorkerReq
DEFAULT_MAX_COUNT = 100


class BaseReq(AutoJSONSerializable['BaseReq'], ABC):
    """
    A class summarizing any requirements for the work to be performed related to renewable and non-renewable
    resources, infrastructure requirements, etc.
    """
    @property
    @abstractmethod
    def name(self) -> str:
        """
        Returns the name of the claim if it exists, e.g. 'dig work claim'.

        :return name: the name of req
        """
        ...


@dataclass
class WorkerReq(BaseReq):
    """
    Requirements related to renewable human resources

    :param kind: type of resource/profession
    :param volume: volume of work in time units
    :param min_count: minimum number of employees needed to perform the work
    :param max_count: maximum allowable number of employees performing the work
    :param name: the name of this requirement
    """

    def __init__(self,
                 kind: str,
                 volume: Time,
                 min_count: Optional[int] = 1,
                 max_count: Optional[int] = DEFAULT_MAX_COUNT,
                 name: Optional[str] = ''):
        self.kind = kind
        self.volume = volume
        self.min_count = min_count
        self.max_count = max_count
        self.name = name

        assert min_count <= max_count

    def name(self):
        return self.name


    def scale_all(self, scalar: float, new_name: Optional[str] = '') -> 'WorkerReq':
        """
        The function scales the requirement to the size of the work including the total
        volume and the maximum number of personnel involved.

        :param scalar: scalar for multiplication
        :param new_name: name for new req
        :return new_req: new object with new volume of the work and extended max_count_commands
        """
        max_count = max(round(self.max_count * scalar), self.min_count)
        new_req = WorkerReq(self.kind, self.volume * scalar, self.min_count, max_count, new_name or self.name)
        return new_req

    def scale_volume(self, scalar: float, new_name: Optional[str] = None) -> 'WorkerReq':
        """
        The function scales only volume of the work for the requirement.

        :param scalar: scalar for multiplication
        :param new_name: name for new req
        :return new_req: new object with new volume of the work.
        """
        new_req = WorkerReq(self.kind, self.volume * scalar, self.min_count, self.max_count, new_name or self.name)
        return new_req


@dataclass(frozen=True)
class EquipmentReq(BaseReq):
    """
    Requirements for renewable non-human resources: equipment, trucks, machines, etc

    :param kind: type of resource/profession
    :param name: the name of this requirement
    """
    kind: str
    count: int
    name: Optional[str] = None


@dataclass(frozen=True)
class MaterialReq(BaseReq):
    """
    Requirements for non-renewable materials: consumables, spare parts, construction materials

    :param kind: type of resource/profession
    :param name: the name of this requirement
    """
    kind: str
    count: int
    # need_start: int TODO Implement handling this
    name: Optional[str] = None

    def material(self) -> Material:
        return Material(str(uuid4()), self.kind, self.count)


@dataclass(frozen=True)
class ConstructionObjectReq(BaseReq):
    """
    Requirements for infrastructure and the construction of other facilities: electricity, pipelines, roads, etc

    :param kind: type of resource/profession
    :param name: the name of this requirement
    """
    kind: str
    count: int
    name: Optional[str] = None


@dataclass(frozen=True)
class ZoneReq(BaseReq):
    kind: str
    required_status: int
    name: Optional[str] = None

    def to_zone(self) -> Zone:
        return Zone(self.kind, self.required_status)
