"""
Layout:

        if __name__==__main__ handles cmd args, instantiates test, runs it, displays results

        run_trial(): defines a model and then runs the test using preset params in hoc and return the info to the class
        SGCInputTest:
            __init__ : defines many static variables
            run(): calls delivers information to the run_trial() function and the recieved information of a single run
                back and stores it as an exstensible list in the class
            show(): displays graphs depending on the graph options selected below it and displayed in a printout

"""
import argparse
import numpy as np
import pyqtgraph as pg
from neuron import h
from cnmodel.protocols import Protocol
from cnmodel import cells
from cnmodel.util import sound
from cnmodel.util import custom_init
import cnmodel.util.pynrnutilities as PU
from cnmodel import data

try:
    from tqdm import trange
except ImportError:
    raise ImportError("Please 'pip install tqdm' to allow for progress bar")

species = "rat"  # tables for other species do not yet exist


def run_trial(cell, info):
    """
    info is a dict
    """
    assert cell == "pyramidal"
    post_cell = cells.Pyramidal.create(species=species)
    inhib_cell = cells.Tuberculoventral.create()
    inhib_cell2 = cells.Tuberculoventral.create()
    # dstell = cells.DStellate.create()
    pre_cells = []
    synapses = []
    inhib_synapses = []
    for nsgc in range(48):
        # attach to pyramidal cell
        pre_cells.append(cells.DummySGC(cf=info["cf"], sr=info["sr"]))
        synapses.append(pre_cells[-1].connect(post_cell, type=info["synapse_type"]))
        pre_cells[-1].set_sound_stim(
            info["stim"], seed=info["seed"] + nsgc, simulator=info["simulator"]
        )
        synapses[
            -1
        ].terminal.relsite.Dep_Flag = False  # no depression in these simulations
    for nsgc in range(16):
        pre_cells.append(cells.DummySGC(cf=info["cf"], sr=info["sr"]))
        inhib_synapses.append(
            pre_cells[-1].connect(inhib_cell, type=info["synapse_type"])
        )
        inhib_synapses.append(
            pre_cells[-1].connect(inhib_cell2, type=info["synapse_type"])
        )
        pre_cells[-1].set_sound_stim(
            info["stim"], seed=info["seed"] + nsgc + 48, simulator=info["simulator"]
        )
        synapses[
            -1
        ].terminal.relsite.Dep_Flag = False  # no depression in these simulations
    # for nsgc in range(20):
    #     pre_cells.append(cells.DummySGC(cf=info['cf'], sr=info['sr']))
    #     inhib_synapses.append(pre_cells[-1].connect(dstell, type=info['synapse_type']))
    #     pre_cells[-1].set_sound_stim(info['stim'], seed=info['seed'] + nsgc + 16 + 48, simulator=info['simulator'])
    #     synapses[-1].terminal.relsite.Dep_Flag = False  # no depression in these simulations
    for _ in range(21):
        inhib_synapses.append(inhib_cell.connect(post_cell, type="simple"))
        inhib_synapses.append(inhib_cell2.connect(post_cell, type="simple"))
    # for _ in range(15):
    #     inhib_synapses.append(inhib_cell.connect(dstell, type='simple'))

    Vm = h.Vector()
    Vm.record(post_cell.soma(0.5)._ref_v)
    Vmtb = h.Vector()
    Vmtb.record(inhib_cell.soma(0.5)._ref_v)
    rtime = h.Vector()
    rtime.record(h._ref_t)
    h.tstop = 1e3 * info["run_duration"]  # duration of a run
    h.celsius = info["temp"]
    h.dt = info["dt"]
    post_cell.cell_initialize()
    info["init"]()
    h.t = 0.0
    h.run()
    # package data
    pre_cells_data = [x._spiketrain for x in pre_cells]
    Vm_list = np.array(Vm)
    Vmtb_list = np.array(Vmtb)
    time_list = np.array(rtime)
    #  clean up
    del (
        pre_cells,
        synapses,
        inhib_cell,
        inhib_synapses,
        Vm,
        Vmtb,
        post_cell,
        inhib_cell2,
        info,
    )

    return {
        "time": time_list,
        "vm": Vm_list,
        "pre_cells": pre_cells_data,
        "vmtb": Vmtb_list,
    }


