"""
LcaModel class-- specifies what makes up the core model
I don't think this is used anywhere
"""

from antelope import EntityNotFound, q_node_activity
from .conventions import logistics_summary_ref, transport_model

from .interface import LcaModelInterface
from .markets import build_market_mix

from collections import namedtuple


EntitySpec = namedtuple('EntitySpec', ('link', 'ref', 'name', 'group'))


class LcaModel(LcaModelInterface):
    def __init__(self, fg, models, activity_container='Activity Container'):
        """

        :param fg: The LcForeground where the model is built
        :param models: the LcForeground where supporting models are stored
        :param activity_container: external ref for activity_container fragment
        """
        self._fg = fg
        self._models = models
        self._ac = activity_container

    @property
    def fg(self):
        return self._fg

    @property
    def models(self):
        return self._models

    # conventional names
    @property
    def _act(self):
        return q_node_activity(self._fg)

    @property
    def _fu_name(self):
        return 'MFA Activity (%s)' % self.scope

    @property
    def lca_entities(self):
        """
        Generates entity definitions suitable for fg.add_or_retrieve: link, ref, name[, group]
        where link is local, ref is the reference quantity or reference unit, name is for printing,
        group [stage] is optional
        :return: generate a list of link, ref, name, group tuples
        """

        for name, link in self.transporters():
            yield EntitySpec(link, 't*km', name, 'Transporters')

        for name, link in self.products():
            yield EntitySpec(link, 'mass', name, 'MFA Products')

    def new_functional_unit(self, name, external_ref=None, **kwargs):
        """
        This should really be a foreground method
        :param name:
        :param external_ref:
        :param kwargs:
        :return:
        """
        if external_ref is None:
            fu = self.fg.new_flow(name, self._act, **kwargs)
        else:
            fu = self.fg.add_or_retrieve(external_ref, self._act, name, strict=True, **kwargs)
        return fu

    def validate_map(self, mapping):
        """
        An MFA study is defined by maps which take MFA flows to LCA terminations.  Given a mapping, this function
        returns True if every key in the mapping is generated by lca_entities
        :param mapping:
        :return:
        """
        fail = 0
        es = [d[0] for d in self.lca_entities]
        for k in mapping.keys():
            if k not in es:
                print('Missing key: %s' % k)
                fail += 1
        return fail == 0

    @property
    def functional_unit(self):
        return self.new_functional_unit(self._fu_name, external_ref=self.ref)

    def _make_activity_container(self):
        f_u = self.functional_unit
        a_c = self.fg.new_fragment(f_u, 'Output', knobs=dict())
        self.fg.observe(a_c, name=self._ac)
        return a_c

    def _check_activity_container(self):
        return self.fg.get(self._ac)

    @property
    def activity_container(self):
        try:
            return self._check_activity_container()
        except EntityNotFound:
            return self._make_activity_container()

    @property
    def knobs(self):
        a = self.activity_container
        knobs_map = a['knobs']
        knobs = {k: (self._fg[v] or self._models[v]) for k, v in knobs_map.items()}
        return knobs

    def add_knob(self, knob, fragment):
        a = self.activity_container
        print('Adding knob %s -> %s' % (knob, fragment.name))
        a['knobs'][knob] = fragment.uuid  # we use the uuid so it will serialize-- this

    def has_knob(self, knob):
        a = self.activity_container
        return knob in a['knobs']

    def logistics_summary(self, sc_frag):
        log_ref = logistics_summary_ref(sc_frag)

        if self._fg[log_ref] is None:
            net_inflow = sum(k.value for k in sc_frag.cutoffs() if k.direction == 'Input' and k.unit == 'kg')

            f = self._fg.new_fragment(self.reference_flow, 'Input', value=net_inflow)
            self._fg.new_fragment(self.reference_flow, 'Output', balance=True, parent=f)
            for k in sc_frag.cutoffs():
                if k.direction == 'Input' and k.unit == 't*km':
                    t = self._fg.new_fragment(k.flow, k.direction, value=k.value, parent=f)
                    self._fg.observe(t)
            self._fg.observe(f, name=log_ref)

        return self._fg[log_ref]

    def mfa_freight_market(self, flow, recipe):
        frag = transport_model(flow)
        if self._fg[frag] is None:
            print('Making new freight market fragment %s' % flow.external_ref)
            k = self._fg.new_fragment(flow, 'Output')
            if build_market_mix(self._fg, k, self._models, recipe, freight=True):
                self._fg.observe(k, name=frag)
            else:
                print('FAILED MARKET MIX: Not building model %s' % frag)
                self._fg.delete_fragment(k)
        return self._fg[frag]

