"""
lca_carbon_workflow module
Returns the summarize of envelope and energy systems
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2024 Concordia CERC group
Project Designer and Developer: Alireza Adli
alireza.adli@mail.concordia.ca
Theoritical Support for LCA emissions:
Mohammad Seyedabadi mohammad.seyedabadi@mail.concordia.ca
"""

from pathlib import Path

from input_geojson_content import InputGeoJsonContent

from hub.imports.geometry_factory import GeometryFactory
from hub.imports.construction_factory import ConstructionFactory
from hub.helpers.dictionaries import Dictionaries

from city_model_structure.life_cycle_assessment.access_nrcan_catalogue \
  import AccessNrcanCatalog
from city_model_structure.life_cycle_assessment.opening_emission \
  import OpeningEmission
from city_model_structure.life_cycle_assessment.envelope_emission \
  import EnvelopeEmission
from city_model_structure.life_cycle_assessment.lca_end_of_life_carbon \
  import EndOfLifeEmission


class LCACarbonWorkflow:
  def __init__(
          self,
          city_path,
          archetypes_catalog_file_name,
          constructions_catalog_file,
          catalog='nrcan',
          building_parameters=('height', 'year_of_construction', 'function')):
    """
      LCACarbonWorkflow takes a number of buildings and enrich the city object
      using cerc-hub GeometryFactory and ConstructionFactory. Then it
      calculates embodied and end of life carbon emission of each building.
      It puts out the results of opening and envelop emission for each
      mentioned cycle separately.
      Final results will be stored in the below attributes:
        building_envelope_emission: <class 'float'>
        building_opening_emission: <class 'float'>
        building_component_emission: <class 'float'>
        building_envelope_end_of_life_emission: <class 'float'>
        building_opening_end_of_life_emission: <class 'float'>
        building_component_end_of_life_emission: <class 'float'>

      The above attributes will be computed when the calculate_emission()
      method of a LCACarbonWorkflow object is called.

      :param city_path: Either a path to the buildings (GeoJson)
      file or the content of such a file.
      :param archetypes_catalog_file_name: Path to the buildings'
      archetypes (JSON).
      :param constructions_catalog_file: Path to the construction materials
      data.
      :param catalog: Type of the catalog (in this case 'nrcan', the default
       argument)
      :param building_parameters: Parameters used for using the catalog (in
      this case three default arguments)
    """
    self.file_path = Path(__file__).parent / 'input_files' / InputGeoJsonContent(
      city_path).content
    self.catalogs_path = Path(__file__).parent / 'input_files'
    self.archetypes_catalog_file_name = archetypes_catalog_file_name
    self.constructions_catalog_file = constructions_catalog_file
    self.nrcan_catalogs = AccessNrcanCatalog(
      self.catalogs_path,
      archetypes=self.archetypes_catalog_file_name,
      constructions=self.constructions_catalog_file)
    self.out_path = (Path(__file__).parent / 'out_files')
    self.handler = catalog
    self.height, self.year_of_construction, self.function = \
        building_parameters

    print('[simulation start]')

    self.city = GeometryFactory(
      'geojson',
      path=self.file_path,
      height_field=self.height,
      year_of_construction_field=self.year_of_construction,
      function_field=self.function,
      function_to_hub=Dictionaries().montreal_function_to_hub_function).city
    print(f'city created from {self.file_path}')
    ConstructionFactory(self.handler, self.city).enrich()

    self.building_envelope_emission = []
    self.building_opening_emission = []
    self.building_component_emission = []
    self.building_envelope_end_of_life_emission = []
    self.building_opening_end_of_life_emission = []
    self.building_component_end_of_life_emission = []

  def calculate_building_component_emission(self, building):
    """
      This method is the core of the whole class. It takes each building
      contained in the city object and calculates and returns the envelope
      and opening emission of the embodied and end of life cycles. The building
      comes from the calculate_emission() method which goes through every
      building of the city object (meaning the input of the LCACarbonWorkflow
      object.
      The calculate_building_component_emission() method goes through each
      surface of the given building then each boundary of that surface to
      calculate the embodied and end of life emission of openings and the
      envelope of the building. It is being carried out by utilizing to
      hidden methods of the current class (methods contain description.)
      At the end, a tuple will be returned, containing the emissions
      attributes. The tuple will be unpacked in the calculate_emission()
      method. The attributes and their types are explained in the constructor.
      The building parameter comes from the calculate_emission() method
      which iterates through the city object buildings.
      :param building: <class 'hub.city_model_structure.building.Building'>
      :return: tuple
    """
    surface_envelope_emission = []
    surface_opening_emission = []
    surface_envelope_end_of_life_emission = []
    surface_opening_end_of_life_emission = []
    opaque_surface_code = self.nrcan_catalogs.find_opaque_surface(
      self.nrcan_catalogs.hub_to_nrcan_function(building.function),
      self.nrcan_catalogs.year_to_period_of_construction(
        building.year_of_construction),
      '6')

    for surface in building.surfaces:
      boundary_envelope_emission = []
      boundary_opening_emission = []
      boundary_envelope_end_of_life_emission = []
      boundary_opening_end_of_life_emission = []

      for boundary in surface.associated_thermal_boundaries:
        opening_emission = None
        opening_end_of_life_emission = None
        layer_emission, layer_end_of_life_emission = \
            self._calculate_envelope_emission(boundary)
        boundary_envelope_emission += layer_emission
        boundary_envelope_end_of_life_emission += layer_end_of_life_emission

        if boundary.window_ratio:
          opening_emission, opening_end_of_life_emission = \
            self._calculate_opening_emission(
              building, surface, boundary, opaque_surface_code)
        if opening_emission:
          boundary_opening_emission += opening_emission
          boundary_opening_end_of_life_emission += opening_end_of_life_emission
      if boundary_opening_emission:
        surface_opening_emission += boundary_opening_emission
        surface_opening_end_of_life_emission += \
            boundary_opening_end_of_life_emission
      surface_envelope_emission += boundary_envelope_emission
      surface_envelope_end_of_life_emission += \
          boundary_envelope_end_of_life_emission
    building_envelope_emission = sum(surface_envelope_emission)
    building_envelope_workload = sum(surface_envelope_end_of_life_emission)
    building_opening_emission = sum(surface_opening_emission)
    building_opening_workload = sum(surface_opening_end_of_life_emission)
    building_component_emission = \
        building_envelope_emission + building_opening_emission
    building_component_workload = \
        building_envelope_workload + building_opening_workload
    return building_envelope_emission, building_opening_emission, \
        building_component_emission, building_envelope_workload, \
        building_opening_workload, building_component_workload

  def _calculate_envelope_emission(self, boundary):
    """
      The method calculates embodied and end of life emission of the building's
      envelope by iterating through each building boundary's layers. The
      argument corresponding to the boundary parameter comes from the
      calculate_building_component_emission() method. The output also is used
      in the calculate_building_component_emission() method. So the current
      method is hidden to the user.
      The method utilizes the EnvelopeEmission and EndOfLifeEmission classes of
      (currently named) life_cycle_assessment series of class.
      :param boundary: <class
      'hub.city_model_structure.
      building_demand.thermal_boundary.ThermalBoundary'>
      :return: tuple
    """
    layer_emission = []
    layer_end_of_life_emission = []
    for layer in boundary.layers:
      if not layer.no_mass:
        layer_material = \
         self.nrcan_catalogs.search_material(layer.material_name)
        layer_emission.append(EnvelopeEmission(
          layer_material['embodied_carbon'],
          boundary.opaque_area,
          layer.thickness, layer.density).calculate_envelope_emission())

        boundary_workload = \
            boundary.opaque_area * \
            layer.thickness * \
            layer.density
        layer_end_of_life_emission.append(EndOfLifeEmission(
          layer_material['recycling_ratio'],
          layer_material['onsite_recycling_ratio'],
          layer_material['company_recycling_ratio'],
          layer_material['landfilling_ratio'],
          boundary_workload).calculate_end_of_life_emission())
    return layer_emission, layer_end_of_life_emission

  def _calculate_opening_emission(
          self,
          building, surface, boundary, opaque_surface_code,
          density=2579):
    """
      This calculates the opening emission by iterating through each thermal
      opening of each building's boundary. It is done based on the mentioned
      parameters.
      The arguments come from the calculate_building_component_emission()
      method and the output is used in the same method. So the current method
      is hidden to the user.
      Windows have the assumed density of 2579 kg/m3
      Window's thickness assumed the same as wall's thickness
      These two values are being used to calculate window's workload for
      the End of Life emission evaluation.
      The method utilizes the OpeningEmission and EndOfLifeEmission classes of
      (currently named) life_cycle_assessment series of class.
      :param building: <class 'hub.city_model_structure.building.Building'>
      :param surface: <class
      'hub.city_model_structure.building_demand.surface.Surface'>
      :param boundary:
      hub.city_model_structure.building_demand.thermal_boundary.ThermalBoundary
      :param opaque_surface_code: str
      :param density: int
      :return: tuple
    """
    opening_emission = []
    opening_end_of_life_emission = []
    for opening in boundary.thermal_openings:
      transparent_surface_type = 'Window'
      if building.year_of_construction >= 2020 and \
              surface.type == 'Roof':
        transparent_surface_type = 'Skylight'
      opening_material = self.nrcan_catalogs.search_transparent_surfaces(
          transparent_surface_type, opaque_surface_code)
      opening_emission.append(
        OpeningEmission(opening_material['embodied_carbon'],
                        opening.area).calculate_opening_emission())

      window_workload = opening.area * boundary.thickness * density
      opening_end_of_life_emission.append(EndOfLifeEmission(
          opening_material['recycling_ratio'],
          opening_material['onsite_recycling_ratio'],
          opening_material['company_recycling_ratio'],
          opening_material['landfilling_ratio'],
          window_workload).calculate_end_of_life_emission())
    return opening_emission, opening_end_of_life_emission

  def calculate_emission(self):
    """
      It iterates through the city object and gives each building to the
      calculate_building_component_emission() method. Then it unpack the results
      of the mentioned method to the (currently six) attributes which hold the
      final results. These attributes are mentioned in the constructor method
      description.
    """
    for building in self.city.buildings:
      envelope_emission, opening_emission, component_emission, \
       envelope_end_of_life_emission, \
       opening_end_of_life_emission, \
       component_end_of_life_emission = \
       self.calculate_building_component_emission(building)
      self.building_envelope_emission.append(envelope_emission)
      self.building_opening_emission.append(opening_emission)
      self.building_component_emission.append(component_emission)
      self.building_envelope_end_of_life_emission.append(
        envelope_end_of_life_emission)
      self.building_opening_end_of_life_emission.append(
        opening_end_of_life_emission)
      self.building_component_end_of_life_emission.append(
        component_end_of_life_emission)