#  Copyright 2020 Zeppelin Bend Pty Ltd
#
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at https://mozilla.org/MPL/2.0/.

from dataclassy import dataclass
from typing import Callable, Optional


from zepben.evolve.model import Organisation
from zepben.evolve.model import OrganisationRole
from zepben.evolve.model import WireInfo
from zepben.evolve.model import Asset
from zepben.evolve.model import Pole
from zepben.evolve.model import Streetlight
from zepben.evolve.model import AssetOrganisationRole
from zepben.evolve.model import Location
from zepben.evolve.model import Customer
from zepben.evolve.model import CustomerAgreement
from zepben.evolve.model import PricingStructure
from zepben.evolve.model import Tariff
from zepben.evolve.model import EndDevice, UsagePoint
from zepben.evolve.model import OperationalRestriction
from zepben.evolve.model import AuxiliaryEquipment
from zepben.evolve.model import BaseVoltage
from zepben.evolve.model import ConductingEquipment
from zepben.evolve.model import ConnectivityNode
from zepben.evolve.model import IdentifiedObject
from zepben.evolve.model import Equipment
from zepben.evolve.model import Terminal
from zepben.evolve.model import Diagram, DiagramObject
from zepben.evolve.model import Measurement
from zepben.evolve.model import Control
from zepben.evolve.model import RemoteSource
from zepben.evolve.model import RemoteControl
from zepben.evolve.model import AcLineSegment, Conductor
from zepben.evolve.model import EnergyConsumer, EnergyConsumerPhase
from zepben.evolve.model import EnergySource
from zepben.evolve.model import EnergySourcePhase
from zepben.evolve.model import PerLengthSequenceImpedance, Feeder, EquipmentContainer, Substation, GeographicalRegion, SubGeographicalRegion, \
    PowerSystemResource, PowerTransformer, PowerTransformerEnd, RatioTapChanger, TransformerEnd

__all__ = ["acls_to_plsi_resolver", "asset_to_asset_org_role_resolver", "asset_to_location_resolver", "pole_to_streetlight_resolver",
           "streetlight_to_pole_resolver", "aux_equip_to_term_resolver", "cond_equip_to_bv_resolver", "cond_equip_to_terminal_resolver",
           "conductor_to_wire_info_resolver", "conn_node_to_term_resolver", "control_to_remote_control_resolver", "cust_to_custagr_resolver",
           "custagr_to_cust_resolver", "custagr_to_ps_resolver", "diag_to_diagobj_resolver", "diagobj_to_diag_resolver", "ed_to_up_resolver",
           "ed_to_loc_resolver", "ec_to_ecp_resolver", "ecp_to_ec_resolver", "es_to_esp_resolver", "esp_to_es_resolver", "eq_to_curfeeder_resolver",
           "eq_to_ec_resolver", "eq_to_or_resolver", "eq_to_up_resolver", "ec_to_eq_resolver", "curfeeder_to_eq_resolver", "feeder_to_nes_resolver",
           "feeder_to_nht_resolver", "gr_to_sgr_resolver", "meas_to_rs_resolver", "or_to_eq_resolver", "orgr_to_org_resolver", "psr_to_loc_resolver",
           "pt_to_pte_resolver", "pte_to_pt_resolver", "ps_to_tariff_resolver", "rtc_to_te_resolver", "rc_to_cont_resolver", "rs_to_meas_resolver",
           "sgr_to_gr_resolver", "sgr_to_sub_resolver", "sub_to_feeder_resolver", "sub_to_sgr_resolver", "sub_to_circuit_resolver", "sub_to_eloop_resolver",
           "sub_to_loop_resolver", "term_to_ce_resolver", "term_to_cn_resolver", "te_to_term_resolver", "te_to_bv_resolver", "te_to_rtc_resolver",
           "up_to_ed_resolver", "up_to_eq_resolver", "up_to_loc_resolver", "circuit_to_loop_resolver", "circuit_to_sub_resolver", "circuit_to_term_resolver",
           "loop_to_circuit_resolver", "loop_to_esub_resolver", "loop_to_sub_resolver", "BoundReferenceResolver", "ReferenceResolver", "UnresolvedReference"]

from zepben.evolve.model import Circuit, Loop


@dataclass(frozen=True, eq=False, slots=True)
class ReferenceResolver(object):
    from_class: type
    to_class: type
    resolve: Callable[[IdentifiedObject, IdentifiedObject], None]

    def __eq__(self, other):
        return self.from_class is other.from_class and self.to_class is other.to_class and self.resolve is other.resolve

    def __neq__(self, other):
        return self.from_class is not other.from_class or self.to_class is not other.to_class or self.resolve is not other.resolve


