#! /usr/bin/env python
#
# neuronview.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST.  If not, see <http://www.gnu.org/licenses/>.

import pygtk
pygtk.require('2.0')
import gtk
import pango
import gobject

from matplotlib.figure import Figure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas 
import matplotlib.gridspec as gridspec
from numpy import arange, sin, pi

import os

import nest


default_neuron = "iaf_neuron"
default_stimulator = "dc_generator"

class Main() :

    def __init__(self):
        
        self._gladefile = "neuronview.glade"

        self._builder = gtk.Builder()
        self._builder.add_from_file(self._gladefile)
        self._builder.connect_signals(self)

        self._win = self._builder.get_object("mainwindow")
        self._win.resize(900, 700)

        box = self._builder.get_object("box5")
        self._stimulatordictview = DictView()
        self._builder.get_object("scrolledwindow2").add(self._stimulatordictview)

        box = self._builder.get_object("box4")
        self._neurondictview = DictView()
        self._builder.get_object("scrolledwindow3").add(self._neurondictview)

        self.populate_comboboxes()

        self._figure = Figure(figsize=(5,4), dpi=100)
        canvas = FigureCanvas(self._figure)
        canvas.set_size_request(200, 250)
        canvas.show()

        box = self._builder.get_object("box3")
        bg_style = box.get_style().bg[gtk.STATE_NORMAL]
        gtk_color = (bg_style.red_float, bg_style.green_float, bg_style.blue_float)
        self._figure.set_facecolor(gtk_color)
        box.pack_start(canvas)

        self._win.show()
        gtk.main()


    def update_figure(self, spikes, potentials):

        if nest.GetKernelStatus("time") != 0.0:

            self._figure.clear()

            num_figures = (len(spikes) != 0) + (len(potentials) != 0)
            fig_num = 1

            gs = gridspec.GridSpec(2, 1, height_ratios=[1, 4]) 

            ax0 = self._figure.add_subplot(gs[0])
            ax0.plot(spikes[0]["times"], [1]*len(spikes[0]["times"]), ".")
            ax0.set_yticks([])
            ax0.set_xticks([])

            ax1 = self._figure.add_subplot(gs[1])
            ax1.plot(potentials[0]["times"], potentials[0]["V_m"], "r-")
            ax1.set_ylabel("$V_m$ (mV)")
            ax1.set_xlabel("time (s)")

