import freej
import os
import sys
import gtk
import gobject
import threading
import gtk.glade
import traceback
import gtksourceview2 as gtksourceview
from pythoncontext import PythonExecutionContext
gtk.gdk.threads_init ()
try:
    import numpy
    has_numpy = True
except:
    has_numpy = False
#freej.set_debug(3)

MENU = -1
CONTROLLER = 0
LAYER = 1
FILTER = 2
SCREEN = 3

class MyConsole(freej.ConsoleController):
    def __init__(self, statuslist, statusbar):
        self.statuslist = statuslist
        #self.scrolledwindow = scroll
        self.infoicon = self.statuslist.render_icon(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
        self.w_icon = self.statuslist.render_icon(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_MENU)
        self.e_icon = self.statuslist.render_icon(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU)
        self.d_icon = self.statuslist.render_icon(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
        self.u_icon = self.statuslist.render_icon(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_MENU)
        self.statusmodel = statuslist.get_model()
        self.statusbar = statusbar
        self.status_id = self.statusbar.get_context_id('freej')
        self.statusbar.push(self.status_id, "FreeJ started")
        freej.ConsoleController.__init__(self)

    def poll(self):
        return 0

    def dispatch(self):
        return 0

    def notice(self, msg):
        """
        task opened
        """
        self.advance(self.infoicon, msg)

    def error(self, msg):
        """
        fatal error
        """
        self.advance(self.e_icon, msg)
    def warning(self, msg):
        """
        warning
        """
        self.advance(self.w_icon, msg)
    def act(self, msg):
        """
        normal message
        """
        self.advance(self.d_icon, msg)
    def func(self, msg):
        """
        debug
        """
        self.advance(self.u_icon, msg)

    def advance(self, icon, msg):
        iter = self.statusmodel.append([icon, msg])
        self.statusbar.push(self.status_id, msg)
        path = self.statuslist.get_model().get_path(iter)
        self.statuslist.scroll_to_cell(path, None)

    def refresh(self, msg):
        print " X*", msg


# -----------------------------------------------------

class MyCall(freej.DumbCall):
   def __init__(self, func, *args):
       super(MyCall, self).__init__()
       self.func = func
       self.args = args

   def callback(self):
       self.func(*self.args)

class ContextMenu(gtk.Menu):
    """
    A context menu for immediate use.
    Takes a list of button names, and a parent widget, and it
    will show itself, and set callbacks on the parent, on the form:
     on_cm_<butname>
    It will also replace spaces in the button name, and set lower letters.
    """
    def __init__(self, parent, buttons, icons=[]):
        gtk.Menu.__init__(self)
        for i, butname in enumerate(buttons):
            if icons and icons[i]:
                item = gtk.ImageMenuItem(icons[i])
                #item.set_text(butname)
            else:
                item = gtk.MenuItem(butname)
            socketname = "on_cm_" + butname.replace(" ", "_").lower()
            item.connect("activate", getattr(parent, socketname))
            item.show()
            self.append(item)

    def popup(self, selected_obj, time):
        self._selected = selected_obj
        gtk.Menu.popup(self, None, None, None, 3, time, selected_obj)



class FreeJ(object):
    def __init__(self):
        self.cx = freej.Context()
        self.cx.init()
        self.cx.clear_all = True

        self.scr = freej.ScreenFactory.get_instance("Screen")
        self.scr.init( 400 , 300 , 32 )
        
        self.cx.add_screen( self.scr )
        if len(sys.argv) > 1:
            for lay in sys.argv[1:]:
                self.open_layer(lay)
        self.th = threading.Thread(target = self.cx.start , name = "freej")
        self.th.start();

        #cb = MyCall(self.finished)
        #v.add_eos_call(cb)

    def delete_layer(self, layer, layer_idx=-1):
        layer.rem()
 #       self.scr.rem_layer(layer)
 #        freej.delete_layer(layer)


    def open_layer(self, filename):
        v = self.cx.open(filename)
        if not v:
            return
        v.init(self.cx)
        v.open(filename)
        v.start()
        self.cx.add_layer( v )
        v.thisown = False
        v.set_blit("ADD")
        return v

    def finished(self):
        print "video looping!"

class JsBuffer(gtksourceview.Buffer):
    def __init__(self):
        gtksourceview.Buffer.__init__(self)
        self.lang_manager = gtksourceview.LanguageManager()
        self.set_syntax_highlight('js')

    def set_syntax_highlight(self, language):
        lang = self.lang_manager.get_language(language)
        self.set_language(lang)
        self.filename = None

    def reset(self):
        self.filename = None
        self.set_property('text', "")

    def save(self, filename=None):
        if not filename:
            filename = self.filename
        else:
            self.filename = filename
        if filename:
            text = self.get_property('text')
            f = open(filename, 'w')
            f.write(text)
            f.close()
            print " * saved as", filename

    def load_file(self, filename):
        f = open(filename, 'r')
        self.set_property('text', f.read())
        f.close()
        self.filename = filename

class App(FreeJ):
    def __init__(self):
        # load the xml interface
        self.fname = 'freej_mixer.glade'
        self.wTree = gtk.glade.XML(self.fname)
        # get some widgets
        self.window = self.wTree.get_widget("main")
        self.statusbar = self.wTree.get_widget("statusbar")
        self.label_mode = self.wTree.get_widget("label_mode")
        self.pythonmode = self.wTree.get_widget("menuitem_pythonmode")
        self.history_w = self.wTree.get_widget("window_history")
        self.history_t = self.wTree.get_widget("textview_history")
        self.vbox2 = self.wTree.get_widget("vbox2")
        # setup subsystems
        self.preview_box = None
        self.setup_editor()
        self.setup_file_dialogs()
        self.prepare_status()
        self.prepare_tree()
        self.console = MyConsole(self.statustree, self.statusbar)
        self.popup = ContextMenu(self, ["delete"])
        self.pyctx = PythonExecutionContext()
        freej.set_console(self.console)
        # init freej
        FreeJ.__init__(self)
        # setup python execution context main engine pointers
        self.pyctx['Context'] = self.cx
        self.pyctx['Screen'] = self.cx.screens.selected()
        # fill lists with engine contents
        self.fill_tree()
        self.fill_effects()
        # connect signals
        self.autoconnect_signals()
        self.history_w.connect('delete-event', self.hide_history)
        self.window.connect('destroy', gtk.main_quit)

    def autoconnect_signals(self):
        self.wTree.signal_autoconnect({"open_file": self.open_file,
                                       "open_script": self.open_script,
                                       "tree_button": self.on_treeview_button_press_event,
                                       "add_effect" : self.add_effect,
                                       "do_reset" : self.do_reset,
                                       "on_debug" : self.on_debug,
                                       "on_script_save" : self.on_script_save,
                                       "on_script_save_as" :
                                       self.on_script_save_as,
                                       "on_script_new" : self.on_script_new,
                                       "set_python_mode" : self.set_python_mode,
                                       "on_script_play" : self.on_script_play,
                                       "on_script_stop" : self.on_script_stop,
                                       "run_command": self.run_command,
                                       "on_item_activated": self.on_item_activated,
                                       "show_preview": self.show_preview,
                                       "show_history": self.show_history})

    def on_item_activated(self, treeview, path, view_column):
        """
        An item from the main tree view was selected.
        """
        obj = self.get_selected_object(treeview.get_model().get_iter(path))
        if not obj: # type of object not handled
            return
        self.console.act(self.get_description(obj))
        

    def invert_array(self, data):
        if not has_numpy:
            return data
        datac = numpy.frombuffer(data, numpy.uint8)
        data = numpy.copy(datac)
        data[0::4], data[2::4] = datac[2::4], datac[0::4] 
        return data

    def set_python_mode(self, toggle):
        if toggle.get_active():
            self.lang = "python"
            self.window.set_title('freej: python mode')
            self.label_mode.set_text('py>')
        else:
            self.lang = "js"
            self.window.set_title('freej: javascript mode')
            self.label_mode.set_text('js>')
        self.buffer.set_syntax_highlight(self.lang)

    def update_previews(self):
	self.scr = self.cx.scr.selected()
	if(not self.scr):
		return
        self.scr.lock()
        self.scr.layers.lock()
        data = self.scr.get_surface_buffer()
        w = self.scr.geo.w
        h = self.scr.geo.h
        #data = self.invert_array(data)
        self.images = {}
        data_array = []
        if len(self.scr.layers):
            # layer preview
            for layer in self.scr.layers:
                data = layer.get_surface_buffer()
                data = self.invert_array(data)
                w = layer.geo.w
                h = layer.geo.h
                data_array.append([data,w,h,layer.get_filename()])
        else:
            return
        if self.preview_box:
            self.vbox2.remove(self.preview_box)
            self.preview_box = None
        self.preview_box = gtk.Table(3,3)
        self.vbox2.pack_start(self.preview_box, expand=False)
        i = 0
        for data,w,h,name in data_array:
            self.pixbuf = gtk.gdk.pixbuf_new_from_data(data, gtk.gdk.COLORSPACE_RGB,
                                                   True, 8, w,
                                                   h, w*4)
            self.scr.layers.unlock()
            self.scr.unlock()
            self.pixbuf = self.pixbuf.scale_simple(200, 150, gtk.gdk.INTERP_BILINEAR)
            self.gtkpixmap = gtk.Image()
            self.gtkpixmap.set_tooltip_text(name)
            self.gtkpixmap.set_from_pixbuf(self.pixbuf)
            self.preview_box.attach(self.gtkpixmap, i/2, (i/2)+1, i%2, (i%2)+1)
            self.gtkpixmap.show()
            i += 1
        self.preview_box.show()

    def __timeout(self, widget):
        self.update_previews()
        self._timeout_id = gobject.idle_add(self.__timeout, self.window)

    def on_script_play(self, button):
#        self.on_script_save(button)
        if self.lang == 'js':
            if self.buffer.filename:
                self.cx.parse_js_cmd(self.buffer.get_property('text'))
#                self.cx.open_script(self.buffer.filename)
        elif self.lang == 'python':
            if self.buffer.filename:
                self.run_python_command(self.buffer.get_property('text'))
        self.fill_tree()

    def on_script_stop(self, button):
        self.do_reset(button)

    def on_script_save(self, button):
        if self.buffer.filename:
            self.buffer.save()
        else:
            self.on_script_save_as(button)

    def on_script_save_as(self, button):
        self.filew = gtk.FileChooserDialog("SaveAs",
                                          None,
                                          gtk.FILE_CHOOSER_ACTION_SAVE,
                                          (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                          gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        self.filew.set_default_response(gtk.RESPONSE_OK)
        response = self.filew.run()
        if response == gtk.RESPONSE_OK:
            filename = self.filew.get_filename()
            self.buffer.save(filename)
        self.filew.hide()

    def on_script_new(self, button):
        self.buffer.reset()


    def setup_editor(self):
        self.lang = "js"
        self.content_pane = self.wTree.get_widget("text_scroll")
        self.buffer = JsBuffer()
        self.editor = gtksourceview.View(self.buffer)
        self.editor.set_show_line_numbers(True)

        self.content_pane.add(self.editor)
        self.editor.show()
        accel_group = gtk.AccelGroup()
        self.window.add_accel_group(accel_group)

        self.editor.add_accelerator("paste-clipboard",
                                               accel_group,
                                               ord('v'),
                                               gtk.gdk.CONTROL_MASK,
                                               0)
        self.editor.add_accelerator("copy-clipboard",
                                               accel_group,
                                               ord('c'),
                                               gtk.gdk.CONTROL_MASK,
                                               0)
        self.editor.add_accelerator("cut-clipboard",
                                               accel_group,
                                               ord('x'),
                                               gtk.gdk.CONTROL_MASK,
                                               0)

    def on_debug(self, menuitem):
        if menuitem.get_active():
            freej.set_debug(3)
        else:
            freej.set_debug(1)

    def on_cm_delete(self, args):
        model, iter = self.main_tree.get_selection().get_selected()
        obj_type = model.get_value(iter, 2)
        if obj_type == LAYER:
            layer_idx = model.get_value(iter, 1)
            layer = self.scr.layers[layer_idx+1]
            self.delete_layer(layer, layer_idx)
        elif obj_type == FILTER:
            self.delete_effect()
        elif obj_type == CONTROLLER:
            c_idx = model.get_value(iter, 1)
            ctrl = self.cx.controllers[c_idx+1]
            self.cx.rem_controller(ctrl)
        self.fill_tree()

    def on_treeview_button_press_event(self, treeview, event):
        if event.button == 3:
            x = int(event.x)
            y = int(event.y)
            time = event.time
            pthinfo = treeview.get_path_at_pos(x, y)
            if pthinfo is not None:
                path, col, cellx, celly = pthinfo
                treeview.grab_focus()
                treeview.set_cursor( path, col, 0)
                self.popup.popup( None, time)
            return 1

    def do_reset(self, button):
        #del self.cx
        self.cx.reset()
#         print " * delete controllers"
#         for controller in list(self.cx.controllers):
#             self.cx.rem_controller(controller)
#         print " * delete layers"
#         for layer in list(self.scr.layers):
#              layer.rem()
# #            self.delete_layer(layer)
#         print " * clear layers"
#        print " * init context"
#        self.init_context()

        self.fill_tree()

    def add_effect(self, button):
        model, iter = self.main_tree.get_selection().get_selected()
        obj_type = model.get_value(iter, 2)
        if obj_type == LAYER:
            layer_idx = model.get_value(iter, 1)
            layer = self.scr.layers[layer_idx+1]
            idx = self.effects_cb.get_active()
            effect = self.cx.filters.pick(idx+1)
            self.eff = effect.apply(layer)
            self.fill_tree()

    def get_selected_object(self, iter):
        if not iter:
            return
        model = self.main_tree.get_model()
        idx = model.get_value(iter, 1)
        obj_type = model.get_value(iter, 2)
        if obj_type == LAYER:
            return self.scr.layers[idx+1]
        elif obj_type == FILTER:
            parent_iter = model.iter_parent(iter)
            lay_idx = model.get_value(parent_iter, 1)
            layer = self.scr.layers[lay_idx+1]
            filter = layer.filters[idx+1]
            return filter
        elif obj_type == CONTROLLER:
            return self.cx.controllers[idx+1]
        elif obj_type == SCREEN:
            return self.cx.screens[idx+1]

    def delete_effect(self, button=None):
        model, iter = self.main_tree.get_selection().get_selected()
        effect_name = model.get_value(iter, 0)
        parent_iter = model.iter_parent(iter)
        lay_idx = model.get_value(parent_iter, 1)
        layer = self.scr.layers[lay_idx+1]
        for idx, filter in enumerate(layer.filters):
            if filter.name == effect_name:
                layer.filters.rem(idx+1)
                filter.rem()
                self.fill_tree()
                return

    def prepare_status(self):
        self.statustree = self.wTree.get_widget("status_list")
        self.statusmodel = gtk.ListStore(gtk.gdk.Pixbuf, str)
        self.statustree.set_model(self.statusmodel)

        px = gtk.CellRendererPixbuf()
        text = gtk.CellRendererText()
        col = gtk.TreeViewColumn()
        col.pack_start(px, expand=False)
        col.pack_start(text, expand=True)
        col.add_attribute(px, "pixbuf", 0)
        col.add_attribute(text, "text", 1)
        self.statustree.append_column(col)

    def fill_effects(self):
        self.effects_cb = self.wTree.get_widget("combobox_effects")
        self.effect_model = gtk.ListStore(str)
        self.effects_cb.set_model(self.effect_model)
        self.cx.plugger.refresh(self.cx)
        for effect in self.cx.filters:
            self.effect_model.append([effect.name])
        cell = gtk.CellRendererText()
        self.effects_cb.pack_start(cell, True)
        self.effects_cb.add_attribute(cell, 'text', 0)

    def on_tree_tooltip(self, widget, x, y, keyboard_mode, tooltip):
        path = self.main_tree.get_path_at_pos(x, y)
        if not path:
            return
        iter = self.main_tree.get_model().get_iter(path[0])
        obj = self.get_selected_object(iter)
        if not obj:
            return
        tooltip.set_text(self.get_description(obj))
        return True

    def get_description(self, obj):
        if isinstance(obj, freej.Layer):
            return obj.name+"\n  active: "+str(obj.active)
        elif isinstance(obj, freej.ViewPort):
            return "a viewport has no name"
        else:
            return obj.name

    def prepare_tree(self):
        self.main_tree = self.wTree.get_widget("main_tree")
        #self.main_tree.set_tooltip_column(4)
        self.main_tree.set_property('has-tooltip', True)
        self.main_tree.connect('query-tooltip', self.on_tree_tooltip)
        self.main_model = gtk.TreeStore(str, int, int, gtk.gdk.Pixbuf, str)
        self.main_tree.set_model(self.main_model)
        self.folder_icon = self.main_tree.render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_MENU)
        self.layer_icon = self.main_tree.render_icon(gtk.STOCK_FILE, gtk.ICON_SIZE_MENU)
        self.screen_icon = self.main_tree.render_icon(gtk.STOCK_FILE, gtk.ICON_SIZE_MENU)
        self.effect_icon = self.main_tree.render_icon(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
        self.ctl_icon = self.main_tree.render_icon(gtk.STOCK_CONNECT, gtk.ICON_SIZE_MENU)
        cell = gtk.CellRendererText()
        px = gtk.CellRendererPixbuf()
        column = gtk.TreeViewColumn('name')
        column.pack_start(px, expand=False)
        column.pack_start(cell)
        self.main_tree.append_column(column)
        column.add_attribute(cell, "text", 0)
        column.add_attribute(px, "pixbuf", 3)

    def fill_tree(self):
        self.main_model.clear()
        tooltip = "tooltip"

        screens = self.main_model.append(None, ["Screens", 0, MENU,
                                                self.folder_icon, tooltip])
        layers = self.main_model.append(None, ["Layers", 0, MENU,
                                                self.folder_icon, tooltip])

        for c_idx, screen in enumerate(self.cx.screens):
            c_iter = self.main_model.append(screens, [ "screen",
                                                       c_idx, SCREEN,
                                                       self.screen_icon, tooltip])

            for l_idx, layer in enumerate(screen.layers):
                name = layer.get_filename()
                if not name:
                    name = layer.name
                lay_iter = self.main_model.append(layers, [name, l_idx, LAYER,
                                                               self.layer_icon, tooltip])
                for f_idx, filter in enumerate(layer.filters):
                     iter = self.main_model.append(lay_iter, [filter.name, f_idx,
                                                                  FILTER,
                                                                  self.effect_icon,
                                                                  tooltip])
            
        controllers = self.main_model.append(None, ["Controllers", 0, MENU,
                                                    self.folder_icon, tooltip])
        for c_idx, controller in enumerate(self.cx.controllers):
            c_iter = self.main_model.append(controllers, [controller.name,
                                                          c_idx, CONTROLLER,
                                                          self.ctl_icon, tooltip])

        self.main_tree.expand_all()

    def hide_history(self, window, event):
        self.wTree.get_widget("history_menu").set_active(False)
        window.hide()
        return 1

    def show_history(self, checkitem):
        if checkitem.get_active():
            self.history_w.show()
        else:
            self.history_w.hide()

    def show_preview(self, checkitem):
        if checkitem.get_active():
            self._timeout_id = gobject.idle_add(self.__timeout, self.window)
        else:
            gobject.source_remove(self._timeout_id)
            if self.preview_box:
                self.vbox2.remove(self.preview_box)
                self.preview_box = None

    def run_command(self, entry):
        text = entry.get_text()
        if  self.lang == 'js':
            self.run_js_command(text)
        else:
            self.run_python_command(text)
        entry.set_text("")
        iter = self.history_t.get_buffer().get_end_iter()
        self.history_t.get_buffer().insert(iter, text+"\n")

    def run_python_command(self, text):
        class OutputRedirector(object):
            def __init__(s):
                pass
            def write(s, msg):
                if msg.strip():
                    self.console.notice(msg)
        def printfunc(msg):
            self.console.error(msg)
        _stdout = sys.stdout
        sys.stdout = OutputRedirector()
        self.pyctx.RunUserCode(text, printfunc)
        sys.stdout = _stdout

    def run_js_command(self, text):
        self.cx.parse_js_cmd(text)

    def setup_file_dialogs(self):
        # video selection dialog
        self.filew = gtk.FileChooserDialog("Video selection",
                                          None,
                                          gtk.FILE_CHOOSER_ACTION_OPEN,
                                          (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                          gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        self.filew.set_default_response(gtk.RESPONSE_OK)
        # script selection dialog
        self.files = gtk.FileChooserDialog("Script selection",
                                          None,
                                          gtk.FILE_CHOOSER_ACTION_OPEN,
                                          (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                          gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        self.files.set_default_response(gtk.RESPONSE_OK)
        currdir = os.path.realpath(os.path.curdir)
        examplesdir = os.path.join(currdir, '..', '..', 'javascript', 'examples')
        if os.path.exists(examplesdir):
            self.files.set_current_folder(examplesdir)


    def open_file(self, *args):
        response = self.filew.run()
        if response == gtk.RESPONSE_OK:
            filename = self.filew.get_filename()
            self.open_layer(filename)
        self.filew.hide()
        self.fill_tree()

    def open_script(self, *args):
        response = self.files.run()
        if response == gtk.RESPONSE_OK:
            filename = self.files.get_filename()
            if filename.endswith('.py'):
                self.pythonmode.set_active(True)
            elif filename.endswith('.js'):
                self.pythonmode.set_active(False)
            self.buffer.load_file(filename)
        self.files.hide()
        self.fill_tree()

# -----------------------------------------------------


# -----------------------------------------------------

if __name__ == "__main__":
    app = App()
    gtk.main()
    print "exiting"
    app.cx.quit = False

