from __future__ import print_function
from neuron import h
from ..util import nstomho
from ..util import Params
import numpy as np
from .cell import Cell
from .. import synapses
from .. import an_model
from .. import data

__all__ = ["SGC", "SGC_TypeI", "DummySGC"]


class SGC(Cell):
    type = "sgc"

    @classmethod
    def create(cls, model="I", species="mouse", **kwds):
        if model == "dummy":
            return DummySGC(**kwds)
        elif model == "I":
            return SGC_TypeI(species=species, **kwds)
        else:
            raise ValueError("SGC model %s is unknown", model)

    def __init__(self, cf=None, sr=None):
        Cell.__init__(self)
        self._cf = cf
        self._sr = sr
        self.spike_source = None  # used by DummySGC to connect VecStim to terminal

    @property
    def cf(self):
        """ Center frequency
        """
        return self._cf

    @property
    def sr(self):
        """ Spontaneous rate group. 1=low, 2=mid, 3=high
        """
        return self._sr

    def make_terminal(self, post_cell, term_type, **kwds):
        """Create a StochasticTerminal and configure it according to the 
        postsynaptic cell type.
        """
        pre_sec = self.soma

        # Return a simple terminal unless a stochastic terminal was requested.
        if term_type == "simple":
            return synapses.SimpleTerminal(
                pre_sec, post_cell, spike_source=self.spike_source, **kwds
            )
        elif term_type == "multisite":
            n_rsites = data.get(
                "sgc_synapse",
                species="mouse",
                post_type=post_cell.type,
                field="n_rsites",
            )
            opts = {"nzones": n_rsites, "delay": 0, "dep_flag": 1}
            opts.update(kwds)
            # when created, depflag is set True (1) so that we compute the DKR D*F to get release
            # this can be modified prior to the run by setting the terminal(s) so that dep_flag is 0
            # (no DKR: constant release probability)
            term = synapses.StochasticTerminal(
                pre_sec, post_cell, spike_source=self.spike_source, **opts
            )

            kinetics = data.get(
                "sgc_ampa_kinetics",
                species="mouse",
                post_type=post_cell.type,
                field=["tau_g", "amp_g"],
            )
            term.set_params(**kinetics)
            dynamics = data.get(
                "sgc_release_dynamics",
                species="mouse",
                post_type=post_cell.type,
                field=["F", "k0", "kmax", "kd", "kf", "taud", "tauf", "dD", "dF"],
            )
            term.set_params(**dynamics)
            return term
        else:
            raise ValueError("Unsupported terminal type %s" % term_type)


class DummySGC(SGC):
    """ SGC class with no cell body; this cell only replays a predetermined
    spike train.
    """

    def __init__(self, cf=None, sr=None, simulator=None):
        """
        Parameters
        ----------
        cf : float (default: None)
            Required: the characteristic frequency for the SGC
        
        sr : int (default None)
            required : Selects the spontaneous rate group from the
            Zilany et al (2010) model. 1 = LSR, 2 = MSR, 3 = HSR
        
        simulator : 'cochlea' | 'matlab' | None (default None)
            Sets the simulator interface that will be used. All models
            currently use the Zilany et al. model, but the simulator can
            be run though a Python-interface directly to the Matlab code
            as publicy available, (simulator='matlab'), or can be run through
            Rudnicki & Hemmert's Python interface to the simulator's C code 
            (simulator='cochlea').
        
        """
        self._simulator = simulator
        SGC.__init__(self, cf, sr)
        self.vecstim = h.VecStim()

        # this causes the terminal to receive events from the VecStim:
        self.spike_source = self.vecstim

        # just an empty section for holding the terminal
        self.add_section(h.Section(), "soma")
        self.status = {
            "soma": True,
            "axon": False,
            "dendrites": False,
            "pumps": False,
            "na": None,
            "species": None,
            "modelType": "dummy",
            "ttx": False,
            "name": "DummysGC",
            "morphology": None,
            "decorator": None,
            "temperature": None,
        }

    def set_spiketrain(self, times):
        """ Set the times of spikes (in seconds) to be replayed by the cell.
        """
        self._spiketrain = times
        self._stvec = h.Vector(times)
        self.vecstim.play(self._stvec)

    def set_sound_stim(self, stim, seed, simulator=None):
        """ Set the sound stimulus used to generate this cell's spike train.
        """
        self._sound_stim = stim
        spikes = self.generate_spiketrain(stim, seed, simulator)
        self.set_spiketrain(spikes)

    def generate_spiketrain(self, stim, seed, simulator=None):
        if simulator is None:
            simulator = self._simulator
        spikes = an_model.get_spiketrain(
            cf=self.cf, sr=self.sr, seed=seed, stim=stim, simulator=simulator
        )
        return spikes * 1000


