import gtk, gobject
import sys
import os
import inspect
import logger
import fluents

def _workflow_classes(modname):
    """Returns a list of all subclasses of Workflow in a given module"""
    workflow_classes = []

    __import__('workflows.%s' % modname)
    module = sys.modules['workflows.%s' % modname]

    d = module.__dict__
    for wf in d.values():
        try:
            if issubclass(wf, Workflow):
                workflow_classes.append(wf)
        except TypeError, e:
            pass
    return workflow_classes
        
def workflow_list():
    """Returns a list containing all new workflows"""
    retval = []

    # List all .py files that can contain workflow classes
    wf_path = sys.modules['workflows'].__path__
    wf_files = []

    for dir in wf_path:
        for fn in os.listdir(dir):
            if fn.endswith('.py') and ('#' not in fn):
                wf_files.append(fn[:-3])

    # Try to load each file and look for Workflow derived classes
    for fn in wf_files:
        try:
            for wf in _workflow_classes(fn):
                retval.append(wf)
        except Exception, e:
            logger.log('warning', 'Cannot load workflow: %s' % fn)
            logger.log('warning', e)
        
    return retval


class Workflow:
    """Defines a workflow that contains a set of analysis stages.

    A Workflow is a set of analysis stages for a certain type of analysis.
    Each stage contains some possible operations to do accomplish that 
    task.
    """

    name = "Workflow"
    ident = None
    description = "Workflow Description"

    def __init__(self, app):
        self.stages = []
        self.stages_by_id = {}
        self.app = app

    def get_data_file_name(self, filename):
        """Checks if a file with the given name exists in the data directory.
        Returns the file name if the file exists in the data directory, which
        is defined as datadir/workflowname. If the file does not exist, or the
        workflow does not have an identificator, this method returns None."""
        print os.path.join(self.app.options.datadir, self.ident, filename)
        if self.ident == None:
            return None
        fn = os.path.join(self.app.options.datadir, self.ident, filename)
        if os.path.isfile(fn):
            return fn
        return None

    def add_stage(self, stage):
        self.stages.append(stage)
        self.stages_by_id[stage.id] = stage

    def print_tree(self):
        print self.name
        for stage in self.stages:
            print '   %s' % stage.name
            for fun in stage.functions:
                print '        %s' % fun.name

    def add_project(self,project):
        if project == None:
            logger.log('notice','Proejct is empty')
        logger.log('notice','Project added in : %s' %self.name)
        self.project = project


class EmptyWorkflow(Workflow):
    name = 'Empty Workflow'

    def __init__(self, app):
        Workflow.__init__(self, None)
    

class Stage:
    """A stage is a part of the data analysis process.

    Each stage contains a set of functions that can be used to
    accomplish the task. A typical early stage is 'preprocessing', which
    can be done in several ways, each represented by a function.
    """

    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.functions = []
        self.functions_by_id = {}

    def add_function(self, fun):
        self.functions.append(fun)
        self.functions_by_id[fun.id] = fun


class Function:
    """A Function object encapsulates a function on a data set.

    Each Function instance encapsulates some function that can be applied
    to one or more types of data.
    """

    def __init__(self, id, name):
        self.id = id
        self.name = name

    # just return a Validation object
    def validate_input(input):
        return Validation(True,"Validation Not Implemented")

    def run(self):
        pass


class Validation:
    def __init__(self,result, reason):
        self.succeeded = result
        self.reason = reason


