import os, sys
if 'nt' in sys.builtin_module_names:
    import winapps

from typing import Dict, List, Optional, Tuple
from theng.data import JSONFile, DataFile
from theng.data.pathfinder import Trigger, TriggerCollection, Door, DoorCollection,\
    Group, GroupCollection, MeasurementRegion, MeasurementRegionCollection,\
    Occupant, OccupantCollection, Room, RoomCollection, Summary, Target, TargetCollection
from theng.models import Model
from theng.args import get_arguments

"""This Dict defines all of the known results files.
Each item should have a descriptive key, used later by the user to access the files,
and a list of tuples representing the files associated with the key.
The first item in the tuple is the file type. This will be nested under the descriptive
key. The second element of the tuple is the filename format.
This format accepts wildcard "*" characters conforming to the glob package's usage, with recurse enabled.
This format will be prepended with the model base name for parsing
"""
_known_output_files: Dict[str, List[Tuple]] = {
    "triggers": [
        ("csv", "_triggers.csv"), 
        ("json", "_triggers.json")
    ],
    "doors": [
        ("csv", "_doors.csv"),
        ("json", "_doors.json")
    ],
    "groups": [
        ("csv", "_groups.csv"),
        ("json", "_groups.json")
    ],
    "measurement_regions": [
        ("csv", "_measurement-regions.csv"),
        ("json", "_measurement-regions.json")
    ],
    "targets": [
        ("csv", "_occtargets.csv"),
        ("json", "_occtargets.json")
    ],
    "occupants_detailed": [
        ("csv", "_occupants_detailed.csv"),
        ("json", "_occupants_detailed.json"),
        ("csv", "_occupant_*_*.csv"),
        ("json", "_occupant_*_*.json")
    ],
    "occupant_params": [
        ("csv", "_occupant_params.csv"),
        ("json", "_occupant_params.json")
    ],
    "occupants": [
        ("csv", "_occupants.csv"),
        ("json", "_occupants.json")
    ],
    "cummulative": [
        ("json", "_out.json")
    ],
    "rooms": [
        ("csv", "_rooms.csv"),
        ("json", "_rooms.json")
    ],
    "social_distance_accumulated": [
        ("csv", "_sd_accumulated.csv"),
        ("json", "_sd_accumulated.json")
    ],
    "social_distance_transient": [
        ("csv", "_sd_transient.csv"),
        ("json", "_sd_transient.json")
    ],
    "summary": [
        ("txt", "_summary.txt"),
        ("json", "_summary.json")
    ],
    "views": [
        ("json", "_views.json")
    ],
    "octrmesh": [
        ("octrmesh", ".octrmesh")
    ],
    "pfd": [
        ("pfd", ".pfd")
    ],
    "pfg": [
        ("pfg", ".pfg")
    ],
    "input": [
        ("txt", ".txt")
    ],
    "performance": [
        ("txt", "_performance.txt")
    ]
}


