from __future__ import print_function

__author__ = "pbmanis"

"""
Decorator:
A class to insert biophysical mechanisms into a model.
This function attempts to automatically decorate a hoc-imported model set of sections
with appropriate conductances.

The class takes as input the object hf, which is an instance of morphology 
It also takes the cellType, a string that directs how conductances should be inserted.

"""

import string
import numpy as np
import cnmodel.util as nu
from cnmodel.util import Params


class Decorator:
    def __init__(self, cell, parMap=None, verify=False):

        cellType = cell.type.lower()
        self.channelInfo = Params(
            newCm=1.0,
            newRa=150.0,  # standard value
            newg_leak=0.000004935,
            eK_def=-85,
            eNa_def=50,
            ca_init=70e-6,  # free calcium in molar
            v_init=-80,  # mV
            pharmManip={
                "TTX": False,
                "ZD": False,
                "Cd": False,
                "DTX": False,
                "TEA": False,
                "XE": False,
            },
            cellType=cell.status["cellClass"],
            modelType=cell.status["modelType"],
            modelName=cell.status["modelName"],
            distanceMap=cell.hr.distanceMap,
            parMap=parMap,
        )
        self.excludeMechs = []  # ['ihvcn', 'kht', 'klt', 'nav11']
        print("modelType in dec: ", cell.status["modelType"])
        print("modelName in dec is ", cell.status["modelName"])
        print("cell type in dec: ", cellType)
        cell.channel_manager(
            modelName=cell.status["modelName"], modelType=cell.status["modelType"]
        )
        #        print 'Cell: \n', dir(cell)
        #        print 'mechanisms: ', cell.hr.mechanisms
        # gmapper allows us tor remap the names of mechanisms and their conductance names, which may
        # vary in the mod files.
        # The versions in the mechanisms directory here have been systematized, but this
        # dictionary may help when adding other conductances.

        self.gbar_mapper = {
            "nacn": "gbar",
            "kht": "gbar",
            "klt": "gbar",
            "leak": "gbar",
            "ihvcn": "gbar",
            "jsrna": "gbar",
            "nav11": "gbar",
            "nacncoop": "gbar",
            "hcnobo": "gbar",
        }
        self.erev_mapper = {
            "nacn": "ena",
            "kht": "ek",
            "klt": "ek",
            "leak": "erev",
            "ihvcn": "eh",
            "jsrna": "ena",
            "nav11": "ena",
            "nacncoop": "ena",
            "hcnobo": "eh",
        }
        self.vshift_mapper = {
            "nacn": None,
            "kht": None,
            "klt": None,
            "leak": None,
            "ihvcn": None,
            "jsrna": None,
            "nav11": "vsna",
            "nacncoop": "vsna",
            "hcnobo": None,
        }
        self._biophys(cell, verify=verify)
        print(
            "\033[1;31;40m Decorator: Model Decorated with channels (if this appears more than once per cell, there is a problem)\033[0m"
        )

    def _biophys(self, cell, verify=False):
        """
        Inputs: run parameter structure, model parameter structure
        verify = True to run through the inserted mechanisms and see that they are really there.
        Outputs: None
        Action: Channel insertion into model
        Side Effects:
            Sets conductances in every different kind of section
            Does not update any class variables (via self).

        original hoc code: Paul B. Manis, Ph.D.
        25 Sept. 2007
        Modified to use gca for HH formulation of calcium current
        14 Oct 2007
        converted for Python, 17 Oct 2012 (PB Manis)
        modified to use new hf hoc_reader class to access section types and mechanisms 10-14 Feb 2014 pbmanis
        """
        # check to see if we already did this
        # createFlag = False
        cellType = self.channelInfo.cellType
        parMap = self.channelInfo.parMap
        dmap = self.channelInfo.distanceMap
        if self.channelInfo is None:
            raise Exception("biophys - no parameters or info passed!")
        if verify:
            print(
                "Biophys: Inserting channels as if cell type is {:s} with modelType {:s}".format(
                    cellType, self.channelInfo.modelType
                )
            )

        cell.hr.mechanisms = []
        for s in list(cell.hr.sec_groups.keys()):
            sectype = self.remapSectionType(s.rsplit("[")[0])
            if sectype not in cell.channelMap.keys():
                print(
                    "encountered unknown section group type: %s  Not decorating"
                    % sectype
                )
                print("channels in map: ", cell.channelMap.keys())
                continue
            # print 'Biophys: Section type: ', sectype, 'from: ', s
            # print sectype
            # print 'channel mapping keys: ', cell.channelMap[sectype].keys()

            # here we go through all themechanisms in the ionchannels table for this cell and compartment type
            # note that a mechanism may have multiple parameters in the table (gbar, vshft), so we:
            #   a. only insert the mechanism once
            #   b. only adjust the relevant parameter

            for mechname in list(cell.channelMap[sectype].keys()):
                mech = mechname.split("_")[0]  # get the part before the _
                parameter = mechname.split("_")[1]  # and the part after
                if mech not in self.gbar_mapper.keys():
                    print("Mechanism %s not found? " % mech)
                    continue
                if mech in self.excludeMechs:
                    continue
                if verify:
                    print(
                        "Biophys: section group: {:s}  insert mechanism: {:s} at {:.8f}".format(
                            s, mech, cell.channelMap[sectype][mech]
                        )
                    )
                if mech not in cell.hr.mechanisms:
                    cell.hr.mechanisms.append(
                        mech
                    )  # just add the mechanism to our list
                x = nu.Mechanism(mech)
                if cell.hr.sec_groups[s] == set():
                    continue  # no sections of this type
                for sec in cell.hr.sec_groups[
                    s
                ]:  # insert into all the sections of this type (group)
                    try:
                        x.insert_into(cell.hr.get_section(sec))
                    except:
                        raise ValueError(
                            "Failed with mech: %s " % mech
                        )  # fail if cannot insert.
                    if verify:
                        print("   inserted %s into section " % mech, sec)

                gbar_setup = None
                gbar = 0.0
                if parameter == "gbar":
                    gbar = self.gbarAdjust(
                        cell, sectype, mechname, sec
                    )  # map density by location/distance
                    gbar_setup = "%s_%s" % (
                        self.gbar_mapper[mech],
                        mech,
                    )  # map name into .mod file name
                    # if parMap is not None and mech in parMap.keys():  # note, this allows parmap to have elements BESIDES mechanisms
                    #     if verify:
                    #         print 'parMap[mech]', mech, parMap[mech], gbar,
                    #     gbar = gbar * parMap[mech]  # change gbar here...
                    #     if verify:
                    print("####### new gbar: ", gbar)

                vshift_setup = None
                vshift = 0.0
                # print 'Parameter: ', parameter, mech, self.vshift_mapper[mech]
                if parameter == "vshift" and self.vshift_mapper[mech] is not None:
                    vshift_setup = "%s_%s" % (
                        self.vshift_mapper[mech],
                        mech,
                    )  # map voltage shift
                    vshift = cell.channelMap[sectype][mechname]
                    print(
                        "*********   mech: gbar, vshift: ", gbar, vshift, vshift_setup
                    )
                    exit()

                cell.hr.h.Ra = self.channelInfo.newRa
                for sec in cell.hr.sec_groups[
                    s
                ]:  # now set conductances and other parameters as requested
                    cell.hr.get_section(sec).Ra = self.channelInfo.newRa  # set Ra here
                    if gbar_setup is not None:
                        setattr(
                            cell.hr.get_section(sec), gbar_setup, gbar
                        )  # set conductance magnitude
                    #   print('gbar_setup: %s %s' % (sectype, gbar_setup), gbar)
                    if vshift_setup is not None:
                        try:
                            setattr(
                                cell.hr.get_section(sec), vshift_setup, vshift
                            )  # set conductance magnitude
                        except:
                            print(dir(cell.hr.get_section(sec)))
                            raise ValueError(
                                " cannot set mechanism attribute %s  ... %s "
                                % (vshift_setup, vshift)
                            )
                        # print('vshift_setup: %s %s' % (sectype, vshift_setup), vshift)

                    if hasattr(
                        cell, "channelErevMap"
                    ):  # may not always have this mapping
                        secobj = cell.hr.get_section(
                            sec
                        )  # get the NEURON section object
                        mechsinsec = cell.get_mechs(
                            secobj
                        )  # get list of mechanisms in this section
                        if (
                            mech in mechsinsec
                        ):  # confirm that the mechanism is really there
                            setrev = (
                                False
                            )  # We try two ways for different mechanisms - just flag it
                            try:
                                setattr(
                                    secobj,
                                    self.erev_mapper[mech],
                                    cell.channelErevMap[sectype][mech],
                                )
                                setrev = True
                                continue  # don't bother with second approach
                            except:
                                raise ValueError("erev set failed")
                                pass  # no error
                            try:
                                setattr(
                                    secobj(),
                                    self.erev_mapper[mech] + "_" + mech,
                                    cell.channelErevMap[sectype][mech],
                                )
                                setrev = True
                            except:
                                raise ValueError("Erev2 set failed")
                                pass  # no error report
                            if (
                                not setrev
                            ):  # here is our error report - soft, not crash.
                                print(
                                    "Failed to set reversal potential in section %s for mechanism %s"
                                    % (sec, mech)
                                )
                        # if mech in mechinsec and mech in self.vshift_mapper.keys():
                        #     try:
                        #         setattr(secobj, self.vshift_mapper[mech], cell.channelVshiftMap[sectype][mech])
                        #     except:
                        #         raise ValueError('Failed to set vshift for mech: %s  sectpe: %s' % (mech, sectype[mech]))

        if verify:
            self.channelValidate(cell)
        return cell

    def gbarAdjust(self, cell, sectype, mech, sec):
        gbar = cell.channelMap[sectype][mech]
        gbar_orig = gbar
        if sectype not in cell.distMap.keys():  # no map for this section type
            return gbar
        elif mech not in cell.distMap[sectype].keys():
            return gbar
        # mecanism exists in the distMap, so we will map gbar to distance from soma
        method = cell.distMap[sectype][mech]["gradient"]  # grab the type
        gminf = cell.distMap[sectype][mech]["gminf"]
        rate = cell.distMap[sectype][mech]["lambda"]
        #  print('sectype: %s  mech: %s method: %s  rate: %s' % (sectype, mech, method, rate))
        if method == "flat":
            return gbar
        if sec in self.channelInfo.distanceMap.keys():
            dist = self.channelInfo.distanceMap[sec]
        else:  # the sec should be in the map, but there could be a coding error that would break that relationship
            raise NameError(
                "gbarAdjust in channel_decorate.py: section %s not in distance map"
                % sec
            )
        if method == "linear":  # rate is "half" point drop
            # print('doing linear, orig gbar: ', gbar)
            gbar = gbar - dist * (gbar - gminf) / rate
            if gbar < 0.0:
                gbar = 0.0  # clip
        #    print('sec dist: %f  final gbar: %f' % (dist, gbar))
        elif method in ["exp", "expdown"]:
            gbar = (gbar - gminf) * np.exp(-dist / rate) + gminf
        if gbar < 0.0:
            gbar = 0.0
        # print 'gbaradjust: orig/adj: ', gbar_orig, gbar, method, dist, sectype
        return gbar

    def channelValidate(self, cell, verify=False):
        """
         verify mechanisms insertions -
         go through all the groups, and find inserted conductances and their values
         print the results to the terminal
        """
        print("\nChannel Validation")
        print("  Looking for sec_groups: ", sorted(cell.hr.sec_groups.keys()))
        print("  Available Channel Maps: ", sorted(cell.channelMap.keys()))
        secstuff = {}
        for s in list(cell.hr.sec_groups.keys()):
            sectype = self.remapSectionType(s.rsplit("[")[0])
            if sectype not in cell.channelMap.keys():
                if sectype in ["undefined"]:  # skip undefined sections
                    continue
                print(
                    "\033[1;31;40m Validation: encountered unknown section group type: %s  Cannot Validate"
                    % sectype
                )
                print("Cell morphology file: %s \033[0m" % cell.morphology_file)
                continue
            #            print 'Validating Section: %s' % s
            for mech in list(cell.channelMap[sectype].keys()):
                if mech not in self.gbar_mapper.keys():
                    continue
                if mech in self.excludeMechs:
                    continue
                if verify:
                    print(
                        "\tSection: %-15ss  found mechanism: %-8ss at %.5f"
                        % (s, mech, cell.channelMap[sectype][mech])
                    )
                x = nu.Mechanism(
                    mech
                )  # , {gmapper[mech]: self.channelMap[cellType][sectype][mech]})
                setup = "%s_%s" % (self.gbar_mapper[mech], mech)
                for sec in cell.hr.sec_groups[s]:
                    bar = getattr(cell.hr.get_section(sec), setup)
                    # print 'mech', mech
                    # print 'bar: ', bar
                    try:
                        Erev = getattr(cell.hr.get_section(sec), self.erev_mapper[mech])
                    except:
                        Erev = -999.0
                    # print 'erev: ', Erev
                    if sec in secstuff.keys():
                        secstuff[sec] += ", g_%s = %g [%.1f]" % (mech, bar, Erev)
                    else:
                        secstuff[sec] = "(%10s) g_%-6s = %g [%.1f] " % (
                            sectype,
                            mech,
                            bar,
                            Erev,
                        )
        if verify:
            for i, k in enumerate(secstuff.keys()):
                print("**%-20s " % k, secstuff[k])

    def remapSectionType(self, sectype):
        if sectype in ["AXON_0"]:
            sectype = "axon"

        if sectype in ["initseg", "initialsegment"]:
            sectype = "initialsegment"
        if sectype in ["dendscaled_0", "dendscaled_1", "dendscaled_2", "dendrite"]:
            sectype = "dendrite"
        if sectype in ["apical_dendrite"]:
            sectype = "secondarydendrite"
        return sectype