class WorkflowView (gtk.VBox):
   
    def __init__(self, wf):
        gtk.VBox.__init__(self)
        self.workflow = wf
        self.setup_workflow(wf)

    def setup_workflow(self, wf):
        # Add stage in the process
        for stage in wf.stages:
            exp = gtk.Expander(stage.name)
            btn_align = gtk.Alignment(xscale=0.9)
            btn_align.set_padding(0,4,20,0)
            btn_align.show()
            btn_box = gtk.VBox()
            btn_align.add(btn_box)
            btn_box.show()
            exp.add(btn_align)
            
            # Add functions in each stage 
            for fun in stage.functions:
                btn = gtk.Button(fun.name)
                btn.connect('clicked',
                            lambda button, f=fun : self.run_function(f))

                btn_box.add(btn)
                btn.show()
            
            exp.show()
            self.pack_start(exp, expand=False, fill=False)

    def remove_workflow(self):
        for c in self.get_children():
            c.hide()
            self.remove(c)

    def set_workflow(self, workflow):
        self.workflow = workflow
        self.remove_workflow()
        self.setup_workflow(workflow)

    def run_function(self, function):
        logger.log('debug', 'Starting function: %s' % function.name)
        project = self.workflow.app.project
        parent_data = project.current_data

        validation  = function.validate_input()

        if not validation.succeeded:
            logger.log('warning','Invalid Inputdata: ' + str(reason))
            return

        args, varargs, varkw, defaults = inspect.getargspec(function.run)

        # first argument is 'self' and second should be the selection
        # and we don't care about those...
        args.remove('self')
        if "selection" in args:
            pass_selection = True
            args.remove('selection')
        else:
            pass_selection = False

        if varargs and len(parent_data) < len(args):
            logger.log('warning', "Function requires minimum %d datasets selected." % len(args))
            return
        elif not varargs and args and len(args) != len(parent_data):
            # functions requiring datasets have to have the right number
            logger.log('warning', "Function requires %d datasets, but only %d selected." % (len(args), len(parent_data)))
            return

        if not args:
            # we allow functions requiring no data to be run even if a
            # dataset is is selected
            data = []
        else:
            data = parent_data
            
        if pass_selection:
            # if the function has a 'selection' argument, we pass in
            # the selection
            new_data = function.run(selection=project.get_selection(), *data)
        else:
            new_data = function.run(*data)
            
        if new_data != None:
            project.add_data(parent_data, new_data, function.name)
        
        logger.log('debug', 'Function ended: %s' % function.name)


class Options(dict):
    """Options base class.
    """
    def __init__(self, *args,**kw):
        dict.__init__(self, *args, **kw)
        self['out_plots'] = []
        self['out_data'] = []
	self['all_plots'] = []
	self['all_data'] = []

    def _copy_from_list(self, key_list):
        """Returns suboptions (dictionary) from a list of keys. 
        """
        d = {}
        for key in key_list:
            d[key] = self.get(key, None)
        return d


