"""
Experiment is the top-most scope. It includes all hosts and registries and acts as a global
namespace.
"""

import numpy as np
from typing import Set, Optional, Union
from abc import ABC

from numpy.random import SeedSequence

from .host import Host
from .random import Generator



class ExperimentException (Exception) :
    """ Exception that is triggered by an experiment. """
    pass



class Experiment (ABC) :
    """ The basic experiment will hold the hosts and a random-generator. """

    hosts: Set[Host]

    # The experiment will hold an internal random generator to allow repeatability.
    rnd_gen: Generator



    def __init__ ( self, seed:Optional[Union[int,SeedSequence]] = None ) :
        self.hosts = set()
        self.rnd_gen = Generator(seed)



    def bind_host ( self, host:Host ) -> None :
        """Keep the host accessible via the experiment."""
        self.hosts.add(host)



    # def create_host ( self, host_class:Type[Host], **kwargs ) -> None :
    #     host = host_class( exp=self, **kwargs )
    #     self.hosts.append( host )

class ExperimentSettings (ABC):
    """Class to hold experiment settings and parameters."""

    # definition of Experiment class with experiment settings
    ExperimentID: str # unique identifier for the experiment
    ExperimentType: str # e.g., "Batch", "Fed-Batch", "Continuous"
    Analytics: list # list of analytics to be performed, e.g., ['OD600', 'HPLC']
    HostName: str # name of the host organism
    Temperature: list # values in Celsius
    CultivationTime: int # default value in hours
    SamplingInterval: float # default sampling interval in hours
    InitBiomass: float # default initial biomass concentration in g/L
    MediumVolume: int # default value in mL
    MediumComposition: dict # dictionary to hold medium components and their concentrations
    CarbonID: str # identifier for the main carbon substrate, following BiGG nomenclature in the GSM model
    CarbonName: str # name of the main carbon substrate
    CarbonConc: float # concentration of the main carbon substrate in mM
    CarbonUptakeRate: float # uptake rate of the main carbon substrate in mmol/gCDW/h
    GrowthRate: float # specific growth rate in 1/h
    Yield: float # biomass yield in gCDW/mmol
    Capacity: int # maximum biomass concentration in g/L
    Results: str # file path to save experiment results

    def __init__(self) -> None :
        # initializing the experiment settings with empty values
        self.ExperimentID = ""
        self.ExperimentType = ""
        self.Analytics = []
        self.HostName = ""
        self.Temperature = []
        self.CultivationTime = 0
        self.SamplingInterval = 1.0
        self.InitBiomass = 0.0
        self.MediumVolume = 0
        self.MediumComposition = {}
        self.CarbonID = ""
        self.CarbonName = "" # name of the main carbon substrate
        self.CarbonConc = 0.0
        self.CarbonUptakeRate = 0.0
        self.GrowthRate = 0.0
        self.Yield = 0.0
        self.Capacity = 0
        self.Results = ""

    def set_SamplingVector(self) -> None :
        """Method to generate the sampling vector based on CultivationTime and SamplingInterval."""
        self.SampleVector = np.arange(0, self.CultivationTime + self.SamplingInterval, self.SamplingInterval)
        