#            plt.tight_layout()

            self._figure.canvas.draw()


    def filter_statusdict(self, params):

        for key in ["vp", "thread", "type", "state", "model", "local", "frozen",
                    "origin", "instantiations", "elementsize", "capacity", "available",
                    "archiver_length", "recordables", "type_id", "t_spike", "tspike",
                    "tlast", "rmax", "receptor_types", "is_refractory", "refractory_input",
                    "offset", "ymod"]:
            if key in params.keys():
                params.pop(key)
        

    def populate_comboboxes(self):

        neuronmodels = self._builder.get_object("neuronmodels")
        neuronmodelsliststore = neuronmodels.get_model()

        stimulatormodels = self._builder.get_object("stimulatormodels")
        stimulatormodelsliststore = stimulatormodels.get_model()

        neuron_it = None
        stimulator_it = None
        
        models = nest.Models("nodes")
        models = [x for x in models if x not in ["correlation_detector", "sli_neuron",
                  "iaf_psc_alpha_norec", "parrot_neuron", "parrot_neuron_ps"]]

        for entry in models:

            try:
                entrytype = nest.GetDefaults(entry)["type"]
            except:
                entrytype = "unknown"

            if entrytype == "neuron":
                it = neuronmodelsliststore.append([entry])
                if entry == default_neuron: neuron_it = it
            elif entrytype == "stimulator":
                it = stimulatormodelsliststore.append([entry])
                if entry == default_stimulator: stimulator_it = it

        cell = gtk.CellRendererText()
        
        neuronmodels.pack_start(cell, True)
        neuronmodels.add_attribute(cell, 'text', 0)
        neuronmodels.set_active_iter(neuron_it)

        stimulatormodels.pack_start(cell, True)
        stimulatormodels.add_attribute(cell, 'text', 0)
        stimulatormodels.set_active_iter(stimulator_it)

        docviewcombo = self._builder.get_object("docviewcombo")
        docviewcomboliststore = docviewcombo.get_model()
        docviewcomboliststore.append(["Stimulating device"])
        it = docviewcomboliststore.append(["Neuron"])
        docviewcombo.pack_start(cell, True)
        docviewcombo.add_attribute(cell, 'text', 0)
        docviewcombo.set_active_iter(it)


    def get_help_text(self, name):

        nest.sli_run("statusdict /prgdocdir get")
        docdir = nest.sli_pop()

        helptext = "No documentation available"

        for subdir in ["cc", "sli"]:
            filename = os.path.join(docdir, "help", subdir, name + ".hlp")
            if os.path.isfile(filename):
                helptext = open(filename, 'r').read()

        return helptext
    

    def on_model_selected(self, widget):

        liststore = widget.get_model()
        model = liststore.get_value(widget.get_active_iter(), 0)

        statusdict = nest.GetDefaults(model)
        self.filter_statusdict(statusdict)

        if widget == self._builder.get_object("neuronmodels"):
            self._neurondictview.set_params(statusdict)

        if widget == self._builder.get_object("stimulatormodels"):
            self._stimulatordictview.set_params(statusdict)

        self.on_doc_selected(self._builder.get_object("docviewcombo"))


    def on_doc_selected(self, widget):

        liststore = widget.get_model()
        doc = liststore.get_value(widget.get_active_iter(), 0)

        docview = self._builder.get_object("docview")
        docbuffer = gtk.TextBuffer()
        
        if doc == "Neuron":
            combobox = self._builder.get_object("neuronmodels")

        if doc == "Stimulating device":
            combobox = self._builder.get_object("stimulatormodels")

        liststore = combobox.get_model()
        model = liststore.get_value(combobox.get_active_iter(), 0)

        docbuffer.set_text(self.get_help_text(model))
        docview.set_buffer(docbuffer)

        docview.modify_font(pango.FontDescription("monospace 10"))
            

    def on_simulate_clicked(self, widget):

        nest.ResetKernel()

        combobox = self._builder.get_object("stimulatormodels")
        liststore = combobox.get_model()
        stimulatormodel = liststore.get_value(combobox.get_active_iter(), 0)
        params = self._stimulatordictview.get_params()
        stimulator = nest.Create(stimulatormodel, params=params)

        combobox = self._builder.get_object("neuronmodels")
        liststore = combobox.get_model()
        neuronmodel = liststore.get_value(combobox.get_active_iter(), 0)
        neuron = nest.Create(neuronmodel, params=self._neurondictview.get_params())

        weight = self._builder.get_object("weight").get_value()
        delay = self._builder.get_object("delay").get_value()
        nest.Connect(stimulator, neuron, weight, delay)

        sd = nest.Create("spike_detector", params={"record_to": ["memory"]})
        nest.Connect(neuron, sd)

        vm = nest.Create("voltmeter", params={"record_to": ["memory"],
                                              "interval": 0.1})
        nest.Connect(vm, neuron)

        simtime = self._builder.get_object("simtime").get_value()
        nest.Simulate(simtime)

        self.update_figure(nest.GetStatus(sd, "events"), nest.GetStatus(vm, "events"))


    def on_delete_event(self, widget, event):

        self.on_quit(widget)
        return True


    def on_quit(self, project):

        self._builder.get_object("mainwindow").hide()
        gtk.main_quit()


class DictView(gtk.TreeView) :

    def __init__(self, params = None) :

        gtk.TreeView.__init__(self)
        if params:
            self.params = params
            self.repopulate()

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn("Name", renderer, text=1)
        self.append_column(column)

        renderer = gtk.CellRendererText()
        renderer.set_property("mode", gtk.CELL_RENDERER_MODE_EDITABLE)
        renderer.set_property("editable", True)
        column = gtk.TreeViewColumn("Value", renderer, text=2)
        self.append_column(column)

        self.set_size_request(200, 150)

        renderer.connect("edited", self.check_value)

        self.show()


    def repopulate(self) :

        model = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING)

        for key in sorted(self.params.keys()) :

            pos = model.insert_after(None, None)

            data = {"key"  : key, "type" : type(self.params[key])}
            model.set_value(pos, 0, data)
            model.set_value(pos, 1, str(key))
            model.set_value(pos, 2, str(self.params[key]))

        self.set_model(model)


    def check_value(self, widget, path, new_text) :

        model = self.get_model()
        data = model[path][0]

        try :
            
            typename = data["type"].__name__
            new_value = eval("%s('%s')" % (typename, new_text))
            if typename == "bool" and new_text.lower() in ["false", "0"] :
                new_value = False
            self.params[data["key"]] = new_value 
            model[path][2] = str(new_value)

        except ValueError :
            
            old_value = self.params[data["key"]]
            model[path][2] = str(old_value)


    def get_params(self) :

        return self.params


    def set_params(self, params) :

        self.params = params
        self.repopulate()


if __name__ == "__main__" :

    Main()