@dataclass(frozen=True, eq=False, slots=True)
class BoundReferenceResolver(object):
    from_obj: IdentifiedObject
    resolver: ReferenceResolver
    reverse_resolver: Optional[ReferenceResolver]

    def __eq__(self, other):
        return self.from_obj is other.from_obj and self.resolver is other.resolver

    def __neq__(self, other):
        return self.from_obj is not other.from_obj or self.resolver is not other.resolver


@dataclass(frozen=True, eq=False, slots=True)
class UnresolvedReference(object):
    from_ref: IdentifiedObject
    to_mrid: str
    resolver: ReferenceResolver


def _resolve_ce_term(ce, t):
    t.conducting_equipment = ce
    ce.add_terminal(t)


def _resolve_pt_pte(pt, pte):
    pte.power_transformer = pt
    pt.add_end(pte)


def _resolve_diag_diagobj(diag, diag_obj):
    diag_obj.diagram = diag
    diag.add_diagram_object(diag_obj)


acls_to_plsi_resolver = ReferenceResolver(AcLineSegment, PerLengthSequenceImpedance, lambda t, r: setattr(t, 'per_length_sequence_impedance', r))

asset_to_asset_org_role_resolver = ReferenceResolver(Asset, AssetOrganisationRole, lambda t, r: t.add_organisation_role(r))
asset_to_location_resolver = ReferenceResolver(Asset, Location, lambda t, r: setattr(t, 'location', r))

pole_to_streetlight_resolver = ReferenceResolver(Pole, Streetlight, lambda t, r: t.add_streetlight(r))
streetlight_to_pole_resolver = ReferenceResolver(Streetlight, Pole, lambda t, r: setattr(t, 'pole', r))

aux_equip_to_term_resolver = ReferenceResolver(AuxiliaryEquipment, Terminal, lambda t, r: setattr(t, 'terminal', r))

cond_equip_to_bv_resolver = ReferenceResolver(ConductingEquipment, BaseVoltage, lambda t, r: setattr(t, 'base_voltage', r))
cond_equip_to_terminal_resolver = ReferenceResolver(ConductingEquipment, Terminal, _resolve_ce_term)

conductor_to_wire_info_resolver = ReferenceResolver(Conductor, WireInfo, lambda t, r: setattr(t, 'asset_info', r))

conn_node_to_term_resolver = ReferenceResolver(ConnectivityNode, Terminal, lambda t, r: t.add_terminal(r))

control_to_remote_control_resolver = ReferenceResolver(Control, RemoteControl, lambda t, r: setattr(t, 'remote_control', r))

cust_to_custagr_resolver = ReferenceResolver(Customer, CustomerAgreement, lambda t, r: t.add_agreement(r))
custagr_to_cust_resolver = ReferenceResolver(CustomerAgreement, Customer, lambda t, r: setattr(t, 'customer', r))

custagr_to_ps_resolver = ReferenceResolver(CustomerAgreement, PricingStructure, lambda t, r: t.add_pricing_structure(r))

diag_to_diagobj_resolver = ReferenceResolver(Diagram, DiagramObject, _resolve_diag_diagobj)
diagobj_to_diag_resolver = ReferenceResolver(DiagramObject, Diagram, lambda t, r: setattr(t, 'diagram', r))

ed_to_up_resolver = ReferenceResolver(EndDevice, UsagePoint, lambda t, r: t.add_usage_point(r))
ed_to_loc_resolver = ReferenceResolver(EndDevice, Location, lambda t, r: setattr(t, 'service_location', r))

ec_to_ecp_resolver = ReferenceResolver(EnergyConsumer, EnergyConsumerPhase, lambda t, r: t.add_phase(r))
ecp_to_ec_resolver = ReferenceResolver(EnergyConsumerPhase, EnergyConsumer, lambda t, r: setattr(t, 'energy_consumer', r))

es_to_esp_resolver = ReferenceResolver(EnergySource, EnergySourcePhase, lambda t, r: t.add_phase(r))
esp_to_es_resolver = ReferenceResolver(EnergySourcePhase, EnergySource, lambda t, r: setattr(t, 'energy_source', r))

eq_to_curfeeder_resolver = ReferenceResolver(Equipment, Feeder, lambda t, r: t.add_current_feeder(r))
eq_to_ec_resolver = ReferenceResolver(Equipment, EquipmentContainer, lambda t, r: t.add_container(r))
eq_to_or_resolver = ReferenceResolver(Equipment, OperationalRestriction, lambda t, r: t.add_restriction(r))
eq_to_up_resolver = ReferenceResolver(Equipment, UsagePoint, lambda t, r: t.add_usage_point(r))

ec_to_eq_resolver = ReferenceResolver(EquipmentContainer, Equipment, lambda t, r: t.add_equipment(r))

curfeeder_to_eq_resolver = ReferenceResolver(Feeder, Equipment, lambda t, r: t.add_equipment(r))