class PathfinderModel(Model):
    """Represents a single Pathfinder simulation and it's results

    Args:
        results_file_name (str): The basename of the .pfr Results file generated by Pathfinder
    
    Attributes: 
        triggers (TriggerCollection): Collection of all :class:`~theng.data.pathfinder.Trigger` objects present in the Pathfinder simulation
        doors (DoorCollection): Collection of all :class:`~theng.data.pathfinder.Door` objects present in the Pathfinder simulation
        groups (GroupCollection): Collection of all :class:`~theng.data.pathfinder.Group` objects present in the Pathfinder simulation
        measurement_regions (MeasurementRegionCollection): Collection of all :class:`~theng.data.pathfinder.MeasurementRegion` objects present in the Pathfinder simulation
        occupants (OccupantCollection): Collection of all :class:`~theng.data.pathfinder.Occupant` objects present in the Pathfinder simulation
        rooms (RoomCollection): Collection of all :class:`~theng.data.pathfinder.Room` objects present in the Pathfinder simulation
        summary (Summary): Summary object that presents the data of the _summary.json file
        targets (TargetCollection): Collection of all :class:`~theng.data.pathfinder.Target` objects present in the Pathfinder simulation
        
        _triggers (Dict[str, Trigger]): Temp
        _doors (Dict[str, Door]): Temp
        _groups (Dict[str, Group]): Temp
        _measurement_regions (Dict[str, MeasurementRegion]): Temp
        _occupants (Dict[str, Occupant]): Temp
        _rooms (Dict[str, Room]): Temp
        _targets (Dict[str, Target]): Temp
    """
    
    def __init__(self, results_file_name: str):
        super().__init__(results_file_name, ".pth")
        self._triggers: Dict[str, Trigger] = {}
        self._doors: Dict[str, Door] = {}
        self._groups: Dict[str, Group] = {}
        self._measurement_regions: Dict[str, MeasurementRegion] = {}
        self._occupants: Dict[str, Occupant] = {}
        self._rooms: Dict[str, Room] = {}
        self._targets: Dict[str, Target] = {}
        
        self.triggers: TriggerCollection = TriggerCollection([])
        self.doors: DoorCollection = DoorCollection([])
        self.groups: GroupCollection = GroupCollection([])
        self.measurement_regions: MeasurementRegionCollection = MeasurementRegionCollection([])
        self.occupants: OccupantCollection = OccupantCollection([])
        self.rooms: RoomCollection = RoomCollection([])
        self.summary: Summary | None = None
        self.targets: TargetCollection = TargetCollection([])
        
        try:    
            self._detect_model_files()
            self._load()
        except Exception as e:
            self._log.exception(f'Error with PathfinderModel "{self.model_name}": {e}')
        
    def _detect_model_files(self) -> None:
        """Search for known model files and create DataFile objects for them
        
        Returns:
            None
        """
        for key, files in _known_output_files.items():
            for pair in files:
                file_type: str = pair[0]
                filename_format: str = pair[1]
                expected_filename_format: str = self.model_name + filename_format
                expected_file_location: str = os.path.join(os.getcwd(), expected_filename_format)
                if os.path.exists(expected_file_location):
                    try:
                        data_files_by_category: Optional[List] = self._results_data_files[key]
                    except KeyError as e:
                        self._results_data_files[key] = {}
                        
                    try:
                        data_files_by_type: Optional[List] = self._results_data_files[key][file_type]
                    except KeyError as e:
                        self._results_data_files[key][file_type] = []
                        data_files_by_type: Optional[List] = self._results_data_files[key][file_type]
                        
                    if file_type == "json":
                        data_files_by_type.append(JSONFile(expected_file_location))                        
                    else:  
                        data_files_by_type.append(DataFile(expected_file_location))
    
    def _load(self) -> None:
        """Loads results for this Model
        
        Returns:
            None
        """
        self._load_triggers()
        self._load_doors()
        self._load_groups()
        self._load_measurement_regions()
        self._load_rooms()
        self._load_targets()
        self._load_occupants() # Load last so occupants can reference other objects
        self._load_summary() # ^Last except for Summary. Occupants don't reference the Summary, but the Summary does reference Occupants
    
    def _load_triggers(self) -> None:
        """Load the raw _triggers.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            triggers_json: List[JSONFile] = self._results_data_files["triggers"]['json'][0]
            for key, data in triggers_json.data.items():
                self._triggers[key] = Trigger(name=key, data=data)
            self.triggers = TriggerCollection(self._triggers.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_triggers.json" file does not exist. Trigger data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
    
    def _load_doors(self) -> None:
        """Load the _doors.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            doors_json: List[JSONFile] = self._results_data_files["doors"]['json'][0]
            print(doors_json)
            for key, data in doors_json.data.items():
                self._doors[key] = Door(name=key, data=data)
            self.doors = DoorCollection(self._doors.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_doors.json" file does not exist. Door data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')

    def _load_groups(self) -> None:
        """Load the _groups.json data in to the Pathfinder Model
        
        Returns:
            None
        """
        try:
            groups_json: Dict[str, Dict] = self._results_data_files["groups"]["json"][0]
            for group_id, data_dict in groups_json.data.items():
                self._groups[group_id] = Group(group_id=group_id, data=data_dict)
            self.groups = GroupCollection(self._groups.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_groups.json" file does not exist. Group data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
     
    def _load_measurement_regions(self) -> None:
        """Load the _measurement-regions.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            regions_json: List[JSONFile] = self._results_data_files["measurement_regions"]['json'][0]
            for key, data in regions_json.data.items():
                self._measurement_regions[key] = MeasurementRegion(name=key, data=data)
            self.measurement_regions = MeasurementRegionCollection(self._measurement_regions.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_measurement-regions.json" file does not exist. Measurement Region data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
       
    def _load_occupants(self) -> None:
        """Load raw _occupants.json, _occupant_params.json, and _occupants_detailed.json data in to the PathfinderModel
        
        Returns:
            None
        """
        occupants_json: Optional[List[JSONFile]] = None
        occupant_params_json: Optional[List[JSONFile]] = None
        occupants_detailed_json: Optional[List[JSONFile]] = None
        sd_accumulated_json: Optional[List[JSONFile]] = None
        sd_transient_json: Optional[List[JSONFile]] = None
        
        #Get general occupant data
        try:
            occupants_json: List[JSONFile] = self._results_data_files["occupants"]["json"][0]
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_occupants.json" file does not exist. Occupant data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
        
        #Get occupant param data
        try:
            occupant_params_json: List[JSONFile] = self._results_data_files["occupant_params"]["json"][0]
        except KeyError as e:
            pass
        
        #Get occupant detailed data
        try:
            occupants_detailed_json: List[JSONFile] = self._results_data_files["occupants_detailed"]["json"]
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The detailed Occupant output JSON files do not exist. Detailed Occupant data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
          
        #Extract Occupant detailed data from multi and single file outputs
        detailed_dict: Dict = {}
        if occupants_detailed_json != None:
            for detailed_file in occupants_detailed_json:
                for key, data in detailed_file.data.items():
                    detailed_dict[key] = data
                
        #Get Social Distancing Accumulated data
        try:
            sd_accumulated_json = self._results_data_files["social_distance_accumulated"]["json"][0]
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_sd_accumulated.json" file does not exist. Accumulated Social Distancing data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')

        #Get Social Distancing Transient data
        try:
            sd_transient_json = self._results_data_files["social_distance_transient"]["json"][0]
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_sd_transient.json" file does not exist. Transient Social Distancing data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')

          
        if occupants_json != None:
            for key, data in occupants_json.data.items():
                params: Optional[Dict] = occupant_params_json.data[key] if occupant_params_json != None else None
                details: Optional[Dict] = detailed_dict[key] if key in detailed_dict.keys() else None
                sd_accum: Optional[Dict] = sd_accumulated_json.data[key] if sd_accumulated_json != None else None
                sd_transient: Optional[Dict] = sd_transient_json.data[key] if sd_transient_json != None else None
                self._occupants[key] = Occupant(
                    occ_id=key, 
                    general_data=data, 
                    param_data=params, 
                    detailed_data=details, 
                    sd_accumulated_data=sd_accum, 
                    sd_transient_data=sd_transient, 
                    parent=self
                )
            self.occupants = OccupantCollection(self._occupants.values())
            for occ in self.occupants:
                occ.update_occupant_references()
        
    def _load_rooms(self) -> None:
        """Loads the raw _rooms.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            rooms_json: List[JSONFile] = self._results_data_files["rooms"]['json'][0]
            for key, data in rooms_json.data.items():
                self._rooms[key] = Room(name=key, data=data)
            self.rooms = RoomCollection(self._rooms.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_rooms.json" file does not exist. Room data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
     
    def _load_summary(self) -> None:
        """Loads the raw _summary.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            summary_json: JSONFile = self._results_data_files["summary"]["json"][0]
            self.summary = Summary(parent=self, data=summary_json.data)
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_summary.json" file does not exist. Summary data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
      
    def _load_targets(self) -> None:
        """Load the raw _occtargets.json data in to the PathfinderModel
        
        Returns:
            None
        """
        try:
            targets_json: List[JSONFile] = self._results_data_files["targets"]['json'][0]
            for key, data in targets_json.data.items():
                self._targets[key] = Target(name=key, data=data)
            self.targets = TargetCollection(self._targets.values())
        except KeyError as e:
            if not get_arguments().quiet:
                self._log.warning(f'The "{self.get_model_name()}_targets.json" file does not exist. Targets data will not be available for this simulation. Make sure you have enabled JSON output in Pathfinder.')
    
    def take_screenshot(
        self, 
        ssview: str | List[str], 
        ssname: str | List[str] | None = None, sswidth: int = 800, 
        ssheight: int = 600, 
        sstime: float | int = 0
    ) -> List[str]:
        if 'nt' in sys.builtin_module_names:
            apps = [app for app in winapps.search_installed("pathfinder")]
            app = apps[0]
            
            results_path = f"{app.install_location}{os.sep}PathfinderResults.exe"
            return super()._take_screenshot(results_path, ssview, ssname, sswidth, ssheight, sstime)
        else:
            self._log.error("Tried to take a screenshot on a non-Windows platform")