# -*- coding: utf-8 -*-
"""
/***************************************************************************
 PDF
                                 A QGIS plugin
 Allows you to fill pdf forms with data from attribute table.
                              -------------------
        begin                : 2016-08-30
        git sha              : $Format:%H$
        copyright            : (C) 2016 by Dawid Dzięgiel
        email                : daw.dziegiel@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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from PyQt4.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, Qt, QVariant, QObject
from PyQt4.QtGui import QAction, QIcon, QFileDialog, QTableWidgetItem, QComboBox, QApplication, QMessageBox
from qgis.core import QgsMapLayerRegistry, QgsProject, QgsVectorDataProvider, QgsField
# Initialize Qt resources from file resources.py
import resources
# Import the code for the dialog
from pdf_filler_dialog import PDFDialog
# Import modules
import os
import subprocess
import sys
from time import sleep, strftime, time
# 3rd party libraries
from fdf import FDFModule


class PDF:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'PDF_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Create the dialog (after translation) and keep reference
        self.dlg = PDFDialog()

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&PDF Filler')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'PDF')
        self.toolbar.setObjectName(u'PDF')

        self.dlg.browseButton.clicked.connect(self.select_fillable_pdf)
        self.dlg.browseFolderButton.clicked.connect(self.select_directory)
        self.dlg.generateButton.clicked.connect(self.generate_wrapper)
        self.dlg.fillablePdfLineEdit.textChanged.connect(self.configure_layer_combobox)
        self.dlg.fillablePdfLineEdit.textChanged.connect(self.check_lineedits)
        self.dlg.folderLineEdit.textChanged.connect(self.check_lineedits)
        self.dlg.layerComboBox.currentIndexChanged.connect(self.add_and_populate_comboboxes_in_table)
        self.dlg.stopButton.clicked.connect(self.stop)
        self.dlg.layerComboBox.setDisabled(True)
        self.dlg.generateButton.setDisabled(True)
        self.dlg.stopButton.setVisible(False)
        self.dlg.progressBar.setVisible(False)
        self.dlg.addFieldCheckBox.setToolTip("Field contains time and date of generated pdf")
        self.msg = QMessageBox

        self.kill = False
        self.x = True

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('PDF', message)


    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/PDF/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'PDF Filler'),
            callback=self.run,
            parent=self.iface.mainWindow())


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&PDF Filler'),
                action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar


    def run(self):
        """Run method that performs all the real work"""

        project = QgsProject.instance()
        # show the dialog
        self.dlg.labelReq.setVisible(False)
        self.dlg.labelReq_2.setVisible(False)
        self.dlg.show()

        self.dlg.progressBar.reset()
        self.number_of_layers_check()
        self.extend_columns_to_table()
        self.check_lineedits()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            project.writeEntry("PDF", "selected_layer", self.dlg.layerComboBox.currentText())

    def select_fillable_pdf(self):
        """Saves path of selected file."""
        self.x = True

        filename = QFileDialog.getOpenFileName(self.dlg, "Select your pdf ","", '*.pdf')
        if filename:
            output_name = self.dump_pdf_field_names(filename)
        else:
            return

        if os.stat(output_name).st_size > 0 and self.x:
            self.dlg.fillablePdfLineEdit.setText(filename)
            self.update_forms_column_in_table()
            self.add_and_populate_comboboxes_in_table()
            self.dlg.labelReq.setVisible(False)
        else:
            self.dlg.labelReq.setVisible(True)

        return output_name

    def dump_pdf_field_names(self, template, output_name='pdf_fields.txt'):
        """Exports pdf field names to txt file.

        :param template: pdf file with spots to fill.
        :param output_name: name of file contains pdf field names
        :returns: txt file contains pdf field names.
        """

        cmd = ('pdftk %s dump_data_fields output %s' % (template, output_name))

        self.call_process(cmd)

        return output_name

    def call_process(self, cmd):
        """Makes command in shell silently.

        :param cmd: command
        """

        try:
            subprocess.call(cmd, shell=True)
        except:
            self.x = False
            self.dlg.labelReq.setVisible(True)
            return

    def imports_and_extracts_pdf_field_names(self, output_name='pdf_fields.txt'):
        """Imports txt file contains pdf field names, then extracts them.

        :param output_name: name of file contains pdf field names
        :return: list of extracted pdf field names
        """

        pdf_field_names = []
        with open(output_name) as file:
            for line in file:
                if "FieldName:" in line:
                    (key, value) = line.split()
                    pdf_field_names.append(value)
        file.close()

        return pdf_field_names

    def update_forms_column_in_table(self):

        pdf_field_names = self.imports_and_extracts_pdf_field_names()

        if self.dlg.fillablePdfLineEdit.text() == "":
            self.remove_all_rows_in_table()
            self.remove_comboboxes_in_table()
        elif self.dlg.fillablePdfLineEdit.isModified:
            self.remove_all_rows_in_table()
            for i in range(0, len(pdf_field_names)):
                self.dlg.tableWidget.insertRow(i)
                self.dlg.tableWidget.setItem(i, 0, QTableWidgetItem(pdf_field_names[i]))

    def remove_all_rows_in_table(self):

        while self.dlg.tableWidget.rowCount() > 0:
            self.dlg.tableWidget.removeRow(0)

    def add_and_populate_comboboxes_in_table(self):

        project = QgsProject.instance()
        project.writeEntry("PDF", "selected_layer", self.dlg.layerComboBox.currentText())

        self.remove_column_with_field_names()

        selected_layer_index = self.dlg.layerComboBox.findText(project.readEntry("PDF", "selected_layer")[0],
                                                               Qt.MatchFixedString)
        if selected_layer_index == -1:
            return

        selected_layer = self.dlg.layerComboBox.itemData(selected_layer_index)

        layer_fields = selected_layer.pendingFields()

        number_of_rows = self.dlg.tableWidget.rowCount()
        i = 0

        while i < number_of_rows:
            combobox = QComboBox()
            for field in layer_fields:
                combobox.addItem(field.name())
            self.dlg.tableWidget.setCellWidget(i, 1, combobox)
            i += 1

    def populate_combobox_with_layers(self):

        project = QgsProject.instance()

        layers = QgsMapLayerRegistry.instance().mapLayers().values()

        for layer in layers:
            self.dlg.layerComboBox.addItem(layer.name(), layer)

        selected_layer_index = self.dlg.layerComboBox.findText(project.readEntry("PDF", "selected_layer")[0],
                                                               Qt.MatchFixedString)
        if selected_layer_index >= 0:
            self.dlg.layerComboBox.setCurrentIndex(selected_layer_index)


    def configure_layer_combobox(self):

        if self.dlg.fillablePdfLineEdit.text() == "":
            self.dlg.layerComboBox.setDisabled(True)
        else:
            self.dlg.layerComboBox.setDisabled(False)

    def extend_columns_to_table(self):

        header = self.dlg.tableWidget.horizontalHeader()
        header.setStretchLastSection(True)

    def remove_column_with_field_names(self):
        """Removes column, where set comboboxes."""

        for row in range(0, self.dlg.tableWidget.rowCount()):
            self.dlg.tableWidget.removeCellWidget(row, 1)

    def number_of_layers_check(self):
        """Updates list of layers in combobox if needed."""

        layers_interface = []
        for layer in QgsMapLayerRegistry.instance().mapLayers().values():
            layers_interface.append(layer.name())

        layers_combobox = [self.dlg.layerComboBox.itemText(i) for i in range(self.dlg.layerComboBox.count())]
        if layers_combobox != layers_interface:
            self.dlg.layerComboBox.clear()
            self.populate_combobox_with_layers()

    def remove_comboboxes_in_table(self):
        """Removes comboboxes with attributes in table."""

        number_of_rows = self.dlg.tableWidget.rowCount()

        while number_of_rows > 0:
            self.dlg.tableWidget.removeCellWidget(0 ,1)
            number_of_rows -= 1

    def generate_pdf(self, template, feature, layer):
        """Generates filled pdf file with given data.

        :param template: pdf file with spots to fill
        :param feature: one of selected features
        :param layer: current selected layer
        :return: filled pdf file
        """

        cmd = ('pdftk %s fill_form %s output %s flatten' % (template, FDFModule().fdf_file,
                                                            self.dlg.folderLineEdit.text()
                                                            + "\\"
                                                            + str(layer.name())
                                                            + "_fid_"
                                                            + str(feature.id())
                                                            + '.pdf'))
        self.call_process(cmd)


    def generate_wrapper(self):
        """Does all work, when generate process is called."""

        self.kill = False

        self.dlg.labelReq.setVisible(False)
        self.dlg.labelReq_2.setVisible(False)
        self.dlg.widget.setDisabled(True)
        self.dlg.generateButton.setVisible(False)
        self.dlg.stopButton.setVisible(True)
        self.dlg.okButton.setDisabled(True)
        self.dlg.progressBar.setVisible(True)
        self.dlg.widget.repaint()

        pdf_path = self.dlg.fillablePdfLineEdit.text()

        layer = self.get_current_layer()

        if self.dlg.addFieldCheckBox.isChecked():
            self.add_pdf_info_field(layer)

        if self.dlg.allFeaturesCheckBox.isChecked():
            selected_features = [feature for feature in layer.getFeatures()]
        else:
            selected_features = [feature for feature in layer.selectedFeatures()]
            if not selected_features:
                self.configure_layout()
                self.dlg.labelReq_2.setVisible(True)
                return

        number_of_rows = self.dlg.tableWidget.rowCount()
        fields = []
        i = 0

        for idx, feature in enumerate(selected_features, 1):
            while i < number_of_rows:
                fields.append((self.dlg.tableWidget.item(i, 0).text(),
                              feature[self.dlg.tableWidget.cellWidget(i, 1).currentText()]))
                i += 1
            QApplication.processEvents()
            if self.kill is True:
                QApplication.processEvents()
                break

            fdf = FDFModule()
            fdf.make_fdf_file(fields)

            self.generate_pdf(pdf_path, feature, layer)

            fields = []
            i = 0

            self.dlg.progressBar.setValue((float(idx)/(len(selected_features)))*100)

            if self.dlg.addFieldCheckBox.isChecked():
                self.add_data_to_pdf_info_field(feature, layer)

        if self.kill is True:
            self.configure_layout()
        else:
            self.configure_layout()
            self.msg.information(self.dlg, 'Success', "The operation completed successfuly.")


    def select_directory(self):
        """Sets directory, where generated files will be save."""

        directory_path = QFileDialog.getExistingDirectory(self.dlg, "Select folder, where you generate pdf files ")
        if directory_path:
            self.dlg.folderLineEdit.setText(directory_path)
        else:
            return

    def check_lineedits(self):
        """Checks if required fields are valid."""

        if self.dlg.fillablePdfLineEdit.text() != "" \
                and self.dlg.folderLineEdit.text() != "" \
                and self.dlg.layerComboBox.count() > 0:
            self.dlg.generateButton.setDisabled(False)
        else:
            self.dlg.generateButton.setDisabled(True)

    def add_pdf_info_field(self, layer):
        """Adds field called "PDF_info" to selected layer.

        :param layer: current selected layer
        """

        layer.updateFields()

        caps = layer.dataProvider().capabilities()

        if caps & QgsVectorDataProvider.AddAttributes:
            if "PDF_info" not in [field.name() for field in layer.fields()]:
                layer.dataProvider().addAttributes([QgsField("PDF_info", QVariant.String)])
        else:
            QMessageBox.critical(self.dlg, 'Error', "You haven't got permission to add attributes.")

        layer.updateFields()
        layer.commitChanges()

    def add_data_to_pdf_info_field(self, feature, layer):
        """Adds info (date and time) about generated files to "PDF_info" field.

        :param feature: one of (selected) features
        :param layer: current selected layer
        """

        layer_idx = layer.fieldNameIndex("PDF_info")

        layer.startEditing()
        feature.setAttribute(layer_idx, strftime("%Y-%m-%d %H:%M:%S"))

        layer.updateFeature(feature)
        layer.commitChanges()

    def get_current_layer(self):
        """Returns current selected layer in combobox."""

        layer_name = self.dlg.layerComboBox.currentText()
        item_index = self.dlg.layerComboBox.findText(layer_name)
        layer = self.dlg.layerComboBox.itemData(item_index)

        return layer

    def stop(self):
        """Called, when stop button is pressed."""

        self.kill = True

    def configure_layout(self):
        """Sets up layout, when "generate_wrapper" function was stopped or ended."""

        self.dlg.widget.setDisabled(False)
        self.dlg.generateButton.setVisible(True)
        self.dlg.stopButton.setVisible(False)
        self.dlg.okButton.setDisabled(False)
        self.dlg.progressBar.setVisible(False)
        self.dlg.progressBar.reset()