class OptionsDialog(gtk.Dialog):
    """The basic input/output dialog box.
    
    This defines the first page of the function options-gui.
    Any function that invokes a option-gui will inherit from this class.
    """
    def __init__(self, data, options, input_names=['X','Y']):
        gtk.Dialog.__init__(self, 'Input-Output dialog',
                            None,
                            gtk.DIALOG_DESTROY_WITH_PARENT,
                            (gtk.STOCK_OK, gtk.RESPONSE_OK,
                             gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))

        self._options = options
        self._data = data
        self._editable = True
        self.set_size_request(550,450)
        
        # create notebook
        self.nb = nb = gtk.Notebook()

        # 1. page: input/output
        
        #inputs
        input_frame = gtk.Frame("Input")
        hbox = gtk.HBox(True, 8)
        align = gtk.Alignment(1, 1, 1, 1)
        align.set_padding(8, 8, 8, 8)
        align.add(hbox)
        input_frame.add(align)
        for i, name in enumerate(input_names):
            frame = gtk.Frame(name)
            frame.set_label_align(0.5, 0.5)
            label = gtk.Label(data[i]._name + "\n" + str(data[i]._array.shape))
            frame.add(label)
            hbox.add(frame)
        
        #outputs
        output_frame = gtk.Frame("Output")
        output_hbox = gtk.HBox(True,4)
        output_align = gtk.Alignment(1, 1, 1, 1)
        output_align.set_padding(8, 8, 8, 8) #left padding:8
        output_align.add(output_hbox)
        output_frame.add(output_align)
        
        # plots
        plot_list = gtk.ListStore(str, 'gboolean', gtk.gdk.Pixbuf)
        plot_treeview = gtk.TreeView(plot_list)

        # Add plots
        plot_icon = fluents.icon_factory.get('line_plot')
        for plt, name, use in self._options['all_plots']:
            plot_list.append((name, use, plot_icon))

        # Renderer for icon
        plot_icon = fluents.icon_factory.get('line_plot')
        icon_renderer = gtk.CellRendererPixbuf()
        icon_renderer.set_property('pixbuf', plot_icon)

        # Renderer for active toggle.
        active_renderer = gtk.CellRendererToggle()
        active_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
        active_renderer.connect('toggled', toggled, plot_list)
        active_column = gtk.TreeViewColumn('Use', active_renderer, active=1)

        # Renderer for plot title.
        title_renderer = gtk.CellRendererText()
        title_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
        title_column = gtk.TreeViewColumn('Plot', title_renderer, text=0)
        title_column.pack_start(icon_renderer, expand=False)
        
        # Add columns to tree view.
        plot_treeview.append_column(active_column)
        plot_treeview.append_column(title_column)

        ## datasets
        dataset_list = gtk.ListStore(str, 'gboolean', gtk.gdk.Pixbuf)
        dataset_treeview = gtk.TreeView(dataset_list)

        # Add datasets
        data_icon = fluents.icon_factory.get('dataset')
        for dat, name, use in self._options['all_data']:
            dataset_list.append((name, use, data_icon))

        # Renderer for icon
        icon_renderer = gtk.CellRendererPixbuf()
        icon_renderer.set_property('pixbuf', data_icon)
        
        # Renderer for active toggle.
        active_renderer = gtk.CellRendererToggle()
        active_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
        active_renderer.connect('toggled', toggled, dataset_list)
        active_column = gtk.TreeViewColumn('Use', active_renderer, active=1)
        
        # Renderer for dataset title.
        title_renderer = gtk.CellRendererText()
        title_renderer.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
        title_column = gtk.TreeViewColumn('Dataset', title_renderer, text=0)
        title_column.pack_start(icon_renderer, expand=False)

        # Add columns to tree view.
        dataset_treeview.append_column(active_column)
        dataset_treeview.append_column(title_column)
        
        # add treeviews to output frame
        output_hbox.add(plot_treeview)
        output_hbox.add(dataset_treeview)
        
        # vbox for input/spacer/output
        vbox1 = gtk.VBox()
        vbox1.add(input_frame)
        vbox1.add(gtk.HSeparator())
        vbox1.add(output_frame)
        
        # add vbox to notebook
        nb.insert_page(vbox1, gtk.Label("Input/Output"), 0)
        self.vbox.add(nb)

        #keep ref to liststores
        self.dataset_list = dataset_list
        self.plot_list = plot_list

    def run(self):
        self.vbox.show_all()
        return gtk.Dialog.run(self)
        
    def set_options(self, options):
        self._options = options
        
    def update_options(self, options):
        self._options.update(options)

    def set_output(self):
        # get toggled output data
        out_data = [item[0] for name, mark, ic in self.dataset_list for item in self._options['all_data'] if mark==True and name==item[1]]
        # get toggled plots
        out_plots = [item[0] for name, mark, ic in self.plot_list for item in self._options['all_plots'] if mark==True and name==item[1]]
        # update options
        self._options['out_data'] = out_data
        self._options['out_plots'] = out_plots
        
    def set_editable(self, editable):
        self._editable = True
                
    def set_data(self, data):
        self._data = data

    def get_data(self):
        return self._data

    def get_options(self):
        return self._options

    def add_page_from_glade(self, glade_file, widget_name, page_title):
        """Adds a new page(s) to the existing notebook.
        The input widget (added as a page in notebook) is defined
        in the glade file.

        input:
        glade_file -- path to glade file
        widget_name -- name of widget from glade file
        """
        
        try:
            self.wTree = gtk.glade.XML(glade_file)
        except:
            logger.log('notice', 'Could not find glade file: %s' %glade_file)

        widget = self.wTree.get_widget(widget_name)
        win = widget.get_parent()
        win.hide()
        widget.unparent()
        self.nb.insert_page(widget, gtk.Label(page_title), -1)
        self.nb.set_current_page(0)


def toggled(renderer, path, store):
    it = store.get_iter(path)
    old_value = store.get_value(it, 1)
    store.set_value(it, 1, not old_value)


class WorkflowMenu (gtk.Menu):
    
    def __init__(self, workflow):
        gtk.Menu.__init__(self)
        self._workflow = workflow
        for stage in workflow.stages:
            self.append(self._create_stage_item(stage))

    def _create_stage_item(self, stage):
        stage_menu_item = gtk.MenuItem(stage.name)
        stage_menu_item.show()
        stage_menu = gtk.Menu()
        stage_menu_item.set_submenu(stage_menu)

        for fun in stage.functions:
            stage_menu.append(self._create_function_item(fun))
        return stage_menu_item

    def _create_function_item(self, function):
        menuitem = gtk.MenuItem(function.name)
        menuitem.show()
        return menuitem