class SGC_TypeI(SGC):
    """
    Spiral ganglion cell model
    
    """

    def __init__(
        self,
        morphology=None,
        decorator=None,
        nach=None,
        ttx=False,
        species="guineapig",
        modelType="bm",
        cf=None,
        sr=None,
        debug=False,
    ):
        """
        Initialize a spiral ganglion Type I cell, based on a bushy cell model.
        Modifications to the cell can be made by calling the methods below. These include
        converting to a model with modified size and conductances (experimental), and
        and changing the sodium channel conductances.
        
        Parameters
        ----------
        morphology : string (default: None)
            a file name to read the cell morphology from. If a valid file is found, a cell is constructed
            as a cable model from the hoc file.
            If None (default), the only a point model is made, exactly according to RM03.
            
        decorator : Python function (default: None)
            decorator is a function that "decorates" the morphology with ion channels according
            to a set of rules.
            If None, a default set of channels aer inserted into the first soma section, and the
            rest of the structure is "bare".

        nach : string (default: 'na')
            nach selects the type of sodium channel that will be used in the model. A channel mechanim
            by that name must exist. The default is jsrna (Rothman et al., 1993)
        
        ttx : Boolean (default: False)
            If ttx is True, then the sodium channel conductance is set to 0 everywhere in the cell.
            Currently, this is not implemented.
        
        species: string (default 'guineapig')
            species defines the channel density that will be inserted for different models. Note that
            if a decorator function is specified, this argument is ignored.
            
        modelType: string (default: None)
            modelType specifies the type of the model that will be used. SGC model know about "a" (apical)
            and "bm" (basal-middle) models, based on Liu et al., JARO, 2014.
            modelType is passed to the decorator, or to species_scaling to adjust point models.

        cf : float (default: None)
            The CF for the auditory nerve fiber that this SGC represents.

        sr : string (default: None)
            The spontaneous rate group to which this fiber belongs. "LS", "MS", and "HS" are known values.

        debug: boolean (default: False)
            debug is a boolean flag. When set, there will be multiple printouts of progress and parameters.
            
        Returns
        -------
            Nothing
        
        """

        super(SGC_TypeI, self).__init__(cf=cf, sr=sr)
        if modelType == None:
            modelType = "bm"  # modelTypes are: a (apical), bm (basal middle)
        if nach == None:
            nach = "jsrna"
        self.status = {
            "soma": True,
            "axon": False,
            "dendrites": False,
            "pumps": False,
            "na": nach,
            "species": species,
            "modelType": modelType,
            "ttx": ttx,
            "name": "SGC",
            "morphology": morphology,
            "decorator": decorator,
            "temperature": None,
        }

        self.i_test_range = {
            "pulse": [(-0.3, 0.3, 0.02), (-0.03, 0.0, 0.005)]
        }  # include finer range as well
        self.vrange = [-75.0, -55.0]
        if morphology is None:
            """
            instantiate a basic soma-only ("point") model
            """
            soma = h.Section(
                name="SGC_Soma_%x" % id(self)
            )  # one compartment of about 29000 um2
            soma.nseg = 1
            self.add_section(soma, "soma")
        else:
            """
            instantiate a structured model with the morphology as specified by 
            the morphology file
            """
            self.set_morphology(morphology_file=morphology)

        # decorate the morphology with ion channels
        if decorator is None:  # basic model, only on the soma
            self.mechanisms = [nach, "klt", "kht", "leak"]
            if modelType == "a":
                self.mechanisms.append("ihsgcApical")
            elif modelType == "bm":
                self.mechanisms.append("ihsgcBasalMiddle")
            else:
                raise ValueError("Type %s not known for SGC model" % modelType)
            for mech in self.mechanisms:
                self.soma.insert(mech)
            self.soma.ek = self.e_k
            self.soma().leak.erev = self.e_leak
            self.species_scaling(
                silent=True, species=species, modelType=modelType
            )  # set the default type II cell parameters
        else:  # decorate according to a defined set of rules on all cell compartments
            self.decorate()
        self.save_all_mechs()  # save all mechanisms inserted, location and gbar values...
        self.get_mechs(self.soma)
        if debug:
            print("<< SGC: Spiral Ganglion Cell created >>")

    def get_cellpars(self, dataset, species="guineapig", celltype="sgc-a"):
        cellcap = data.get(
            dataset, species=species, cell_type=celltype, field="soma_Cap"
        )
        chtype = data.get(
            dataset, species=species, cell_type=celltype, field="soma_na_type"
        )
        pars = Params(soma_Cap=cellcap, natype=chtype)
        for g in [
            "soma_na_gbar",
            "soma_kht_gbar",
            "soma_klt_gbar",
            "soma_ihap_gbar",
            "soma_ihbm_gbar",
            "soma_ihap_eh",
            "soma_ihbm_eh",
            "soma_leak_gbar",
            "soma_leak_erev",
            "soma_e_k",
            "soma_e_na",
        ]:
            pars.additem(
                g, data.get(dataset, species=species, cell_type=celltype, field=g)
            )
        return pars

    def species_scaling(self, silent=True, species="guineapig", modelType="a"):
        """
        Adjust all of the conductances and the cell size according to the species requested.
        Used ONLY for point models.
        
        Parameters
        ----------
        species : string (default: 'guineapig')
            name of the species to use for scaling the conductances in the base point model
            Must be one of mouse or guineapig
        
        modelType: string (default: 'a')
            definition of HCN model type from Liu et al. JARO 2014:
            'a' for apical model
            'bm' for basal-middle model
        
        silent : boolean (default: True)
            run silently (True) or verbosely (False)
        
        Returns
        -------
        Nothing
        
        Notes
        -----
        The 'guineapig' model uses the mouse HCN channel model, verbatim. This may not
        be appropriate, given that the other conductances are scaled up.
        
        """

        soma = self.soma
        if modelType == "a":
            celltype = "sgc-a"
        elif modelType == "bm":
            celltype = "sgc-bm"
        else:
            raise ValueError("SGC: unrecognized model type %s " % modelType)

        if species == "mouse":
            self._valid_temperatures = (34.0,)
            if self.status["temperature"] is None:
                self.set_temperature(34.0)
            par = self.get_cellpars(
                "sgc_mouse_channels", species=species, celltype=celltype
            )
        elif species == "guineapig":
            # guinea pig data from Rothman and Manis, 2003, modelType II
            self._valid_temperatures = (22.0,)
            if self.status["temperature"] is None:
                self.set_temperature(22.0)
            par = self.get_cellpars(
                "sgc_guineapig_channels", species=species, celltype=celltype
            )

        self.set_soma_size_from_Cm(par.soma_Cap)
        self.adjust_na_chans(soma, gbar=par.soma_na_gbar)
        soma().kht.gbar = nstomho(par.soma_kht_gbar, self.somaarea)
        soma().klt.gbar = nstomho(par.soma_klt_gbar, self.somaarea)
        if celltype == "sgc-a":
            soma().ihsgcApical.gbar = nstomho(par.soma_ihap_gbar, self.somaarea)
            soma().ihsgcApical.eh = par.soma_ihap_eh
        elif celltype == "sgc-bm":
            soma().ihsgcBasalMiddle.gbar = nstomho(par.soma_ihbm_gbar, self.somaarea)
            soma().ihsgcBasalMiddle.eh = par.soma_ihbm_eh
        else:
            raise ValueError(
                "Ihsgc modelType %s not recognized for species %s" % (celltype, species)
            )
        soma().leak.gbar = nstomho(par.soma_leak_gbar, self.somaarea)
        soma().leak.erev = par.soma_leak_erev

        self.status["species"] = species
        self.status["modelType"] = modelType
        self.check_temperature()
        if not silent:
            print("set cell as: ", species)
            print(" with Vm rest = %f" % self.vm0)

    def adjust_na_chans(self, soma, gbar=1000.0, debug=False):
        """
        adjust the sodium channel conductance
        :param soma: a soma object whose sodium channel complement will have it's
        conductances adjusted depending on the channel type
        :return nothing:
        """
        if self.status["ttx"]:
            gnabar = 0.0
        else:
            gnabar = nstomho(gbar, self.somaarea)
        nach = self.status["na"]
        if nach == "jsrna":
            soma().jsrna.gbar = gnabar
            soma.ena = self.e_na
            if debug:
                print("jsrna gbar: ", soma().jsrna.gbar)
        elif nach == "nav11":
            soma().nav11.gbar = gnabar * 0.5
            soma.ena = self.e_na
            soma().nav11.vsna = 4.3
            if debug:
                print("sgc using inva11")
            print("nav11 gbar: ", soma().nav11.gbar)
        elif nach in ["na", "nacn"]:
            soma().na.gbar = gnabar
            soma.ena = self.e_na
            if debug:
                print("na gbar: ", soma().na.gbar)
        else:
            raise ValueError("Sodium channel %s is not recognized for SGC cells", nach)

    def i_currents(self, V):
        """
        For the steady-state case, return the total current at voltage V
        Used to find the zero current point
        vrange brackets the interval
        Implemented here are the basic RM03 mechanisms
        This function should be replaced for specific cell types.
        """
        for part in self.all_sections.keys():
            for sec in self.all_sections[part]:
                sec.v = V

        h.t = 0.0
        h.celsius = self.status["temperature"]
        h.finitialize()
        self.ix = {}
        if "na" in self.mechanisms:
            # print dir(self.soma().na)
            self.ix["na"] = self.soma().na.gna * (V - self.soma().ena)
        if "jsrna" in self.mechanisms:
            # print dir(self.soma().na)
            self.ix["jsrna"] = self.soma().jsrna.gna * (V - self.soma().ena)
        if "klt" in self.mechanisms:
            self.ix["klt"] = self.soma().klt.gklt * (V - self.soma().ek)
        if "kht" in self.mechanisms:
            self.ix["kht"] = self.soma().kht.gkht * (V - self.soma().ek)
        if "ihsgcApical" in self.mechanisms:
            self.ix["ihsgcApical"] = self.soma().ihsgcApical.gh * (
                V - self.soma().ihsgcApical.eh
            )
        if "ihsgcBasalMiddle" in self.mechanisms:
            self.ix["ihsgcBasalMiddle"] = self.soma().ihsgcBasalMiddle.gh * (
                V - self.soma().ihsgcBasalMiddle.eh
            )
        if "leak" in self.mechanisms:
            self.ix["leak"] = self.soma().leak.gbar * (V - self.soma().leak.erev)
        #        print self.status['name'], self.status['type'], V, self.ix
        return np.sum([self.ix[i] for i in self.ix])
