# -*- coding: UTF-8 -*-
#
#       tablez.py
#
#       Copyright 2009-2013 Giuseppe Penone <giuspen@gmail.com>
#
#       This program 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 3 of the License, or
#       (at your option) any later version.
#
#       This program 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 this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

import gtk, pango
import os, csv, codecs, cStringIO, copy
import cons, support


class TablesHandler:
    """Handler of the Tables"""

    def __init__(self, dad):
        """Lists Handler boot"""
        self.dad = dad

    def table_cut(self, *args):
        """Cut Table"""
        self.dad.object_set_selection(self.curr_table_anchor)
        self.dad.sourceview.emit("cut-clipboard")

    def table_copy(self, *args):
        """Copy Table"""
        self.dad.object_set_selection(self.curr_table_anchor)
        self.dad.sourceview.emit("copy-clipboard")

    def table_delete(self, *args):
        """Delete Table"""
        self.dad.object_set_selection(self.curr_table_anchor)
        self.dad.curr_buffer.delete_selection(True, self.dad.sourceview.get_editable())
        self.dad.sourceview.grab_focus()

    def on_key_press_tablehandledialog(self, widget, event):
        """Catches TableHandle Dialog key presses"""
        keyname = gtk.gdk.keyval_name(event.keyval)
        if keyname == "Return": self.dad.glade.tablehandledialog_button_ok.clicked()

    def table_export(self, action):
        """Table Export as CSV File"""
        filename = support.dialog_file_save_as(curr_folder=self.dad.pick_dir,
                                               filter_pattern="*.csv",
                                               filter_name=_("CSV File"),
                                               parent=self.dad.window)
        if filename == None: return
        if len(filename) < 4 or filename[-4:] != ".csv": filename += ".csv"
        self.dad.pick_dir = os.path.dirname(filename)
        table_dict = self.dad.state_machine.table_to_dict(self.curr_table_anchor)
        table_matrix = table_dict['matrix']
        table_matrix.insert(0, table_matrix.pop())
        file_descriptor = open(filename, "w")
        writer = UnicodeWriter(file_descriptor)
        writer.writerows(table_matrix)
        file_descriptor.close()

    def table_handle(self):
        """Insert Table"""
        iter_insert = self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert())
        self.dad.glade.tablehandledialog.set_title(_("Insert Table"))
        self.dad.glade.tablehandle_frame_table.show()
        self.dad.glade.tablehandle_frame_col.show()
        self.dad.glade.checkbutton_table_ins_from_file.set_active(False)
        self.dad.glade.checkbutton_table_ins_from_file.show()
        self.dad.glade.tablehandle_vbox_col.hide()
        response = self.dad.glade.tablehandledialog.run()
        self.dad.glade.tablehandledialog.hide()
        if response != 1: return
        if not self.dad.glade.checkbutton_table_ins_from_file.get_active():
            self.table_rows = int(self.dad.glade.spinbutton_table_rows.get_value())
            self.table_columns = int(self.dad.glade.spinbutton_table_columns.get_value())
            self.table_col_min = int(self.dad.glade.spinbutton_table_col_min.get_value())
            self.table_col_max = int(self.dad.glade.spinbutton_table_col_max.get_value())
            self.table_insert(iter_insert)
        else:
            filepath = support.dialog_file_select(filter_pattern="*.csv",
                                                  filter_name=_("CSV File"),
                                                  curr_folder=self.dad.pick_dir,
                                                  parent=self.dad.window)
            if filepath != None:
                self.dad.pick_dir = os.path.dirname(filepath)
                support.text_file_rm_emptylines(filepath)
                file_descriptor = open(filepath, 'r')
                reader = UnicodeReader(file_descriptor)
                table_matrix = []
                row = reader.next()
                while row:
                    table_matrix.append(row)
                    row = reader.next()
                file_descriptor.close()
                table_matrix.append(table_matrix.pop(0))
                self.table_insert(iter_insert, {'col_min': cons.TABLE_DEFAULT_COL_MIN,
                                                'col_max': cons.TABLE_DEFAULT_COL_MAX,
                                                'matrix': table_matrix})

    def table_insert(self, iter_insert, table=None, table_justification=None, text_buffer=None):
        """Insert a Table at the Given Iter"""
        if not text_buffer: text_buffer = self.dad.curr_buffer
        if table != None:
            self.table_columns = len(table['matrix'][0])
            self.table_rows = len(table['matrix']) - 1
            headers = table['matrix'][-1]
            table_col_min = table['col_min']
            table_col_max = table['col_max']
        else:
            headers = [_("click me")]*self.table_columns
            table_col_min = self.table_col_min
            table_col_max = self.table_col_max
        anchor = text_buffer.create_child_anchor(iter_insert)
        anchor.liststore = gtk.ListStore(*(str,)*self.table_columns)
        anchor.treeview = gtk.TreeView(anchor.liststore)
        for element in range(self.table_columns):
            label = gtk.Label('<b>' + headers[element] + '</b>')
            label.set_use_markup(True)
            label.set_tooltip_text(_("Click to Edit the Column Settings"))
            label.show()
            renderer_text = gtk.CellRendererText()
            renderer_text.set_property('editable', True)
            renderer_text.set_property('wrap-width', table_col_max)
            renderer_text.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
            renderer_text.set_property('font-desc', pango.FontDescription(self.dad.text_font))
            renderer_text.connect('edited', self.on_table_cell_edited, anchor.liststore, element)
            renderer_text.connect('editing-started', self.on_table_cell_editing_started, anchor.liststore, element)
            column = gtk.TreeViewColumn("", renderer_text, text=element)
            column.set_min_width(table_col_min)
            column.set_clickable(True)
            column.set_widget(label)
            column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            column.connect('clicked', self.table_column_clicked, anchor, element)
            anchor.treeview.append_column(column)
        anchor.headers = headers
        anchor.table_col_min = table_col_min
        anchor.table_col_max = table_col_max
        anchor.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
        anchor.treeview.connect('button-press-event', self.on_mouse_button_clicked_table, anchor)
        anchor.frame = gtk.Frame()
        anchor.frame.add(anchor.treeview)
        anchor.frame.set_shadow_type(gtk.SHADOW_NONE)
        anchor.eventbox = gtk.EventBox()
        anchor.eventbox.add(anchor.frame)
        self.dad.sourceview.add_child_at_anchor(anchor.eventbox, anchor)
        anchor.eventbox.show_all()
        for row in range(self.table_rows):
            row_iter = anchor.liststore.append([""]*self.table_columns)
            if table != None:
                for column in range(self.table_columns):
                    try: anchor.liststore[row_iter][column] = table['matrix'][row][column]
                    except: pass # there are cases when some rows have less columns
        if table_justification:
            text_iter = text_buffer.get_iter_at_child_anchor(anchor)
            self.dad.state_machine.apply_object_justification(text_iter, table_justification, text_buffer)
        elif self.dad.user_active:
            # if I apply a justification, the state is already updated
            self.dad.state_machine.update_state(self.dad.treestore[self.dad.curr_tree_iter][3])

    def table_edit_properties(self, *args):
        """Edit Table Properties"""
        self.table_col_min = self.curr_table_anchor.table_col_min
        self.table_col_max = self.curr_table_anchor.table_col_max
        self.dad.glade.spinbutton_table_col_min.set_value(self.curr_table_anchor.table_col_min)
        self.dad.glade.spinbutton_table_col_max.set_value(self.curr_table_anchor.table_col_max)
        self.dad.glade.tablehandledialog.set_title(_("Edit Table Properties"))
        self.dad.glade.tablehandle_frame_table.hide()
        self.dad.glade.tablehandle_frame_col.show()
        self.dad.glade.tablehandle_vbox_col.hide()
        self.dad.glade.checkbutton_table_ins_from_file.hide()
        self.dad.glade.tablehandle_frame_col.set_sensitive(True)
        response = self.dad.glade.tablehandledialog.run()
        self.dad.glade.tablehandledialog.hide()
        if response != 1: return
        self.table_col_min = int(self.dad.glade.spinbutton_table_col_min.get_value())
        table = self.dad.state_machine.table_to_dict(self.curr_table_anchor)
        self.table_col_max = int(self.dad.glade.spinbutton_table_col_max.get_value())
        table['col_min'] = self.table_col_min
        table['col_max'] = self.table_col_max
        iter_insert = self.dad.curr_buffer.get_iter_at_child_anchor(self.curr_table_anchor)
        table_justification = self.dad.state_machine.get_iter_alignment(iter_insert)
        iter_bound = iter_insert.copy()
        iter_bound.forward_char()
        self.dad.curr_buffer.delete(iter_insert, iter_bound)
        self.table_insert(iter_insert, table, table_justification)

    def on_key_press_table_cell(self, widget, event, path, model, col_num):
        """Catches Table Cell key presses"""
        keyname = gtk.gdk.keyval_name(event.keyval)
        if event.state & gtk.gdk.SHIFT_MASK:
            pass
        elif event.state & gtk.gdk.MOD1_MASK:
            pass
        elif event.state & gtk.gdk.CONTROL_MASK:
            if keyname == "period":
                cursor_pos = widget.get_position()
                widget.insert_text(cons.CHAR_NEWLINE, cursor_pos)
                widget.set_position(cursor_pos+1)
        else:
            if keyname in ["Return", "Up", "Down"]:
                if model[path][col_num] != widget.get_text():
                    model[path][col_num] = widget.get_text()
                    self.dad.update_window_save_needed("nbuf", True)
                if keyname == "Up":
                    if col_num > 0:
                        next_col_num = col_num - 1
                        next_path = path
                    else:
                        next_iter = None
                        next_path =  model.get_path(model.get_iter(path))
                        while not next_iter and next_path[0] > 0:
                            node_path_list = list(next_path)
                            node_path_list[0] -= 1
                            next_path = tuple(node_path_list)
                            next_iter = model.get_iter(next_path)
                        #next_iter = model.iter_next(model.get_iter(path))
                        if not next_iter: return
                        next_path = model.get_path(next_iter)
                        next_col_num = self.table_columns-1
                else:
                    if col_num < self.table_columns-1:
                        next_col_num = col_num + 1
                        next_path = path
                    else:
                        next_iter = model.iter_next(model.get_iter(path))
                        if not next_iter: return
                        next_path = model.get_path(next_iter)
                        next_col_num = 0
                #print "(path, col_num) = (%s, %s)" % (path, col_num)
                #print "(next_path, next_col_num) = (%s, %s)" % (next_path, next_col_num)
                next_column = self.curr_table_anchor.treeview.get_columns()[next_col_num]
                self.curr_table_anchor.treeview.set_cursor_on_cell(next_path,
                                                                   focus_column=next_column,
                                                                   focus_cell=next_column.get_cell_renderers()[0],
                                                                   start_editing=True)

    def on_table_cell_editing_started(self, cell, editable, path, model, col_num):
        """A Table Cell is going to be Edited"""
        if isinstance(editable, gtk.Entry):
            editable.connect('key_press_event', self.on_key_press_table_cell, path, model, col_num)

    def on_table_cell_edited(self, cell, path, new_text, model, col_num):
        """A Table Cell has been Edited"""
        if model[path][col_num] != new_text:
            model[path][col_num] = new_text
            self.dad.update_window_save_needed("nbuf", True)

    def table_column_clicked(self, column, anchor, col_num):
        """The Column Header was Clicked"""
        self.dad.glade.tablehandledialog.set_title(_("Table Column Action"))
        self.dad.glade.tablehandle_frame_table.hide()
        self.dad.glade.tablehandle_frame_col.hide()
        self.dad.glade.checkbutton_table_ins_from_file.hide()
        self.dad.glade.tablehandle_vbox_col.show()
        col_label = column.get_widget()
        self.dad.glade.table_column_rename_entry.set_text(col_label.get_text())
        self.dad.glade.table_column_rename_entry.grab_focus()
        self.dad.glade.table_column_new_entry.set_sensitive(self.dad.table_column_mode == 'add')
        self.dad.glade.table_column_rename_entry.set_sensitive(self.dad.table_column_mode == 'rename')
        response = self.dad.glade.tablehandledialog.run()
        self.dad.glade.tablehandledialog.hide()
        if response != 1: return
        table = self.dad.state_machine.table_to_dict(anchor)
        headers = table['matrix'].pop()
        if (self.dad.table_column_mode == 'right' and col_num == len(headers)-1)\
        or (self.dad.table_column_mode == 'left' and col_num == 0):
            return
        iter_insert = self.dad.curr_buffer.get_iter_at_child_anchor(anchor)
        table_justification = self.dad.state_machine.get_iter_alignment(iter_insert)
        iter_bound = iter_insert.copy()
        iter_bound.forward_char()
        self.dad.curr_buffer.delete(iter_insert, iter_bound)
        if self.dad.table_column_mode == 'rename':
            new_label = unicode(self.dad.glade.table_column_rename_entry.get_text(), cons.STR_UTF8, cons.STR_IGNORE)
            col_label.set_text("<b>" + new_label + "</b>")
            col_label.set_use_markup(True)
            headers[col_num] = new_label
        elif self.dad.table_column_mode == 'add':
            headers.insert(col_num + 1, unicode(self.dad.glade.table_column_new_entry.get_text(), cons.STR_UTF8, cons.STR_IGNORE))
            for row in table['matrix']: row.insert(col_num + 1, "")
        elif self.dad.table_column_mode == 'delete':
            headers.pop(col_num)
            for row in table['matrix']: row.pop(col_num)
        elif self.dad.table_column_mode == 'right':
            temp = headers.pop(col_num)
            headers.insert(col_num + 1, temp)
            for row in table['matrix']:
                temp = row.pop(col_num)
                row.insert(col_num + 1, temp)
        elif self.dad.table_column_mode == 'left':
            temp = headers.pop(col_num)
            headers.insert(col_num - 1, temp)
            for row in table['matrix']:
                temp = row.pop(col_num)
                row.insert(col_num - 1, temp)
        table['matrix'].append(headers)
        self.table_insert(iter_insert, table, table_justification)

    def table_row_action(self, action):
        """All Rows Actions"""
        treeviewselection = self.curr_table_anchor.treeview.get_selection()
        model, iter = treeviewselection.get_selected()
        if action == "delete": model.remove(iter)
        elif action == "add": model.insert_after(iter, [""]*len(self.curr_table_anchor.headers))
        elif action == "move_up":
            prev_iter = self.dad.get_tree_iter_prev_sibling(model, iter)
            if prev_iter == None: return
            model.swap(iter, prev_iter)
            self.curr_table_anchor.treeview.set_cursor(model.get_path(iter))
        elif action == "move_down":
            subseq_iter = model.iter_next(iter)
            if subseq_iter == None: return
            model.swap(iter, subseq_iter)
        elif action == "sort_desc":
            father_iter = model.iter_parent(iter)
            movements = False
            while self.dad.node_siblings_sort_iteration(model, father_iter, True, 0):
                movements = True
            if not movements: return
        elif action == "sort_asc":
            father_iter = model.iter_parent(iter)
            movements = False
            while self.dad.node_siblings_sort_iteration(model, father_iter, False, 0):
                movements = True
            if not movements: return
        elif action in ["cut", "copy"]:
            columns_num = len(self.curr_table_anchor.headers)
            table = {'matrix':[],
                     'col_min': self.curr_table_anchor.table_col_min,
                     'col_max': self.curr_table_anchor.table_col_max}
            row = []
            for column in range(columns_num): row.append(self.curr_table_anchor.liststore[iter][column])
            table['matrix'].append(row)
            table['matrix'].append(copy.deepcopy(self.curr_table_anchor.headers))
            self.dad.clipboard_handler.table_row_to_clipboard(table)
            if action == "cut": model.remove(iter)
            else: return
        elif action == "paste":
            if not self.dad.clipboard_handler.table_row_paste([model, iter]): return
        else: return
        self.dad.update_window_save_needed("nbuf", True)

    def table_row_add(self, *args):
        """Add a Table Row"""
        self.table_row_action("add")

    def table_row_cut(self, *args):
        """Cut a Table Row"""
        self.table_row_action("cut")

    def table_row_copy(self, *args):
        """Copy a Table Row"""
        self.table_row_action("copy")

    def table_row_paste(self, *args):
        """Paste a Table Row"""
        self.table_row_action("paste")

    def table_row_delete(self, *args):
        """Delete a Table Row"""
        self.table_row_action("delete")

    def table_row_up(self, *args):
        """Move the Selected Row Up"""
        self.table_row_action("move_up")

    def table_row_down(self, *args):
        """Move the Selected Row Down"""
        self.table_row_action("move_down")

    def table_rows_sort_descending(self, *args):
        """Sort all the Rows Descending"""
        self.table_row_action("sort_desc")

    def table_rows_sort_ascending(self, *args):
        """Sort all the Rows Ascending"""
        self.table_row_action("sort_asc")

    def on_mouse_button_clicked_table(self, widget, event, anchor):
        """Catches mouse buttons clicks"""
        self.curr_table_anchor = anchor
        self.dad.object_set_selection(self.curr_table_anchor)
        if event.button == 3:
            self.dad.ui.get_widget("/TableMenu").popup(None, None, None, event.button, event.time)


class UTF8Recoder:
    """
    Iterator that reads an encoded stream and reencodes the input to UTF-8
    """
    def __init__(self, f, encoding):
        self.reader = codecs.getreader(encoding)(f)

    def __iter__(self):
        return self

    def next(self):
        return self.reader.next().encode(cons.STR_UTF8)


class UnicodeReader:
    """
    A CSV reader which will iterate over lines in the CSV file "f",
    which is encoded in the given encoding.
    """
    def __init__(self, f, dialect=csv.excel, encoding=cons.STR_UTF8, **kwds):
        f = UTF8Recoder(f, encoding)
        self.reader = csv.reader(f, dialect=dialect, **kwds)

    def next(self):
        try:
            row = self.reader.next()
            return [unicode(s, cons.STR_UTF8, cons.STR_IGNORE) for s in row]
        except: return None

    def __iter__(self):
        return self


class UnicodeWriter:
    """
    A CSV writer which will write rows to CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding=cons.STR_UTF8, **kwds):
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, row):
        self.writer.writerow([s.encode(cons.STR_UTF8) for s in row])
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode(cons.STR_UTF8)
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)
