# -*- coding: UTF-8 -*-
#
#       clipboard.py
#
#       Copyright 2009-2011 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 2 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, os, xml.dom.minidom, re, base64, mimetypes
import cons, machines, exports, imports


TARGET_CTD_PLAIN_TEXT = 'UTF8_STRING'
TARGET_CTD_RICH_TEXT = 'CTD_RICH'
TARGET_CTD_TABLE = 'CTD_TABLE'
TARGET_CTD_CODEBOX = 'CTD_CODEBOX'
TARGET_HTML = 'text/html'
TARGET_URI_LIST = 'text/uri-list'
TARGETS_PLAIN_TEXT = ("UTF8_STRING", "COMPOUND_TEXT", "STRING", "TEXT")
TARGETS_IMAGES = ('image/png', 'image/jpeg', 'image/bmp', 'image/tiff', 'image/x-MS-bmp', 'image/x-bmp')


class ClipboardHandler:
    """Handler of Clipboard"""

    def __init__(self, dad):
        """Clipboard Handler boot"""
        self.dad = dad
        self.clipboard = gtk.clipboard_get()
        self.force_plain_text = False

    def copy(self, sourceview):
        """Copy to Clipboard"""
        sourceview.stop_emission("copy-clipboard")
        if not self.dad.curr_buffer.get_has_selection(): return
        self.selection_to_clipboard(self.dad.curr_buffer, sourceview)

    def cut(self, sourceview):
        """Cut to Clipboard"""
        sourceview.stop_emission("cut-clipboard")
        if not self.dad.curr_buffer.get_has_selection(): return
        self.selection_to_clipboard(self.dad.curr_buffer, sourceview)
        if not self.dad.treestore[self.dad.curr_tree_iter][7]:
            self.dad.curr_buffer.delete_selection(True, sourceview.get_editable())
            self.dad.sourceview.grab_focus()

    def table_row_to_clipboard(self, table_dict):
        """Put the Selected Table Row to the Clipboard"""
        html_text = self.dad.html_handler.table_export_to_html(table_dict)
        self.clipboard.set_with_data([(t, 0, 0) for t in (TARGET_CTD_TABLE, TARGET_HTML)],
                                     self.get_func,
                                     self.clear_func,
                                     (table_dict, None, html_text))

    def table_row_paste(self, model_n_iter):
        """Paste Table Row from the Clipboard"""
        targets = self.clipboard.wait_for_targets()
        if not targets: return False
        if TARGET_CTD_TABLE in targets:
            self.clipboard.request_contents(TARGET_CTD_TABLE, self.to_table, model_n_iter)
            return True
        return False

    def selection_to_clipboard(self, text_buffer, sourceview):
        """Write the Selected Content to the Clipboard"""
        iter_sel_start, iter_sel_end = text_buffer.get_selection_bounds()
        num_chars = iter_sel_end.get_offset() - iter_sel_start.get_offset()
        if num_chars == 1:
            anchor = iter_sel_start.get_child_anchor()
            if anchor:
                anchor_dir = dir(anchor)
                if "pixbuf" in anchor_dir:
                    self.clipboard.set_with_data([(t, 0, 0) for t in TARGETS_IMAGES],
                                                 self.get_func,
                                                 self.clear_func,
                                                 anchor.pixbuf)
                    return
                elif "liststore" in anchor_dir:
                    table_dict = self.dad.state_machine.table_to_dict(anchor)
                    html_text = self.dad.html_handler.table_export_to_html(table_dict)
                    self.clipboard.set_with_data([(t, 0, 0) for t in (TARGET_CTD_TABLE, TARGET_HTML)],
                                                 self.get_func,
                                                 self.clear_func,
                                                 (table_dict, None, html_text))
                    return
                elif "sourcebuffer" in anchor_dir:
                    codebox_dict = self.dad.state_machine.codebox_to_dict(anchor, for_print=0)
                    codebox_dict_html = self.dad.state_machine.codebox_to_dict(anchor, for_print=2)
                    html_text = self.dad.html_handler.codebox_export_to_html(codebox_dict_html)
                    self.clipboard.set_with_data([(t, 0, 0) for t in (TARGET_CTD_CODEBOX, TARGET_HTML)],
                                                 self.get_func,
                                                 self.clear_func,
                                                 (codebox_dict, None, html_text))
                    return
        if not os.path.isdir(cons.TMP_FOLDER): os.mkdir(cons.TMP_FOLDER)
        html_text = self.dad.html_handler.selection_export_to_html(text_buffer, iter_sel_start, iter_sel_end, self.dad.syntax_highlighting)
        if self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            plain_text = text_buffer.get_text(iter_sel_start, iter_sel_end)
            rich_text = self.rich_text_get_from_text_buffer_selection(text_buffer, iter_sel_start, iter_sel_end)
            self.clipboard.set_with_data([(t, 0, 0) for t in (TARGET_CTD_PLAIN_TEXT, TARGET_CTD_RICH_TEXT, TARGET_HTML)],
                                         self.get_func,
                                         self.clear_func,
                                         (plain_text, rich_text, html_text))
        else:
            plain_text = text_buffer.get_text(iter_sel_start, iter_sel_end)
            self.clipboard.set_with_data([(t, 0, 0) for t in (TARGET_CTD_PLAIN_TEXT, TARGET_HTML)],
                                         self.get_func,
                                         self.clear_func,
                                         (plain_text, None, html_text))

    def get_func(self, clipboard, selectiondata, info, data):
        """Connected with clipboard.set_with_data"""
        target = selectiondata.get_target()
        if target == TARGET_CTD_PLAIN_TEXT: selectiondata.set('UTF8_STRING', 8, data[0])
        elif target == TARGET_CTD_RICH_TEXT: selectiondata.set('UTF8_STRING', 8, data[1])
        elif target == TARGET_HTML: selectiondata.set('UTF8_STRING', 8, data[2])
        elif target == TARGET_CTD_CODEBOX:
            dom = xml.dom.minidom.Document()
            self.dad.xml_handler.codebox_element_to_xml([0, data[0], "left"], dom)
            selectiondata.set('UTF8_STRING', 8, dom.toxml())
        elif target == TARGET_CTD_TABLE:
            dom = xml.dom.minidom.Document()
            self.dad.xml_handler.table_element_to_xml([0, data[0], "left"], dom)
            selectiondata.set('UTF8_STRING', 8, dom.toxml())
        elif target in TARGETS_IMAGES: selectiondata.set_pixbuf(data)

    def clear_func(self, clipboard, data):
        """Connected with clipboard.set_with_data"""
        # this is to free memory allocated when filling the clipboard
        del data

    def paste(self, sourceview):
        """Paste from Clipboard"""
        sourceview.stop_emission("paste-clipboard")
        if self.dad.treestore[self.dad.curr_tree_iter][7]: return
        targets = self.clipboard.wait_for_targets()
        if not targets: return
        self.dad.curr_buffer.delete_selection(True, sourceview.get_editable())
        if self.force_plain_text:
            self.force_plain_text = False
            for target in TARGETS_PLAIN_TEXT:
                if target in targets:
                    self.clipboard.request_contents(target, self.to_plain_text)
                    return
        #print targets
        if TARGET_CTD_RICH_TEXT in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            self.clipboard.request_contents(TARGET_CTD_RICH_TEXT, self.to_rich_text)
            return
        if TARGET_CTD_CODEBOX in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            self.clipboard.request_contents(TARGET_CTD_CODEBOX, self.to_codebox)
            return
        if TARGET_CTD_TABLE in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            self.clipboard.request_contents(TARGET_CTD_TABLE, self.to_table, None)
            return
        if TARGET_HTML in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            self.clipboard.request_contents(TARGET_HTML, self.to_html)
            return
        if TARGET_URI_LIST in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            self.clipboard.request_contents(TARGET_URI_LIST, self.to_uri_list)
            return
        for target in TARGETS_IMAGES:
            if target in targets and self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
                self.clipboard.request_contents(target, self.to_image)
                return
        for target in TARGETS_PLAIN_TEXT:
            if target in targets:
                self.clipboard.request_contents(target, self.to_plain_text)
                break

    def to_uri_list(self, clipboard, selectiondata, data):
        """From Clipboard to URI list"""
        selection_data = re.sub(cons.BAD_CHARS, "", selectiondata.data)
        uri_list = selection_data.split(cons.CHAR_NEWLINE)
        for element in uri_list:
            if len(element) > 7:
                iter_insert = self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert())
                if (element[0:4] == "http" or element[0:3] == "ftp"): property_value = "webs " + element
                elif element[0:7] == "file://":
                    file_path = element[7:].replace("%20", cons.CHAR_SPACE)
                    mimetype = mimetypes.guess_type(file_path)[0]
                    if len(mimetype) > 5 and mimetype[0:6] == "image/" and os.path.isfile(file_path):
                        self.dad.image_insert(iter_insert, gtk.gdk.pixbuf_new_from_file(file_path))
                        iter_insert = self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert())
                        self.dad.curr_buffer.insert(iter_insert, 3*cons.CHAR_SPACE)
                        continue
                    else: property_value = "file %s" % base64.b64encode(file_path)
                else: property_value = None
                start_offset = iter_insert.get_offset()
                self.dad.curr_buffer.insert(iter_insert, element + 3*cons.CHAR_SPACE)
                if property_value:
                    iter_sel_start = self.dad.curr_buffer.get_iter_at_offset(start_offset)
                    iter_sel_end = self.dad.curr_buffer.get_iter_at_offset(start_offset + len(element))
                    self.dad.curr_buffer.apply_tag_by_name(self.dad.apply_tag_exist_or_create("link", property_value),
                                                           iter_sel_start, iter_sel_end)

    def to_html(self, clipboard, selectiondata, data):
        """From Clipboard to HTML Text"""
        if ord(selectiondata.data[0]) == 0xff \
        and ord(selectiondata.data[1]) in [0xfe, 0xff]:
            selection_data = selectiondata.data.decode("utf-16", "ignore")
        else:
            match = re.match('.*\x00\w\x00\w\x00\w.*', selectiondata.data) # \w is alphanumeric char
            if match: selection_data = selectiondata.data.decode("utf-16", "ignore")
            else: selection_data = selectiondata.data
        #print "###########################"
        #print selectiondata.data
        #print "###########################"
        #print selectiondata.data.decode("utf-16", "ignore")
        #print "###########################"
        #for char in selection_data: print ord(char)
        selection_data = re.sub(cons.BAD_CHARS, "", selection_data)
        html_import = imports.HTMLFromClipboardHandler(self.dad)
        xml_string = html_import.get_clipboard_selection_xml(selection_data)
        self.from_xml_string_to_buffer(xml_string)

    def to_plain_text(self, clipboard, selectiondata, data):
        """From Clipboard to Plain Text"""
        plain_text = selectiondata.get_text()
        iter_insert = self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert())
        start_offset = iter_insert.get_offset()
        self.dad.curr_buffer.insert(iter_insert, plain_text)
        if self.dad.syntax_highlighting == cons.CUSTOM_COLORS_ID:
            for offsets in imports.get_web_links_offsets_from_plain_text(plain_text):
                iter_sel_start = self.dad.curr_buffer.get_iter_at_offset(start_offset + offsets[0])
                iter_sel_end = self.dad.curr_buffer.get_iter_at_offset(start_offset + offsets[1])
                link_url = plain_text[offsets[0]:offsets[1]]
                if link_url[0:3] not in ["htt", "ftp"]: link_url = "http://" + link_url
                property_value = "webs " + link_url
                self.dad.curr_buffer.apply_tag_by_name(self.dad.apply_tag_exist_or_create("link", property_value),
                                                       iter_sel_start, iter_sel_end)

    def to_rich_text(self, clipboard, selectiondata, data):
        """From Clipboard to Rich Text"""
        self.from_xml_string_to_buffer(selectiondata.get_text())

    def from_xml_string_to_buffer(self, xml_string):
        """From XML String to Text Buffer"""
        dom = xml.dom.minidom.parseString(xml_string)
        dom_node = dom.firstChild
        if dom_node.nodeName != "root":
            print "rich text from clipboard error"
            return
        child_dom_iter = dom_node.firstChild
        while child_dom_iter != None:
            if child_dom_iter.nodeName == "slot":
                nephew_dom_iter = child_dom_iter.firstChild
                while nephew_dom_iter != None:
                    if nephew_dom_iter.nodeName == "rich_text":
                        self.dom_node_to_rich_text(nephew_dom_iter)
                    elif nephew_dom_iter.nodeName == "encoded_png":
                        self.dom_node_to_image(nephew_dom_iter)
                    elif nephew_dom_iter.nodeName == "table":
                        self.dom_node_to_table(nephew_dom_iter, None)
                    elif nephew_dom_iter.nodeName == "codebox":
                        self.dom_node_to_codebox(nephew_dom_iter)
                    nephew_dom_iter = nephew_dom_iter.nextSibling
            child_dom_iter = child_dom_iter.nextSibling

    def dom_node_to_rich_text(self, dom_node):
        """From dom_node to Rich Text"""
        if dom_node.firstChild: text = dom_node.firstChild.data
        else: text = ""
        tag_names = []
        for tag_property in cons.TAG_PROPERTIES:
            if dom_node.hasAttribute(tag_property):
                property_value = dom_node.attributes[tag_property].value
                if property_value: tag_names.append(self.dad.apply_tag_exist_or_create(tag_property, property_value))
        tags_num = len(tag_names)
        if tags_num == 0:
            self.dad.curr_buffer.insert(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()),
                                        text)
        else:
            self.dad.curr_buffer.insert_with_tags_by_name(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()),
                                                          text,
                                                          *tag_names)

    def to_image(self, clipboard, selectiondata, data):
        """From Clipboard to Image"""
        pixbuf = selectiondata.get_pixbuf()
        self.dad.image_insert(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()), pixbuf)

    def dom_node_to_image(self, dom_node):
        """From dom_node to Image"""
        if dom_node.hasAttribute("justification"): justification = dom_node.attributes["justification"].value
        else: justification = "left"
        if dom_node.hasAttribute("anchor"):
            pixbuf = gtk.gdk.pixbuf_new_from_file(cons.ANCHOR_CHAR)
            pixbuf.anchor = dom_node.attributes["anchor"].value
        else: pixbuf = machines.get_pixbuf_from_encoded_buffer(dom_node.firstChild.data)
        self.dad.image_insert(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()),
                              pixbuf,
                              justification)

    def to_codebox(self, clipboard, selectiondata, data):
        """From Clipboard to CodeBox"""
        dom = xml.dom.minidom.parseString(selectiondata.get_text())
        dom_node = dom.firstChild
        if dom_node.nodeName != "codebox":
            print "codebox from clipboard error"
            return
        self.dom_node_to_codebox(dom_node)

    def dom_node_to_codebox(self, dom_node):
        """From dom_node to CodeBox"""
        justification = dom_node.attributes["justification"].value if dom_node.hasAttribute("justification") else "left"
        codebox_dict = {
           'frame_width': int(dom_node.attributes['frame_width'].value),
           'frame_height': int(dom_node.attributes['frame_height'].value),
           'width_in_pixels': dom_node.hasAttribute("width_in_pixels") and dom_node.attributes['width_in_pixels'].value == "True",
           'syntax_highlighting': dom_node.attributes['syntax_highlighting'].value,
           'highlight_brackets': dom_node.hasAttribute("highlight_brackets") and dom_node.attributes['highlight_brackets'].value == "True",
           'show_line_numbers': dom_node.hasAttribute("show_line_numbers") and dom_node.attributes['show_line_numbers'].value == "True",
           'fill_text': dom_node.firstChild.data if dom_node.firstChild else ""
        }
        self.dad.codeboxes_handler.codebox_insert(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()),
                                                  codebox_dict,
                                                  justification)

    def to_table(self, clipboard, selectiondata, table_model_n_iter):
        """From Clipboard to Table"""
        dom = xml.dom.minidom.parseString(selectiondata.get_text())
        dom_node = dom.firstChild
        if dom_node.nodeName != "table":
            print "table from clipboard error"
            return
        self.dom_node_to_table(dom_node, table_model_n_iter)

    def dom_node_to_table(self, dom_node, table_model_n_iter):
        """From dom_node to Table"""
        if dom_node.hasAttribute("justification"): justification = dom_node.attributes["justification"].value
        else: justification = "left"
        table = {'matrix': [],
                 'col_min': int(dom_node.attributes['col_min'].value),
                 'col_max': int(dom_node.attributes["col_max"].value)}
        child_dom_iter = dom_node.firstChild
        while child_dom_iter != None:
            if child_dom_iter.nodeName == "row":
                table['matrix'].append([])
                nephew_dom_iter = child_dom_iter.firstChild
                while nephew_dom_iter != None:
                    if nephew_dom_iter.nodeName == "cell":
                        if nephew_dom_iter.firstChild != None: table['matrix'][-1].append(nephew_dom_iter.firstChild.data)
                        else: table['matrix'][-1].append("")
                    nephew_dom_iter = nephew_dom_iter.nextSibling
            child_dom_iter = child_dom_iter.nextSibling
        if not table_model_n_iter:
            # insert new table
            self.dad.tables_handler.table_insert(self.dad.curr_buffer.get_iter_at_mark(self.dad.curr_buffer.get_insert()),
                                                 table,
                                                 justification)
        else:
            # paste into existing table
            (model, iter) = table_model_n_iter
            num_columns = model.get_n_columns()
            for i, table_row in enumerate(table['matrix']):
                if i < len(table['matrix']) - 1:
                    if len(table_row) > num_columns: table_row = table_row[:num_columns]
                    elif len(table_row) < num_columns: table_row = table_row + [""]*(num_columns - len(table_row))
                    iter = model.insert_after(iter, table_row)

    def rich_text_get_from_text_buffer_selection(self, text_buffer, iter_sel_start, iter_sel_end, change_case="n"):
        """Given text_buffer and selection, returns the rich text xml"""
        pixbuf_table_codebox_vector = self.dad.state_machine.get_embedded_pixbufs_tables_codeboxes(text_buffer,
                                                                                                   for_print=0,
                                                                                                   sel_range=(iter_sel_start.get_offset(), iter_sel_end.get_offset()))
        # pixbuf_table_codebox_vector is [ [ "pixbuf"/"table"/"codebox", [offset, pixbuf, alignment] ],... ]
        dom = xml.dom.minidom.Document()
        root = dom.createElement("root")
        dom.appendChild(root)
        obj_pos = 0
        start_offset = iter_sel_start.get_offset()
        for end_offset_element in pixbuf_table_codebox_vector:
            end_offset = end_offset_element[1][0]
            if obj_pos < len(pixbuf_table_codebox_vector): obj_element = pixbuf_table_codebox_vector[obj_pos]
            else: obj_element = None
            self.rich_text_process_slot(dom, root, start_offset, end_offset, text_buffer, obj_element, change_case)
            obj_pos += 1
            start_offset = end_offset
        self.rich_text_process_slot(dom, root, start_offset, iter_sel_end.get_offset(), text_buffer, None, change_case)
        return dom.toxml()

    def rich_text_process_slot(self, dom, root, start_offset, end_offset, text_buffer, obj_element, change_case="n"):
        """Process a Single Pango Slot"""
        dom_iter = dom.createElement("slot")
        root.appendChild(dom_iter)
        start_iter = text_buffer.get_iter_at_offset(start_offset)
        #print "process slot (%s->%s)" % (start_offset, end_offset)
        # begin operations
        self.curr_attributes = {}
        for tag_property in cons.TAG_PROPERTIES: self.curr_attributes[tag_property] = ""
        curr_iter = start_iter.copy()
        self.dad.xml_handler.rich_text_attributes_update(curr_iter, self.curr_attributes)
        tag_found = curr_iter.forward_to_tag_toggle(None)
        while tag_found:
            if curr_iter.get_offset() > end_offset: curr_iter = text_buffer.get_iter_at_offset(end_offset)
            self.dad.xml_handler.rich_text_serialize(dom_iter, start_iter, curr_iter, self.curr_attributes, change_case)
            offset_old = curr_iter.get_offset()
            if offset_old >= end_offset: break
            else:
                self.dad.xml_handler.rich_text_attributes_update(curr_iter, self.curr_attributes)
                start_iter.set_offset(offset_old)
                tag_found = curr_iter.forward_to_tag_toggle(None)
                if curr_iter.get_offset() == offset_old: break
        else:
            if curr_iter.get_offset() > end_offset: curr_iter = text_buffer.get_iter_at_offset(end_offset)
            self.dad.xml_handler.rich_text_serialize(dom_iter, start_iter, curr_iter, self.curr_attributes, change_case)
        if obj_element:
            if obj_element[0] == "pixbuf": self.dad.xml_handler.pixbuf_element_to_xml(obj_element[1], dom_iter, dom)
            elif obj_element[0] == "table": self.dad.xml_handler.table_element_to_xml(obj_element[1], dom_iter)
            elif obj_element[0] == "codebox": self.dad.xml_handler.codebox_element_to_xml(obj_element[1], dom_iter)