feeder_to_nes_resolver = ReferenceResolver(Feeder, Substation, lambda t, r: setattr(t, 'normal_energizing_substation', r))
feeder_to_nht_resolver = ReferenceResolver(Feeder, Terminal, lambda t, r: setattr(t, 'normal_head_terminal', r))

gr_to_sgr_resolver = ReferenceResolver(GeographicalRegion, SubGeographicalRegion, lambda t, r: t.add_sub_geographical_region(r))

meas_to_rs_resolver = ReferenceResolver(Measurement, RemoteSource, lambda t, r: setattr(t, 'remote_source', r))

or_to_eq_resolver = ReferenceResolver(OperationalRestriction, Equipment, lambda t, r: t.add_equipment(r))

orgr_to_org_resolver = ReferenceResolver(OrganisationRole, Organisation, lambda t, r: setattr(t, 'organisation', r))

psr_to_loc_resolver = ReferenceResolver(PowerSystemResource, Location, lambda t, r: setattr(t, 'location', r))

pt_to_pte_resolver = ReferenceResolver(PowerTransformer, PowerTransformerEnd, _resolve_pt_pte)
pte_to_pt_resolver = ReferenceResolver(PowerTransformerEnd, PowerTransformer, lambda t, r: setattr(t, 'power_transformer', r))

ps_to_tariff_resolver = ReferenceResolver(PricingStructure, Tariff, lambda t, r: t.tariff_to_cim(r))

rtc_to_te_resolver = ReferenceResolver(RatioTapChanger, PowerTransformerEnd, lambda t, r: setattr(t, 'transformer_end', r))

rc_to_cont_resolver = ReferenceResolver(RemoteControl, Control, lambda t, r: setattr(t, 'control', r))

rs_to_meas_resolver = ReferenceResolver(RemoteSource, Measurement, lambda t, r: setattr(t, 'measurement', r))

sgr_to_gr_resolver = ReferenceResolver(SubGeographicalRegion, GeographicalRegion, lambda t, r: setattr(t, 'geographical_region', r))
sgr_to_sub_resolver = ReferenceResolver(SubGeographicalRegion, Substation, lambda t, r: t.add_substation(r))

sub_to_feeder_resolver = ReferenceResolver(Substation, Feeder, lambda t, r: t.add_feeder(r))
sub_to_sgr_resolver = ReferenceResolver(Substation, SubGeographicalRegion, lambda t, r: setattr(t, 'sub_geographical_region', r))
sub_to_circuit_resolver = ReferenceResolver(Substation, Circuit, lambda t, r: t.add_circuit(r))
sub_to_loop_resolver = ReferenceResolver(Substation, Loop, lambda t, r: t.add_loop(r))
sub_to_eloop_resolver = ReferenceResolver(Substation, Loop, lambda t, r: t.add_energized_loop(r))

term_to_ce_resolver = ReferenceResolver(Terminal, ConductingEquipment, lambda t, r: setattr(t, 'conducting_equipment', r))
term_to_cn_resolver = ReferenceResolver(Terminal, ConnectivityNode, lambda t, r: setattr(t, 'connectivity_node', r))

te_to_term_resolver = ReferenceResolver(TransformerEnd, Terminal, lambda t, r: setattr(t, 'terminal', r))
te_to_bv_resolver = ReferenceResolver(TransformerEnd, BaseVoltage, lambda t, r: setattr(t, 'base_voltage', r))
te_to_rtc_resolver = ReferenceResolver(TransformerEnd, RatioTapChanger, lambda t, r: setattr(t, 'ratio_tap_changer', r))

up_to_ed_resolver = ReferenceResolver(UsagePoint, EndDevice, lambda t, r: t.add_end_device(r))
up_to_eq_resolver = ReferenceResolver(UsagePoint, Equipment, lambda t, r: t.add_equipment(r))
up_to_loc_resolver = ReferenceResolver(UsagePoint, Location, lambda t, r: setattr(t, 'usage_point_location', r))

circuit_to_term_resolver = ReferenceResolver(Circuit, Terminal, lambda t, r: t.add_terminal(r))
circuit_to_loop_resolver = ReferenceResolver(Circuit, Loop, lambda t, r: setattr(t, 'loop', r))
circuit_to_sub_resolver = ReferenceResolver(Circuit, Substation, lambda t, r: t.add_substation(r))

loop_to_circuit_resolver = ReferenceResolver(Loop, Circuit, lambda t, r: t.add_circuit(r))
loop_to_sub_resolver = ReferenceResolver(Loop, Substation, lambda t, r: t.add_substation(r))
loop_to_esub_resolver = ReferenceResolver(Loop, Substation, lambda t, r: t.add_energizing_substation(r))


