"""Runner-related configuration module for the histopath simulation model."""
from collections.abc import Sequence
from typing import Self
import pandas as pd
from openpyxl import Workbook
from pydantic import AliasChoices, BaseModel, Field, NonNegativeFloat
from .. import excel
[docs]
class ProcessDoorMap(BaseModel):
    """A mapping of process stages to door names.
    Each door name corresponds to a room where the the
    process takes place.
    Note that some processes (e.g., cutup) may take place in multiple rooms.
    """
    reception: str | Sequence[str] = Field(validation_alias='Reception')
    cutup: str | Sequence[str] = Field(validation_alias=AliasChoices('Cutup', 'Cut-up'))
    processing: str | Sequence[str] = Field(validation_alias='Processing')
    microtomy: str | Sequence[str] = Field(validation_alias='Microtomy')
    staining: str | Sequence[str] = Field(validation_alias='Staining')
    labelling: str | Sequence[str] = Field(validation_alias='Labelling')
    scanning: str | Sequence[str] = Field(validation_alias='Scanning')
    qc: str | Sequence[str] = Field(validation_alias='QC') 
[docs]
class PathDefinition(BaseModel):
    """Defines a direct path between two doors, with a travel duration."""
    path: tuple[str, str]
    duration_seconds: float 
[docs]
class RunnerConfig(BaseModel):
    """Configuration dataclass for runner-related parameters."""
    door_map: ProcessDoorMap
    runner_speed: float
    cutup_dist: Sequence[float]
    extra_paths: Sequence[PathDefinition]
    extra_durations: RunnerExtraDurations
[docs]
    @staticmethod
    def from_excel(wbook: Workbook) -> Self:
        """Generate a `RunnerConfig` from an Excel file.
        Args:
            wbook (Workbook): The Excel file to parse.
        Returns:
            RunnerConfig: the parsed `RunnerConfig`.
        """
        t = excel.get_table(
            wbook, 'Runner Times', 'tableProcessStageDoors'
        )
        door_map = {
            k: (
                v['doors'].split(' ') if ' ' in v['doors'] else v['doors']
            ) for k, v in (
                pd.DataFrame(t[1:], columns=t[0])
                .set_index('stage')
                .to_dict('index')
            ).items()
        }
        runner_speed = excel.get_name(wbook, 'runnerSpeed')
        print(excel.get_name(wbook, 'runnerCutupDist'))
        cutup_dist = [v[0] for v in excel.get_name(wbook, 'runnerCutupDist')]  # flatten
        t = excel.get_table(
            wbook, 'Runner Times', 'tableAdditionalPaths'
        )
        extra_paths = [
            {
                'path': v['path'].split(' '),
                'duration_seconds': float(v['seconds'])
            }
            for v in dict(
                pd.DataFrame(t[1:], columns=t[0])
                .set_index('path_name')
                .to_dict('index')
            ).values()
        ]
        t = excel.get_table(
            wbook, 'Runner Times', 'tableExtraTasks'
        )
        extra_durations = {
            k: float(v['seconds'])
            for k, v in (
                pd.DataFrame(t[1:], columns=t[0])
                .set_index('runner_task')
                .to_dict('index')
            ).items()
        }
        return RunnerConfig(
            door_map=door_map,
            runner_speed=runner_speed,
            cutup_dist=cutup_dist,
            extra_paths=extra_paths,
            extra_durations=extra_durations
        ) 
 
[docs]
class RunnerTimesConfig(BaseModel):
    """Configuration dataclass containing runner times between process stages."""
    reception_cutup: NonNegativeFloat = Field(alias='(reception, cutup)')
    cutup_processing: NonNegativeFloat = Field(alias='(cutup, processing)')
    processing_microtomy: NonNegativeFloat = Field(alias='(processing, microtomy)')
    microtomy_staining: NonNegativeFloat = Field(alias='(microtomy, staining)')
    staining_labelling: NonNegativeFloat = Field(alias='(staining, labelling)')
    labelling_scanning: NonNegativeFloat = Field(alias='(labelling, scanning)')
    scanning_qc: NonNegativeFloat = Field(alias='(scanning, qc)')
    extra_loading: NonNegativeFloat
    extra_unloading: NonNegativeFloat
[docs]
    @staticmethod
    def from_workbook(wbook: Workbook, speed: float | None = None) -> 'RunnerTimesConfig':
        """Construct a dataclass instance from an Excel workbook.
        Args:
            wbook (Workbook): The Excel workbook to parse.
        Returns:
            RunnerTimesConfig: The parsed dataclass instance.
        """
        if speed is None:
            speed = excel.get_name(wbook, 'runnerSpeed')
        df = pd.DataFrame(
            (
                table := excel.get_table(wbook, 'Runner Times output', 'tableRunnerDistances')
            )[1:],
            columns=table[0]
        ).set_index('runner_journey')
        times = dict(df.to_records())
        return RunnerTimesConfig(
            **times,
            extra_loading=excel.get_name(wbook, 'runnerLoadingTime'),
            extra_unloading=excel.get_name(wbook, 'runnerUnloadingTime'),
        )