# -*- coding: utf-8 -*-
#
# Poio Tools for Linguists
#
# Copyright (C) 2001-2012 Poio Project
# Author: <NAME> <<EMAIL>>
# URL: <http://www.cidles.eu/ltll/poio>
# For license information, see LICENSE.TXT

from __future__ import unicode_literals
import os.path
import re
import pickle

from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QListWidgetItem, QPainter, QMessageBox

import poioapi.annotationtree
import poioapi.data

#from poio.ui.PoioGraidTextEdit import PoioGraidTextEdit
from poio.ui.Ui_MainWindowGRAID import Ui_MainWindow
from poio.ui.Ui_NewFileGraid import Ui_NewFileGraid
from poio.ui.FindReplaceDialog import FindReplaceDialog
from poio.ui.FindDialog import FindDialog
from poio.poioproject import PoioProject



class PoioGRAID(QtGui.QMainWindow):
    """The main window of the PoioGRAID application."""

    def __init__(self, *args):
        """
        The consctructor of the main application object, i.e. the main window.
        Calls a lot of other init methods.
        """
        QtGui.QMainWindow.__init__(self, *args)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.init_vars()
        self.init_connects()
        self.init_settings()

        self.init = 0

        self.project = PoioProject(os.getcwd())
        self.ui.listFiles.setModel(self.project)
        self.ui.projectManager.setShown(False)
        self.ui.projectBtn.setStyleSheet(""" QPushButton#projectBtn
                                            {
                                                border-style: outset;
                                                border-width: 1px;
                                                border-color: black;
                                                padding: 1px;
                                            }
                                            QPushButton#projectBtn:checked
                                            {
                                                background-color: lightblue;
                                                border-style: inset;
                                                border-width: 2px;
                                            }""")

        self.ui.textedit.append_title(
            self.tr("Please create or open a file..."))

        self._dialog_find_and_replace = FindReplaceDialog(self)
        self._dialog_find_and_replace.setModal(False)
        self._dialog_find_and_replace.set_text_edit(self.ui.textedit)

        self._dialog_find = FindDialog(self)
        self._dialog_find.setModal(False)
        self._dialog_find.set_text_edit(self.ui.textedit)

    def init_vars(self):
        """
        Initializes several attributes of the application, for example
        creates an empty annotation tree and a data structure type.
        """
        self.reset_data_structure_type(poioapi.data.GRAID)

        self.filepath = None
        self.projectfilepath = None
        self.title = ''

    def init_settings(self):
        """
        Load application settings from QSettings object.
        """
        QtCore.QCoreApplication.setOrganizationName(
            "Interdisciplinary Centre for Social and Language Documentation");
        QtCore.QCoreApplication.setOrganizationDomain("cidles.eu");
        QtCore.QCoreApplication.setApplicationName("PoioGRAID");
        #settings = QtCore.QSettings()

    def init_connects(self):
        """
        Initializes all signal/slots connections of the application.
        """

        # Files
        self.ui.actionOpenFile.triggered.connect(self.open_file)
        self.ui.actionSaveFile.triggered.connect(self.save_file)
        self.ui.actionSaveFileAs.triggered.connect(self.save_file_as)
        self.ui.actionOpen_Project.triggered.connect(self.open_project)
        self.ui.actionSave_Project.triggered.connect(self.save_project)
        self.ui.actionSave_Project_as.triggered.connect(self.save_project_as)
        self.ui.actionNewFile.triggered.connect(self.new_file)

        # Application stuff
        self.ui.actionQuit.triggered.connect(self.close)
        self.ui.actionAboutPoioGRAID.triggered.connect(self.about_dialog)

        # insert and delete tables and columns
        self.ui.actionInsertUtteranceAfter.triggered.connect(
            self.insert_utterance_after)
        self.ui.actionInsertUtteranceBefore.triggered.connect(
            self.insert_utterance_before)
        self.ui.actionDeleteUtterance.triggered.connect(
            self.delete_utterance)

        self.ui.actionInsertColumnBefore.triggered.connect(
            self.insert_column_before)
        self.ui.actionInsertColumnAfter.triggered.connect(
            self.insert_column_after)
        self.ui.actionDeleteColumn.triggered.connect(self.delete_column)

        # find and replace
        self.ui.actionFindAndReplace.triggered.connect(
            self.find_and_replace)
        self.ui.actionFind.triggered.connect(self.find)

        # Poio Project
        self.ui.listFiles.activated.connect(self.open_selected_file)
        self.connect(self.ui.projectBtn,SIGNAL("toggled()"),self.show_project)
        self.connect(self.ui.addfileBtn,SIGNAL("clicked()"),self.add_file)
        self.connect(self.ui.removefileBtn,SIGNAL("clicked()"),self.remove_file)
        self.connect(self.ui.saveprojectBtn,SIGNAL("clicked()"),self.save_project)
        self.connect(self.ui.openprojectBtn,SIGNAL("clicked()"),self.open_project)


    def about_dialog(self):
        """
        Display the About dialog.
        """
        about = QtGui.QMessageBox(self)
        about.setTextFormat(QtCore.Qt.RichText)
        about.setWindowTitle(self.tr("About PoioGRAID"))
        about.setText(self.tr("<b>PoioGRAID 0.2.1</b><br/>Poio GRAID Editor "
                              "by the <a href=\"http://www.cidles.eu\">"
                              "Interdisciplinary Centre for Social and "
                              "Language Documentation</a>.<br/><br/>All "
                              "rights reserved. See LICENSE file for details."
                              "<br/><br/>For more information visit the "
                              "website:<br/><a href=\"http://media.cidles.eu"
                              "/poio/\">http://media.cidles.eu/poio/"
                              "</a>"))
        about.exec_()

    def reset_data_structure_type(self, data_structure_type):
        self.annotation_tree = poioapi.annotationtree.AnnotationTree(
            data_structure_type)
        self.ui.textedit.structure_type_handler =\
            self.annotation_tree.structure_type_handler

    def show_project(self):
        """
        Show or hide the project manager.
        """
        self.ui.projectManager.setShown(self.ui.projectBtn.isChecked())

    def open_file(self):
        """
        Prompt the user for a file, add it to the project and open it.
        """
        filepaths = QtGui.QFileDialog.getOpenFileNames(self, self.tr("Open File"), "", self.tr("Pickle files (*.pickle);;All files (*.*)"))
        if len(filepaths) == 1:
            self.project.clear()
            self.project.addFilePaths(filepaths)
            self.open_file_at_path(filepaths[0])

    def open_file_at_path(self, filepath):
        """
        Load the data into the annotation tree and then update the text edit widget.

        ...

        Parameters
        ----------
        filepath: str
        """
        if filepath != '':
            self.annotation_tree.load_tree_from_pickle(filepath)
            self.ui.textedit.structure_type_handler =\
                self.annotation_tree.structure_type_handler

        #file = open(filepath, "rb")
            #data = pickle.load(file)
            #if data[0] == 'poio_pickle_v2':
            #    self.reset_data_structure_type(data[1])
            #    self.annotation_tree.tree = data[2]
            #else:
            #    file.seek(0)
            #    self.reset_data_structure_type(poioapi.data.GRAID)
            #    self.annotation_tree.tree = pickle.load(file)
            #file.close()
            self.update_textedit()
            self.filepath = filepath

    def open_project(self):
        """
        Prompt the user for a project, clear previous project and screen and open the project in the manager.
        """
        path = QtGui.QFileDialog.getOpenFileNames(
            self,
            self.tr("Open Project"),
            "",
            self.tr("Poio project file (*.poioprj);;All files (*.*)"))
        if len(path) > 0:
            self.project.clear()
            self.annotation_tree.tree = []
            self.update_textedit()
            self.projectfilepath = path[0]
            self.project.openproject(path[0])

    def add_file(self):
        """
        Add a file to the current project
        """
        filepaths = QtGui.QFileDialog.getOpenFileNames(self, self.tr("Add Files"), "", self.tr("Pickle files (*.pickle);;All files (*.*)"))
        self.project.addFilePaths(filepaths)
        self.open_file_at_path(filepaths[0])

    def remove_file(self):
        """
        Remove the selected file from the current project
        """
        countRemoved = 0
        for i in self.ui.listFiles.selectedIndexes():
            currentrow = i.row()-countRemoved
            project = self.project.poioFileAt(currentrow)
            self.project.removeFilePathAt(currentrow)

            if self.filepath == project.filepath:
                self.ui.textedit.clear()

            countRemoved += 1

    def open_selected_file(self):
        """
        Open the selected file in the manager to the TextEdit Screen
        """
        selected = self.ui.listFiles.selectedIndexes()
        count = 0
        for item in self.project.projectfiles:
            if item.filepath == 'tmp\\untitled.pickle':
                ret = self.project.saveuntitled()
                if ret == QMessageBox.Save:
                    path = self.save_file_as()
                    self.project.removeFilePathAt(count)
                    self.project.addFilePath(path)
                elif ret == QMessageBox.Discard:
                    self.project.removeFilePathAt(count)
                elif ret == QMessageBox.Cancel:
                    return
            count +=1
        if len(selected) == 1:
            project = self.project.poioFileAt(selected[0].row())
            self.open_file_at_path(project.filepath)

    def update_textedit(self):
        """
        Updates the text edit view with the data from the annotation tree.
        """
        self.ui.textedit.blockSignals(True)
        self.ui.textedit.clear()
        self.ui.textedit.append_title(self.title)

        for element in self.annotation_tree.elements():
            self.ui.textedit.append_element(element)

        self.ui.textedit.scrollToAnchor("title")
        self.ui.textedit.blockSignals(False)

    def delete_utterance(self):
        """
        Delete one utterance from the text edit widget and from the annotation
        tree.
        """
        deleted_id = self.ui.textedit.delete_current_element()
        if deleted_id:
            self.annotation_tree.remove_element(deleted_id)

    def insert_utterance_before(self):
        """
        Insert an utteranance *before* the currently edited utterance in the
        text view. Then adds the utterance to the annotation tree.
        """
        element = self.annotation_tree.empty_element()
        current_id = self.ui.textedit.insert_element(element)
        if current_id:
            self.annotation_tree.insert_element(element, current_id)

    def insert_utterance_after(self):
        """
        Insert an utteranance *after* the currently edited utterance in the
        text view. Then adds the utterance to the annotation tree.
        """
        element = self.annotation_tree.empty_element()
        current_id = self.ui.textedit.insert_element(element, True)
        if current_id:
            self.annotation_tree.insert_element(element, current_id, True)

    def delete_column(self):
        """
        Deletes the column that is currently edited in the text view. Also
        remove all annotation and sub-elements belonging to this column.
        Then delete the elements from the annotation tree.
        """
        self.ui.textedit.delete_column_at_cursor()

    def insert_column_before(self):
        """
        Inserts an empty column at the current cursor position *before* the
        currently edited element. Then insert the element into the annotation
        tree.
        """
        next_id = self.ui.textedit.insert_column_at_cursor(
            self.annotation_tree.next_annotation_id, False)
        self.annotation_tree.next_annotation_id = next_id

    def insert_column_after(self):
        """
        Inserts an empty column at the current cursor position *after* the
        currently edited element. Then insert the element into the annotation
        tree.
        """
        next_id = self.ui.textedit.insert_column_at_cursor(
            self.annotation_tree.next_annotation_id, True)
        self.annotation_tree.next_annotation_id = next_id

    def new_file(self):
        """
        Create a new file from a given input text. The user has to enter the
        text in an input dialog. There are two types of input text: plain text
        or tb style text (with markup like ``\sl`` at the beginning of lines).
        """
        dialog = QtGui.QDialog(self)
        ui = Ui_NewFileGraid()
        ui.setupUi(dialog)
        ret = dialog.exec_()
        if ret == QtGui.QDialog.Accepted:
            # get the data structure type that the user chose
            combo_data_structure_type = ui.comboDataStructureType.currentText()
            data_structure_type = poioapi.data.GRAID
            if combo_data_structure_type == "GRAID2 (Diana)":
                data_structure_type = poioapi.data.GRAIDDIANA

            self.reset_data_structure_type(data_structure_type)

            self.title = ""
            self.statusBar().showMessage(self.tr("Parsing text..."), 5)
            if ui.radioButtoTbStyleText.isChecked():
                self._parse_tb_style_text(
                    ui.textedit.document().toPlainText())
            else:
                self._parse_plain_text(
                    ui.textedit.document().toPlainText())
            self.statusBar().showMessage(self.tr("Parsing done."), 5)
            self.update_textedit()
            self.project.addFilePath('tmp\\untitled.pickle')

    def save_file(self):
        """
        Save the current data into a file. If no filename is specified yet
        then ask for the path and filename by opening a file dialog.
        """
        if not self.filepath:
            self.save_file_as()
        else:
            tree = self.ui.textedit.annotation_tree_from_document()
            self.annotation_tree.tree = tree
            file = open(self.filepath, "wb")
            pickle.dump(['poio_pickle_v2',
                         self.annotation_tree.data_structure_type,
                         self.annotation_tree.tree], file)
            file.close()
            self.statusBar().showMessage(self.tr("File saved."), 5)

    def save_file_as(self):
        """
        Open a file dialog and ask for path and filename for the file. Then
        call `PoioGRAID.save_file()`.
        """
        filepath = QtGui.QFileDialog.getSaveFileName(
            self,
            self.tr("Save File As"),
            "",
            self.tr("Pickle file (*.pickle);;All files (*.*)"))
        if filepath != '':
            if not filepath.endswith(".pickle"):
                filepath += ".pickle"
            self.filepath = filepath
            self.save_file()
            return filepath
        else:
            return

    def save_project(self):
        """
        Save the current project in the manager.
        Prompt for a path if the current project didn't exist yet
        """
        if not self.projectfilepath:
            self.save_project_as()
        else:
            self.save_file()
            self.project.saveprojectas(self.projectfilepath)

    def save_project_as(self):
        """
        Prompt for a path and save the current project to it
        """
        self.save_file()
        savepath = ""
        savepath = QtGui.QFileDialog.getSaveFileName(
            self,
            self.tr("Save Project As"),
            "",
            self.tr("Poio project file (*.poioprj);;All files (*.*)"))
        if savepath !="":
            self.project.saveprojectas(savepath)
            self.projectfilepath = savepath

    def find_and_replace(self):
        """
        Shows find and replace dialog
        """
        self._dialog_find_and_replace.show()

    def find(self):
        """
        Shows find dialog
        """
        self._dialog_find.show()

    # Private functions #######################################################

    def _parse_plain_text(self, text):
        """
        Parses plain text data into an annotation tree.

        ...

        Parameters
        ----------
        text : str
        """
        lines = text.split("\n")

        progress = QtGui.QProgressDialog(self.tr("Parsing text..."), self.tr("Abort"), 0, len(lines), self.parent())
        progress.setWindowModality(QtCore.Qt.WindowModal)

        for i, line in enumerate(lines):
            progress.setValue(i)

            line = line.strip()
            utterance = line
            clause_unit = re.sub("[.,;:]", "", line)
            words = clause_unit.split()

            il_elements = list()
            for w in words:
                if self.annotation_tree.data_structure_type == \
                        poioapi.data.GRAID:
                    il_elements.append([
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' :  w },
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' },
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' }])
                elif self.annotation_tree.data_structure_type ==\
                                        poioapi.data.GRAIDDIANA:
                    il_elements.append([
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' :  w },
                            # morphemes
                            [
                            [
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' },
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' }
                            ]
                            ],
                            # graid1, graid 3
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' },
                            { 'id' : self.annotation_tree.next_annotation_id,
                              'annotation' : '' }])

                elements = [ [
                { 'id' : self.annotation_tree.next_annotation_id,
                  'annotation' : clause_unit },
                il_elements,
                { 'id' : self.annotation_tree.next_annotation_id,
                  'annotation' : '' }] ]

            utterance = [ { 'id' : self.annotation_tree.next_annotation_id,
                            'annotation' : utterance },
                          elements,
                          { 'id' : self.annotation_tree.next_annotation_id,
                            'annotation' : '' },
                          { 'id' : self.annotation_tree.next_annotation_id,
                            'annotation' : '' } ]
            if self.annotation_tree.data_structure_type ==\
               poioapi.data.GRAIDDIANA:
                utterance.append(
                        { 'id' : self.annotation_tree.next_annotation_id,
                          'annotation' : '' })

            self.annotation_tree.append_element(utterance)
            if (progress.wasCanceled()):
                initCorpusReader()
                break

        progress.setValue(len(lines))

    def _parse_tb_style_text(self, text):
        """
        Parses tb style data into an annotation tree. In tb style data lines
        start with markup like ``\sl``.

        ...

        Parameters
        ----------
        text : str
        """
        block = list()

        lines = text.split("\n")
        line = lines.pop(0)
        title = []
        while not line.startswith("\\"):
            if line:
                title.append(line)
            line = lines.pop(0)

        self.title = " ".join(title)

        for line in lines:
            if line and line.startswith("\\id") and len(block):
                utterance = self._parse_element_from_tb_style(block)
                self.annotation_tree.append_element(utterance)
                block = list()
            elif line:
                if line.startswith("\\"):
                    block.append(line.strip())

        utterance = self._parse_element_from_tb_style(block)
        self.annotation_tree.append_element(utterance)

        #print self.annotation_tree.tree

    def _parse_element_from_tb_style(self, block):
        """
        Helper function for `PoioGRAID._parse_tb_style_text()`. Parse one
        paragraph of tb style data.
        ...

        Parameters
        ----------
        block : list
        """
        element_tb = dict()
        utterance = ""
        translation = ""
        comment = ""

        for line in block:
            line = re.sub(" +", " ", line)
            line = line.strip()
            line_elements = line.split(None, 1)
            if len(line_elements) < 2:
                type = line
                text = ""
            else:
                type = line_elements[0]
                text = line_elements[1]

            if type.startswith("\\"):
                if type[1:] == "sl":
                    #text = re.sub("\(\d+\) ?", "", text)
                    utterance = text
                    utterance = re.sub("\d?# ?", "", utterance)
                    utterance = re.sub(" +", " ", utterance)
                    utterance = utterance.strip()

                if type[1:] == "ft":
                    translation = text

                elif type[1:] == "com":
                    comment = text

                else:
                    #text = re.sub("^x ", "", text)
                    element_tb[type[1:]] = list()
                    last_start = 0
                    for m in re.finditer("(?:\d?#|$)", text):
                        element_tb[type[1:]].append(
                            text[last_start:m.start(0)])
                        last_start = m.end(0)
        elements = []
        for i, phrase in enumerate(element_tb['sl']):
            words = phrase.split()
            wfw = []
            try:
                wfw = element_tb['wfw'][i].split()
            except IndexError:
                pass
            except KeyError:
                pass

            graid1 = []
            try:
                graid1 = element_tb['gr_1'][i].split()
            except IndexError:
                pass
            except KeyError:
                pass

            graid2 = ''
            try:
                graid2 = element_tb['gr_2'][i]
            except IndexError:
                pass
            except KeyError:
                pass

            il_elements = []
            for i in range(max(len(words), len(wfw), len(graid1))):
                e1 = ''
                e2 = ''
                e3 = ''
                if i < len(words):
                    e1 = words[i]
                if i < len(wfw):
                    e2 = wfw[i]
                if i < len(graid1):
                    e3 = graid1[i]
                il_elements.append([
                    { 'id' : self.annotation_tree.next_annotation_id,
                      'annotation' :  e1 },
                    { 'id' : self.annotation_tree.next_annotation_id,
                      'annotation' : e2 },
                    { 'id' : self.annotation_tree.next_annotation_id,
                      'annotation' : e3 }])

            elements.append([
                { 'id' : self.annotation_tree.next_annotation_id,
                  'annotation' : phrase },
                il_elements,
                { 'id' : self.annotation_tree.next_annotation_id,
                  'annotation' : graid2 }])

        return [ { 'id' : self.annotation_tree.next_annotation_id,
                   'annotation' : utterance },
                  elements,
                  { 'id' : self.annotation_tree.next_annotation_id,
                    'annotation' : translation },
                  { 'id' : self.annotation_tree.next_annotation_id,
                    'annotation' : comment } ]