class SGCTestPSTH(Protocol):
    """
    Tests a Single cell with input recieved from the SGC

    __init__: almost all parameters can be modified
    run(): simply loops over the run_trial() function and stores the results just
    show(): constructs the graphs using other functions

    """

    def __init__(
        self,
        temp=34.0,
        seed=2918,
        nrep=10,
        stimulus="tone",
        simulator="cochlea",
        n_sgc=12,
        debug=True,
        cell="pyramidal",
    ):
        """
        :param temp: (float) must be at 34 for default pyramidal cells
        :param dt: (float) determine hoc resolution
        :param seed: (int) contributes to randomization, needs to be changed to see different results (reduce this
                            number if you keep getting a timeout error
        :param nrep: (int) number of presentations !!must be changed in the __name__ function if not calling from cmd line!!
        :param stimulus: (str) must be 'tone'
        :param simulator: (str) currently using cochlea instead of matlab
        :param n_sgc:(int) This is the number of SGC fibers that connect to the post synaptic cell
        :param debug: (bool) controls most of the terminal printouts is on by default
        :param cell: (str) cell type !!must be changed in the __name__ function if not calling from cmd line!!
        """
        super().__init__()
        assert stimulus == "tone"
        assert cell in [
            "bushy",
            "tstellate",
            "octopus",
            "dstellate",
            "tuberculoventral",
            "pyramidal",
        ]
        self.debug = debug
        self.nrep = nrep
        self.stimulus = stimulus
        self.run_duration = 0.30  # in seconds
        self.pip_duration = 0.05  # in seconds
        self.pip_start = [0.1]  # in seconds
        self.Fs = 100e3  # in Hz
        self.f0 = 13000.0  # stimulus in Hz
        self.cf = 13000.0  # SGCs in Hz
        self.fMod = 100.0  # mod freq, Hz
        self.dMod = 0.0  # % mod depth, Hz
        self.dbspl = 40.0
        self.simulator = simulator
        self.sr = 2  # set SR group
        self.seed = seed
        self.temp = temp
        self.dt = 0.025
        self.cell = cell
        self.synapse_type = "multisite"

        if self.stimulus == "tone":
            self.stim = sound.TonePip(
                rate=self.Fs,
                duration=self.run_duration,
                f0=self.f0,
                dbspl=self.dbspl,
                ramp_duration=5e-3,
                pip_duration=self.pip_duration,
                pip_start=self.pip_start,
            )

        if not n_sgc:
            n_sgc = data.get(
                "convergence", species="mouse", post_type=self.cell, pre_type="sgc"
            )[0]
        self.n_sgc = int(np.round(n_sgc))
        # convert nS to uS for NEURON

        self.vms = [None for n in range(self.nrep)]
        self.vmtbs = [None for n in range(self.nrep)]
        self.synapses = [None for n in range(self.nrep)]
        self.pre_cells = [None for n in range(self.nrep)]
        self.time = [None for n in range(self.nrep)]
        # debug function reports a print out of various information about the run
        if self.debug:
            print("SGCInputTest Created")
            print()
            print("Test parameters")
            print("#" * 70)
            print(f"Running test of {cell} cell synapse with Simulated SGC fibers")
            print()
            print(f"Run Conditions:         Run Time: {self.run_duration}s,")
            print(f"                        Run Temp: {self.temp} ")
            print(f"                        Sgc Connections {n_sgc}")
            print(f"                        Number of Presentations: {nrep}")
            print()
            print(f"Stimulus Conditions:    Type: {stimulus}")
            print(f"                        Stim Duration: {self.pip_duration}s")
            print(f"                        Characteristic F: {self.cf}hz")
            print(f"                        Stim Start:{str(self.pip_start)}s")

    def run(self):
        super().run()
        info = {
            "n_sgc": self.n_sgc,
            "stim": self.stim,
            "simulator": self.simulator,
            "cf": self.cf,
            "sr": self.sr,
            "seed": self.seed,
            "run_duration": self.run_duration,
            "synapse_type": self.synapse_type,
            "temp": self.temp,
            "dt": self.dt,
            "init": custom_init,
        }
        for nr in trange(self.nrep):
            info["seed"] = self.seed + self.n_sgc + (nr * (48 + 16 + 20))
            res = run_trial(self.cell, info)
            # res contains: {'time': time, 'vm': list(Vm), 'pre_cells': pre_cells._spiketrain,'vmtb': list(Vmtb)}
            self.pre_cells[nr] = res["pre_cells"]
            self.time[nr] = res["time"]
            self.vms[nr] = res["vm"]
            self.vmtbs[nr] = res["vmtb"]

    def show(self):
        """
        Creates a single page graph that contains all of the graphs based on the graphical functions in the class

        """
        self.win = pg.GraphicsWindow()
        self.win.setBackground("w")
        p1 = self.stimulus_graph()
        p2 = self.an_spike_graph()
        p3 = self.pyram_spike_graph()
        p4 = self.voltage_graph()
        p5 = self.tb_cell_spike_graph()
        p6 = (
            self.an_psth_graph()
        )  # requires that an_spikes_graph() has been called before
        p7 = (
            self.cell_psth_graph()
        )  # requires that cell_spikes_graph() has been called before

        # links x axis
        p1.setXLink(p1)
        p2.setXLink(p1)
        p3.setXLink(p1)
        p4.setXLink(p1)
        p5.setXLink(p1)
        p6.setXLink(p1)
        p7.setXLink(p1)
        self.win.show()
        if self.debug:
            print("finished")

    ############# Graph options to be included in the show() method ###################
    def stimulus_graph(self):
        p1 = self.win.addPlot(
            title="Stimulus", row=0, col=0, labels={"bottom": "T (ms)", "left": "V"}
        )
        p1.plot(self.stim.time * 1000, self.stim.sound, pen=pg.mkPen("k", width=0.75))
        return p1

    def an_spike_graph(self):
        p2 = self.win.addPlot(
            title="AN spikes",
            row=1,
            col=0,
            labels={"bottom": "T (ms)", "left": "AN spikes (first trial)"},
        )
        self.all_xan = []
        for nr in range(self.nrep):
            xan = []
            yan = []
            for k in range(len(self.pre_cells[nr])):
                r = self.pre_cells[nr][k]
                xan.extend(r)
                self.all_xan.extend(r)
                yr = k + np.zeros_like(r) + 0.2
                yan.extend(yr)
        c = pg.PlotCurveItem()
        xp = np.repeat(np.array(xan), 2)
        yp = np.repeat(np.array(yan), 2)
        yp[1::2] = yp[::2] + 0.6
        c.setData(
            xp.flatten(),
            yp.flatten(),
            connect="pairs",
            width=1.0,
            pen=pg.mkPen("k", width=1.5),
        )
        p2.addItem(c)

        return p2

    def pyram_spike_graph(self):
        p3 = self.win.addPlot(
            title="Pyramidal Spikes",
            row=2,
            col=0,
            labels={"bottom": "T (ms)", "left": "Trial #"},
        )
        xcn = []
        ycn = []
        for k in range(self.nrep):
            bspk = PU.findspikes(self.time[k], self.vms[k], -35.0)
            xcn.extend(bspk)
            yr = k + np.zeros_like(bspk) + 0.2
            ycn.extend(yr)
        d = pg.PlotCurveItem()
        xp = np.repeat(np.array(xcn), 2)
        yp = np.repeat(np.array(ycn), 2)
        yp[1::2] = yp[::2] + 0.6
        d.setData(
            xp.flatten(), yp.flatten(), connect="pairs", pen=pg.mkPen("k", width=1.5)
        )
        self.xcn = xcn
        self.ycn = ycn
        p3.addItem(d)

        return p3

    def voltage_graph(self):
        p4 = self.win.addPlot(
            title="%s Vm" % self.cell,
            row=0,
            col=1,
            labels={"bottom": "T (ms)", "left": "Vm (mV)"},
        )
        if self.nrep > 3:
            display = 3
        else:
            display = self.nrep
        for nr in range(display):
            p4.plot(
                self.time[nr],
                self.vms[nr],
                pen=pg.mkPen(pg.intColor(nr, self.nrep), hues=self.nrep, width=1.0),
            )
        return p4

    def tb_cell_spike_graph(self):
        p5 = self.win.addPlot(
            title="Tuberculoventral Spikes",
            row=3,
            col=0,
            labels={"bottom": "T (ms)", "left": "Trial #"},
        )
        xtcn = []
        ytcn = []
        for k in range(self.nrep):
            bspk = PU.findspikes(self.time[k], self.vmtbs[k], -35.0)
            xtcn.extend(bspk)
            yr = k + np.zeros_like(bspk) + 0.2
            ytcn.extend(yr)
        d = pg.PlotCurveItem()
        xp = np.repeat(np.array(xtcn), 2)
        yp = np.repeat(np.array(ytcn), 2)
        yp[1::2] = yp[::2] + 0.6
        d.setData(
            xp.flatten(), yp.flatten(), connect="pairs", pen=pg.mkPen("k", width=1.5)
        )
        p5.addItem(d)

        return p5

    def an_psth_graph(self):
        p6 = self.win.addPlot(
            title="AN PSTH",
            row=1,
            col=1,
            labels={"bottom": "T (ms)", "left": "Sp/ms/trial"},
        )
        bins = np.arange(50, 200, 1)
        (hist, binedges) = np.histogram(self.all_xan, bins)
        curve6 = p6.plot(
            binedges,
            hist,
            stepMode=True,
            fillBrush=(0, 0, 0, 255),
            brush=pg.mkBrush("k"),
            fillLevel=0,
        )
        return p6

    def cell_psth_graph(self):
        p7 = self.win.addPlot(
            title="Pyramidal PSTH",
            row=2,
            col=1,
            labels={"bottom": "T (ms)", "left": "Sp/ms/trial"},
        )
        bins = np.arange(50, 200, 1)
        (hist, binedges) = np.histogram(self.xcn, bins)
        curve7 = p7.plot(
            binedges,
            hist,
            stepMode=True,
            fillBrush=(0, 0, 0, 255),
            brush=pg.mkBrush("k"),
            fillLevel=0,
        )
        return p7


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Compute AN only PSTH in postsynaptic cell"
    )
    parser.add_argument(
        "-n",
        "--nrep",
        type=int,
        dest="nrep",
        default=10,
        help="Set number of repetitions",
    )

    args = parser.parse_args()

    nrep = args.nrep
    prot = SGCTestPSTH(nrep=50)
    prot.run()
    prot.show()

    import sys

    if sys.flags.interactive == 0:
        pg.QtGui.QApplication.exec_()
