""" 
 @file
 @brief This file loads the title editor dialog (i.e SVG creator)
 @author Jonathan Thomas <jonathan@openshot.org>
 @author Andy Finch <andy@openshot.org>
 
 @section LICENSE
 
 Copyright (c) 2008-2016 OpenShot Studios, LLC
 (http://www.openshotstudios.com). This file is part of
 OpenShot Video Editor (http://www.openshot.org), an open-source project
 dedicated to delivering high quality video editing and animation solutions
 to the world.
 
 OpenShot Video Editor 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.
 
 OpenShot Video Editor 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 OpenShot Library.  If not, see <http://www.gnu.org/licenses/>.
 """

import sys
import os
import fnmatch
import shutil
import functools
import subprocess
from xml.dom import minidom

from PyQt5.QtCore import *
from PyQt5.QtGui import QIcon, QStandardItemModel, QStandardItem, QFont
from PyQt5.QtWidgets import *
from PyQt5 import uic, QtSvg, QtGui
from PyQt5.QtWebKitWidgets import QWebView
import openshot

from classes import info, ui_util, settings, qt_types, updates
from classes.logger import log
from classes.app import get_app
from classes.query import File
from classes.metrics import *

try:
    import json
except ImportError:
    import simplejson as json


class TitleEditor(QDialog):
    """ Title Editor Dialog """

    # Path to ui file
    ui_path = os.path.join(info.PATH, 'windows', 'ui', 'title-editor.ui')

    def __init__(self):

        # Create dialog class
        QDialog.__init__(self)

        self.app = get_app()
        self.project = self.app.project

        # Get translation object
        _ = self.app._tr

        # Load UI from designer
        ui_util.load_ui(self, self.ui_path)

        # Init UI
        ui_util.init_ui(self)

        # Track metrics
        track_metric_screen("title-screen")

        # Initialize variables
        self.template_name = ""
        imp = minidom.getDOMImplementation()
        self.xmldoc = imp.createDocument(None, "any", None)

        self.bg_color_code = ""
        self.font_color_code = "#ffffff"

        self.bg_style_string = ""
        self.title_style_string = ""
        self.subtitle_style_string = ""

        self.font_weight = 'normal'
        self.font_style = 'normal'

        self.new_title_text = ""
        self.sub_title_text = ""
        self.subTitle = False

        self.display_name = ""
        self.font_family = "Bitstream Vera Sans"
        self.tspan_node = None

        # Hide all textboxes
        self.hide_textboxes()

        # Remove temp file (if found)
        temp_svg_path = os.path.join(info.TITLE_PATH, "temp.svg")
        if os.path.exists(temp_svg_path):
            os.remove(temp_svg_path)

        # load the template files
        self.cmbTemplate.addItem(_("<Select a template>"))

        # Add user-defined titles (if any)
        for file in sorted(os.listdir(info.TITLE_PATH)):
            # pretty up the filename for display purposes
            if fnmatch.fnmatch(file, '*.svg'):
                (fileName, fileExtension) = os.path.splitext(file)
                self.cmbTemplate.addItem(fileName.replace("_", " "), os.path.join(info.TITLE_PATH, file))

        # Add built-in titles
        self.template_dir = os.path.join(info.PATH, 'titles')
        for file in sorted(os.listdir(self.template_dir)):
            # pretty up the filename for display purposes
            if fnmatch.fnmatch(file, '*.svg'):
                (fileName, fileExtension) = os.path.splitext(file)
                self.cmbTemplate.addItem(fileName.replace("_", " "), os.path.join(self.template_dir, file))

        # set event handlers
        self.cmbTemplate.activated.connect(functools.partial(self.cmbTemplate_activated))
        self.btnFontColor.clicked.connect(functools.partial(self.btnFontColor_clicked))
        self.btnBackgroundColor.clicked.connect(functools.partial(self.btnBackgroundColor_clicked))
        self.btnFont.clicked.connect(functools.partial(self.btnFont_clicked))
        self.btnAdvanced.clicked.connect(functools.partial(self.btnAdvanced_clicked))
        self.txtLine1.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine2.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine3.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine4.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine5.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine6.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine7.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine8.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine9.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine10.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine11.textChanged.connect(functools.partial(self.txtLine_changed))
        self.txtLine12.textChanged.connect(functools.partial(self.txtLine_changed))

    def txtLine_changed(self):

        # Update text values in the SVG
        text_list = []
        text_list.append(self.txtLine1.toPlainText())
        text_list.append(self.txtLine2.toPlainText())
        text_list.append(self.txtLine3.toPlainText())
        text_list.append(self.txtLine4.toPlainText())
        text_list.append(self.txtLine5.toPlainText())
        text_list.append(self.txtLine6.toPlainText())
        text_list.append(self.txtLine7.toPlainText())
        text_list.append(self.txtLine8.toPlainText())
        text_list.append(self.txtLine9.toPlainText())
        text_list.append(self.txtLine10.toPlainText())
        text_list.append(self.txtLine11.toPlainText())
        text_list.append(self.txtLine12.toPlainText())
        for i in range(0, self.text_fields):
            if len(self.tspan_node[i].childNodes) > 0 and i <= (len(text_list) - 1):
                new_text_node = self.xmldoc.createTextNode(text_list[i])
                old_text_node = self.tspan_node[i].childNodes[0]
                self.tspan_node[i].removeChild(old_text_node)
                # add new text node
                self.tspan_node[i].appendChild(new_text_node)

        # Something changed, so update temp SVG
        self.writeToFile(self.xmldoc)

        # Display SVG again
        self.display_svg()

    def hide_textboxes(self):
        """ Hide all text inputs """
        self.txtLine1.setVisible(False)
        self.lblLine1.setVisible(False)
        self.txtLine2.setVisible(False)
        self.lblLine2.setVisible(False)
        self.txtLine3.setVisible(False)
        self.lblLine3.setVisible(False)
        self.txtLine4.setVisible(False)
        self.lblLine4.setVisible(False)
        self.txtLine5.setVisible(False)
        self.lblLine5.setVisible(False)
        self.txtLine6.setVisible(False)
        self.lblLine6.setVisible(False)
        self.txtLine7.setVisible(False)
        self.lblLine7.setVisible(False)
        self.txtLine8.setVisible(False)
        self.lblLine8.setVisible(False)
        self.txtLine9.setVisible(False)
        self.lblLine9.setVisible(False)
        self.txtLine10.setVisible(False)
        self.lblLine10.setVisible(False)
        self.txtLine11.setVisible(False)
        self.lblLine11.setVisible(False)
        self.txtLine12.setVisible(False)
        self.lblLine12.setVisible(False)


    def show_textboxes(self, num_fields):
        """ Only show a certain number of text inputs """

        if num_fields >= 1:
            self.txtLine1.setEnabled(True)
            self.txtLine1.setVisible(True)
            self.lblLine1.setVisible(True)
        if num_fields >= 2:
            self.txtLine2.setEnabled(True)
            self.txtLine2.setVisible(True)
            self.lblLine2.setVisible(True)
        if num_fields >= 3:
            self.txtLine3.setEnabled(True)
            self.txtLine3.setVisible(True)
            self.lblLine3.setVisible(True)
        if num_fields >= 4:
            self.txtLine4.setEnabled(True)
            self.txtLine4.setVisible(True)
            self.lblLine4.setVisible(True)
        if num_fields >= 5:
            self.txtLine5.setEnabled(True)
            self.txtLine5.setVisible(True)
            self.lblLine5.setVisible(True)
        if num_fields >= 6:
            self.txtLine6.setEnabled(True)
            self.txtLine6.setVisible(True)
            self.lblLine6.setVisible(True)
        if num_fields >= 7:
            self.txtLine7.setEnabled(True)
            self.txtLine7.setVisible(True)
            self.lblLine7.setVisible(True)
        if num_fields >= 8:
            self.txtLine8.setEnabled(True)
            self.txtLine8.setVisible(True)
            self.lblLine8.setVisible(True)
        if num_fields >= 9:
            self.txtLine9.setEnabled(True)
            self.txtLine9.setVisible(True)
            self.lblLine9.setVisible(True)
        if num_fields >= 10:
            self.txtLine10.setEnabled(True)
            self.txtLine10.setVisible(True)
            self.lblLine10.setVisible(True)
        if num_fields >= 11:
            self.txtLine11.setEnabled(True)
            self.txtLine11.setVisible(True)
            self.lblLine11.setVisible(True)
        if num_fields >= 12:
            self.txtLine12.setEnabled(True)
            self.txtLine12.setVisible(True)
            self.lblLine12.setVisible(True)


    def display_svg(self):
        scene = QGraphicsScene(self)
        view = self.graphicsView
        svg = QtGui.QPixmap(self.filename)
        svg_scaled = svg.scaled(svg.width() / 4, svg.height() / 4, Qt.KeepAspectRatio)
        scene.addPixmap(svg_scaled)
        view.setScene(scene)
        view.show()

    def cmbTemplate_activated(self):
        # reconstruct the filename from the modified display name
        if self.cmbTemplate.currentIndex() > 0:
            # ignore the 'select a template entry'
            template = self.cmbTemplate.currentText()
            template_path = self.cmbTemplate.itemData(self.cmbTemplate.currentIndex())

            # Create temp version of SVG title
            self.filename = self.create_temp_title(template_path)

            # Load temp title
            self.load_svg_template()

            # Display tmp title
            self.display_svg()

    def create_temp_title(self, template_path):

        # Set temp file path
        self.filename = os.path.join(info.TITLE_PATH, "temp.svg")

        # Copy template to temp file
        shutil.copy(template_path, self.filename)

        # return temp path
        return self.filename

    def load_svg_template(self):
        """ Load an SVG title and init all textboxes and controls """

        # parse the svg object
        self.xmldoc = minidom.parse(self.filename)
        # get the text elements
        self.tspan_node = self.xmldoc.getElementsByTagName('tspan')
        self.text_fields = len(self.tspan_node)

        # Hide all text inputs
        self.hide_textboxes()
        # Show the correct number of text inputs
        self.show_textboxes(self.text_fields)

        # Get text nodes and rect nodes
        self.text_node = self.xmldoc.getElementsByTagName('text')
        self.rect_node = self.xmldoc.getElementsByTagName('rect')

        # Get text values
        title_text = []
        for i in range(0, self.text_fields):
            if len(self.tspan_node[i].childNodes) > 0:
                title_text.append(self.tspan_node[i].childNodes[0].data)

        # Set textbox values
        num_fields = len(title_text)
        if num_fields >= 1:
            self.txtLine1.setText("")
            self.txtLine1.setText(title_text[0])
        if num_fields >= 2:
            self.txtLine2.setText("")
            self.txtLine2.setText(title_text[1])
        if num_fields >= 3:
            self.txtLine3.setText("")
            self.txtLine3.setText(title_text[2])
        if num_fields >= 4:
            self.txtLine4.setText("")
            self.txtLine4.setText(title_text[3])
        if num_fields >= 5:
            self.txtLine5.setText("")
            self.txtLine5.setText(title_text[4])
        if num_fields >= 6:
            self.txtLine6.setText("")
            self.txtLine6.setText(title_text[5])
        if num_fields >= 7:
            self.txtLine7.setText("")
            self.txtLine7.setText(title_text[6])
        if num_fields >= 8:
            self.txtLine8.setText("")
            self.txtLine8.setText(title_text[7])
        if num_fields >= 9:
            self.txtLine9.setText("")
            self.txtLine9.setText(title_text[8])
        if num_fields >= 10:
            self.txtLine10.setText("")
            self.txtLine10.setText(title_text[9])
        if num_fields >= 11:
            self.txtLine11.setText("")
            self.txtLine11.setText(title_text[10])
        if num_fields >= 12:
            self.txtLine12.setText("")
            self.txtLine12.setText(title_text[11])

        # Update color buttons
        self.update_font_color_button()
        self.update_background_color_button()

        # Enable / Disable buttons based on # of text nodes
        if num_fields >= 1:
            self.btnFont.setEnabled(True)
            self.btnFontColor.setEnabled(True)
            self.btnBackgroundColor.setEnabled(True)
            self.btnAdvanced.setEnabled(True)
        else:
            self.btnFont.setEnabled(False)
            self.btnFontColor.setEnabled(False)

    def writeToFile(self, xmldoc):
        '''writes a new svg file containing the user edited data'''

        if not self.filename.endswith("svg"):
            self.filename = self.filename + ".svg"
        try:
            file = open(self.filename.encode('UTF-8'), "wb")  # wb needed for windows support
            file.write(bytes(xmldoc.toxml(), 'UTF-8'))
            file.close()
        except IOError as inst:
            log.error("Error writing SVG title")

    def btnFontColor_clicked(self):
        app = get_app()
        _ = app._tr

        # Get color from user
        col = QColorDialog.getColor(Qt.white, self, _("Select a Color"),
                                    QColorDialog.DontUseNativeDialog | QColorDialog.ShowAlphaChannel)

        # Update SVG colors
        if col.isValid():
            self.btnFontColor.setStyleSheet("background-color: %s" % col.name())
            self.set_font_color_elements(col.name(), col.alphaF())

        # Something changed, so update temp SVG
        self.writeToFile(self.xmldoc)

        # Display SVG again
        self.display_svg()

    def btnBackgroundColor_clicked(self):
        app = get_app()
        _ = app._tr

        # Get color from user
        col = QColorDialog.getColor(Qt.white, self, _("Select a Color"),
                                    QColorDialog.DontUseNativeDialog | QColorDialog.ShowAlphaChannel)

        # Update SVG colors
        if col.isValid():
            self.btnBackgroundColor.setStyleSheet("background-color: %s" % col.name())
            self.set_bg_style(col.name(), col.alphaF())

        # Something changed, so update temp SVG
        self.writeToFile(self.xmldoc)

        # Display SVG again
        self.display_svg()

    def btnFont_clicked(self):
        app = get_app()
        _ = app._tr

        # Get font from user
        font, ok = QFontDialog.getFont(QFont(), caption=_("Change Font"))

        # Update SVG font
        if ok:
            fontinfo = QtGui.QFontInfo(font)
            self.font_family = fontinfo.family()
            self.font_style = fontinfo.styleName()
            self.font_weight = fontinfo.weight()
            self.set_font_style()

        # Something changed, so update temp SVG
        self.writeToFile(self.xmldoc)

        # Display SVG again
        self.display_svg()

    def find_in_list(self, l, value):
        '''when passed a partial value, function will return the list index'''
        for item in l:
            if item.startswith(value):
                return l.index(item)

    def update_font_color_button(self):
        """Updates the color shown on the font color button"""

        # Loop through each TEXT element
        for node in self.text_node:

            # Get the value in the style attribute
            s = node.attributes["style"].value

            # split the node so we can access each part
            ar = s.split(";")
            color = self.find_in_list(ar, "fill:")

            try:
                # Parse the result
                txt = ar[color]
                color = txt[5:]
            except:
                # If the color was in an invalid format, try the next text element
                continue

            opacity = self.find_in_list(ar, "opacity:")

            try:
                # Parse the result
                txt = ar[opacity]
                opacity = float(txt[8:])
            except:
                pass

            # Default the font color to white if non-existing
            if color == None:
                color = "#FFFFFF"

            # Default the opacity to fully visible if non-existing
            if opacity == None:
                opacity = 1.0

            color = QtGui.QColor(color)
            # Convert the opacity into the alpha value
            alpha = int(opacity * 65535.0)
            self.btnFontColor.setStyleSheet("background-color: %s; opacity %s" % (color.name(), alpha))

    def update_background_color_button(self):
        """Updates the color shown on the background color button"""

        if self.rect_node:

            # All backgrounds should be the first (index 0) rect tag in the svg
            s = self.rect_node[0].attributes["style"].value

            # split the node so we can access each part
            ar = s.split(";")

            color = self.find_in_list(ar, "fill:")

            try:
                # Parse the result
                txt = ar[color]
                color = txt[5:]
            except ValueError:
                pass

            opacity = self.find_in_list(ar, "opacity:")

            try:
                # Parse the result
                txt = ar[opacity]
                opacity = float(txt[8:])
            except ValueError:
                pass
            except TypeError:
                pass

            # Default the background color to black if non-existing
            if color == None:
                color = "#000000"

            # Default opacity to fully visible if non-existing
            if opacity == None:
                opacity = 1.0

            color = QtGui.QColor(color)
            # Convert the opacity into the alpha value
            alpha = int(opacity * 65535.0)
            # Set the alpha value of the button
            self.btnBackgroundColor.setStyleSheet("background-color: %s; opacity %s" % (color.name(), alpha))

    def set_font_style(self):
        '''sets the font properties'''

        # Loop through each TEXT element
        for text_child in self.text_node:
            # set the style elements for the main text node
            s = text_child.attributes["style"].value
            # split the text node so we can access each part
            ar = s.split(";")
            # we need to find each element that we are changing, shouldn't assume
            # they are in the same position in any given template.

            # ignoring font-weight, as not sure what it represents in Qt.
            fs = self.find_in_list(ar, "font-style:")
            ff = self.find_in_list(ar, "font-family:")
            ar[fs] = "font-style:" + self.font_style
            ar[ff] = "font-family:" + self.font_family
            # rejoin the modified parts
            t = ";"
            self.title_style_string = t.join(ar)

            # set the text node
            text_child.setAttribute("style", self.title_style_string)

        # Loop through each TSPAN
        for tspan_child in self.tspan_node:
            # set the style elements for the main text node
            s = tspan_child.attributes["style"].value
            # split the text node so we can access each part
            ar = s.split(";")
            # we need to find each element that we are changing, shouldn't assume
            # they are in the same position in any given template.

            # ignoring font-weight, as not sure what it represents in Qt.
            fs = self.find_in_list(ar, "font-style:")
            ff = self.find_in_list(ar, "font-family:")
            ar[fs] = "font-style:" + self.font_style
            ar[ff] = "font-family:" + self.font_family
            # rejoin the modified parts
            t = ";"
            self.title_style_string = t.join(ar)

            # set the text node
            tspan_child.setAttribute("style", self.title_style_string)

    def set_bg_style(self, color, alpha):
        '''sets the background color'''

        if self.rect_node:
            # split the node so we can access each part
            s = self.rect_node[0].attributes["style"].value
            ar = s.split(";")
            fill = self.find_in_list(ar, "fill:")
            if fill == None:
                ar.append("fill:" + color)
            else:
                ar[fill] = "fill:" + color

            opacity = self.find_in_list(ar, "opacity:")
            if opacity == None:
                ar.append("opacity:" + str(alpha))
            else:
                ar[opacity] = "opacity:" + str(alpha)

            # rejoin the modifed parts
            t = ";"
            self.bg_style_string = t.join(ar)
            # set the node in the xml doc
            self.rect_node[0].setAttribute("style", self.bg_style_string)

    def set_font_color_elements(self, color, alpha):

        # Loop through each TEXT element
        for text_child in self.text_node:

            # SET TEXT PROPERTIES
            s = text_child.attributes["style"].value
            # split the text node so we can access each part
            ar = s.split(";")
            fill = self.find_in_list(ar, "fill:")
            if fill == None:
                ar.append("fill:" + color)
            else:
                ar[fill] = "fill:" + color

            opacity = self.find_in_list(ar, "opacity:")
            if opacity == None:
                ar.append("opacity:" + str(alpha))
            else:
                ar[opacity] = "opacity:" + str(alpha)

            t = ";"
            text_child.setAttribute("style", t.join(ar))


            # Loop through each TSPAN
            for tspan_child in self.tspan_node:

                # SET TSPAN PROPERTIES
                s = tspan_child.attributes["style"].value
                # split the text node so we can access each part
                ar = s.split(";")
                fill = self.find_in_list(ar, "fill:")
                if fill == None:
                    ar.append("fill:" + color)
                else:
                    ar[fill] = "fill:" + color
                t = ";"
                tspan_child.setAttribute("style", t.join(ar))

    def accept(self):
        app = get_app()
        _ = app._tr

        # Get current project folder (if any)
        project_path = get_app().project.current_filepath
        default_folder = info.HOME_PATH
        if project_path:
            default_folder = os.path.dirname(project_path)

        # Init file path for new title
        title_path = os.path.join(default_folder, "%s.svg" % _("New Title"))

        # Get file path for SVG title
        file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Title As..."), title_path, _("Scalable Vector Graphics (*.svg)"))

        if file_path:
            # Append .svg (if not already there)
            if not file_path.endswith("svg"):
                file_path = file_path + ".svg"

            # Update filename
            self.filename = file_path

            # Save title
            self.writeToFile(self.xmldoc)

            # Add file to project
            self.add_file(self.filename)

            # Close window
            super(TitleEditor, self).accept()

    def add_file(self, filepath):
        path, filename = os.path.split(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        try:
            reader = clip.Reader()
            file_data = json.loads(reader.Json())

            # Set media type
            file_data["media_type"] = "image"

            # Save new file to the project data
            file = File()
            file.data = file_data
            file.save()
            return True

        except:
            # Handle exception
            msg = QMessageBox()
            msg.setText(_("{} is not a valid video, audio, or image file.".format(filename)))
            msg.exec_()
            return False

    def btnAdvanced_clicked(self):
        _ = self.app._tr
        # use an external editor to edit the image
        try:
            # Get settings
            s = settings.get_settings()

            # get the title editor executable path
            prog = s.get("title_editor")

            # launch advanced title editor
            # debug info
            log.info("Advanced title editor command: {} {} ".format(prog, self.filename))

            p = subprocess.Popen([prog, self.filename])

            # wait for process to finish (so we can update the preview)
            p.communicate()

            # update image preview
            self.load_svg_template()
            self.display_svg()

        except OSError:
            msg = QMessageBox()
            msg.setText(_("Please install {} to use this function").format(prog.capitalize()))
            msg.exec_()
