# -*- coding: utf-8 -*-
#-----------------------------------------------------------
#
# fTools
# Copyright (C) 2008-2011  Carson Farmer
# EMAIL: carson.farmer (at) gmail.com
# WEB  : http://www.ftools.ca/fTools.html
#
# A collection of data management and analysis tools for vector data
#
#-----------------------------------------------------------
#
# licensed under the terms of GNU GPL 2
#
# 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.
#
#---------------------------------------------------------------------

from PyQt4.QtCore import QObject, SIGNAL, QThread, QMutex, QFile
from PyQt4.QtGui import QDialog, QDialogButtonBox, QMessageBox

from qgis.core import QGis, QgsVectorFileWriter, QgsPoint, QgsGeometry, QgsFeature

import ftools_utils

from ui_frmSimplify import Ui_Dialog


class Dialog(QDialog, Ui_Dialog):

    def __init__(self, iface, function):
        QDialog.__init__(self, iface.mainWindow())
        self.setupUi(self)
        self.iface = iface
        self.myFunction = function

        self.workThread = None

        if self.myFunction == 2:
            self.setWindowTitle(self.tr("Densify geometries"))
            self.lblTolerance.setText(self.tr("Vertices to add"))
            self.spnTolerance.setDecimals(0)
            self.spnTolerance.setMinimum(1)
            self.spnTolerance.setSingleStep(1)
            self.spnTolerance.setValue(1.0)

        self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok)
        self.btnClose = self.buttonBox.button(QDialogButtonBox.Close)

        QObject.connect(self.chkWriteShapefile, SIGNAL("stateChanged( int )"), self.updateGui)
        QObject.connect(self.btnSelectOutputFile, SIGNAL("clicked()"), self.selectOutputFile)

        self.populateLayers()

    def populateLayers(self):
        layers = ftools_utils.getLayerNames([QGis.Polygon, QGis.Line])
        self.cmbInputLayer.clear()
        self.cmbInputLayer.addItems(layers)

    def updateGui(self):
        if self.chkWriteShapefile.isChecked():
            self.edOutputFile.setEnabled(True)
            self.btnSelectOutputFile.setEnabled(True)
            self.chkAddToCanvas.setEnabled(True)
        else:
            self.edOutputFile.setEnabled(False)
            self.btnSelectOutputFile.setEnabled(False)
            self.chkAddToCanvas.setEnabled(False)

        self.encoding = None

    def selectOutputFile(self):
        self.edOutputFile.clear()
        (self.shapeFileName, self.encoding) = ftools_utils.saveDialog(self)
        if self.shapeFileName is None or self.encoding is None:
            return
        self.edOutputFile.setText(self.shapeFileName)

    def accept(self):
        if not self.cmbInputLayer.currentText():
            QMessageBox.warning(self, self.tr("Warning"), self.tr("Please specify an input layer"))
            return
        vLayer = ftools_utils.getVectorLayerByName(self.cmbInputLayer.currentText())

        self.btnOk.setEnabled(False)

        if self.chkWriteShapefile.isChecked():
            outFileName = self.edOutputFile.text()
            outFile = QFile(outFileName)
            if outFile.exists():
                if not QgsVectorFileWriter.deleteShapeFile(outFileName):
                    QMessageBox.warning(self, self.tr("Delete error"),
                                        self.tr("Can't delete file %s") % (outFileName))
                    return

            self.workThread = GeomThread(self.myFunction, vLayer, self.chkUseSelection.isChecked(),
                                         self.spnTolerance.value(), True, outFileName, self.encoding)
        else:
            res = QMessageBox.warning(self, self.tr("Warning"),
                                      self.tr(
                "Currently QGIS doesn't allow simultaneous access from "
                                           "different threads to the same datasource. Make sure your layer's "
                                           "attribute tables are closed. Continue?"),
                                      QMessageBox.Yes | QMessageBox.No)
            if res == QMessageBox.No:
                return

            self.workThread = GeomThread(self.myFunction, vLayer, self.chkUseSelection.isChecked(),
                                         self.spnTolerance.value(), False, None, None)

        QObject.connect(self.workThread, SIGNAL("rangeCalculated( PyQt_PyObject )"), self.setProgressRange)
        QObject.connect(self.workThread, SIGNAL("featureProcessed()"), self.featureProcessed)
        QObject.connect(self.workThread, SIGNAL("processingFinished( PyQt_PyObject )"), self.processFinished)
        QObject.connect(self.workThread, SIGNAL("processingInterrupted()"), self.processInterrupted)

        self.btnClose.setText(self.tr("Cancel"))
        QObject.disconnect(self.buttonBox, SIGNAL("rejected()"), self.reject)
        QObject.connect(self.btnClose, SIGNAL("clicked()"), self.stopProcessing)

        self.workThread.start()

    def setProgressRange(self, maximum):
        self.progressBar.setRange(0, maximum)

    def featureProcessed(self):
        self.progressBar.setValue(self.progressBar.value() + 1)

    def processFinished(self, pointsCount):
        self.stopProcessing()

        if self.myFunction == 1:
            QMessageBox.information(self, self.tr("Simplify results"),
                                    self.tr("There were %d vertices in original dataset which\nwere reduced to %d vertices after simplification") % (pointsCount[0], pointsCount[1]))

        self.restoreGui()

        if self.chkAddToCanvas.isEnabled() and self.chkAddToCanvas.isChecked():
            if not ftools_utils.addShapeToCanvas(unicode(self.shapeFileName)):
                QMessageBox.warning(self, self.tr("Error"),
                                    self.tr("Error loading output shapefile:\n%s") % (unicode(self.shapeFileName)))
            self.populateLayers()

        QMessageBox.information(self, self.tr("Finished"), self.tr("Processing completed."))
        self.iface.mapCanvas().refresh()

    def processInterrupted(self):
        self.restoreGui()

    def stopProcessing(self):
        if self.workThread is not None:
            self.workThread.stop()
            self.workThread = None

    def restoreGui(self):
        self.progressBar.setValue(0)
        QObject.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
        self.btnClose.setText(self.tr("Close"))
        self.btnOk.setEnabled(True)


def geomVertexCount(geometry):
    geomType = geometry.type()
    if geomType == QGis.Line:
        if geometry.isMultipart():
            pointsList = geometry.asMultiPolyline()
            points = sum(pointsList, [])
        else:
            points = geometry.asPolyline()
        return len(points)
    elif geomType == QGis.Polygon:
        if geometry.isMultipart():
            polylinesList = geometry.asMultiPolygon()
            polylines = sum(polylinesList, [])
        else:
            polylines = geometry.asPolygon()
        points = []
        for l in polylines:
            points.extend(l)
        return len(points)
    else:
        return None


def densify(polyline, pointsNumber):
    output = []
    if pointsNumber != 1:
        multiplier = 1.0 / float(pointsNumber + 1)
    else:
        multiplier = 1
    for i in xrange(len(polyline) - 1):
        p1 = polyline[i]
        p2 = polyline[i + 1]
        output.append(p1)
        for j in xrange(pointsNumber):
            delta = multiplier * (j + 1)
            x = p1.x() + delta * (p2.x() - p1.x())
            y = p1.y() + delta * (p2.y() - p1.y())
            output.append(QgsPoint(x, y))
            if j + 1 == pointsNumber:
                break
    output.append(polyline[len(polyline) - 1])
    return output


def densifyGeometry(geometry, pointsNumber, isPolygon):
    output = []
    if isPolygon:
        if geometry.isMultipart():
            polygons = geometry.asMultiPolygon()
            for poly in polygons:
                p = []
                for ring in poly:
                    p.append(densify(ring, pointsNumber))
                output.append(p)
            return QgsGeometry.fromMultiPolygon(output)
        else:
            rings = geometry.asPolygon()
            for ring in rings:
                output.append(densify(ring, pointsNumber))
            return QgsGeometry.fromPolygon(output)
    else:
        if geometry.isMultipart():
            lines = geometry.asMultiPolyline()
            for points in lines:
                output.append(densify(points, pointsNumber))
            return QgsGeometry.fromMultiPolyline(output)
        else:
            points = geometry.asPolyline()
            output = densify(points, pointsNumber)
            return QgsGeometry.fromPolyline(output)


class GeomThread(QThread):

    def __init__(self, function, inputLayer, useSelection, tolerance, writeShape, shapePath, shapeEncoding):
        QThread.__init__(self, QThread.currentThread())
        self.inputLayer = inputLayer
        self.useSelection = useSelection
        self.tolerance = tolerance
        self.writeShape = writeShape
        self.outputFileName = shapePath
        self.outputEncoding = shapeEncoding
        self.myFunction = function

        self.mutex = QMutex()
        self.stopMe = 0

    def run(self):
        if self.myFunction == 1:
            self.runSimplify()
        else:
            self.runDensify()

    def runSimplify(self):
        self.mutex.lock()
        self.stopMe = 0
        self.mutex.unlock()

        interrupted = False

        shapeFileWriter = None

        pointsBefore = 0
        pointsAfter = 0

        if self.writeShape:
            vProvider = self.inputLayer.dataProvider()
            shapeFields = vProvider.fields()
            crs = vProvider.crs()
            wkbType = self.inputLayer.wkbType()
            if not crs.isValid():
                crs = None
            shapeFileWriter = QgsVectorFileWriter(self.outputFileName, self.outputEncoding, shapeFields, wkbType, crs)
            featureId = 0
            if self.useSelection:
                selection = self.inputLayer.selectedFeatures()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection))
                for f in selection:
                    featGeometry = QgsGeometry(f.geometry())
                    attrMap = f.attributes()

                    pointsBefore += geomVertexCount(featGeometry)
                    newGeometry = featGeometry.simplify(self.tolerance)
                    pointsAfter += geomVertexCount(newGeometry)

                    feature = QgsFeature()
                    feature.setGeometry(newGeometry)
                    feature.setAttributes(attrMap)
                    shapeFileWriter.addFeature(feature)
                    featureId += 1
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
            else:
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount())
                f = QgsFeature()
                fit = vProvider.getFeatures()
                while fit.nextFeature(f):
                    featGeometry = QgsGeometry(f.geometry())
                    attrMap = f.attributes()

                    pointsBefore += geomVertexCount(featGeometry)
                    newGeometry = featGeometry.simplify(self.tolerance)
                    pointsAfter += geomVertexCount(newGeometry)

                    feature = QgsFeature()
                    feature.setGeometry(newGeometry)
                    feature.setAttributes(attrMap)
                    shapeFileWriter.addFeature(feature)
                    featureId += 1
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
        else: # modify existing shapefile
            if not self.inputLayer.isEditable():
                self.inputLayer.startEditing()
            self.inputLayer.beginEditCommand("Simplify line(s)")
            if self.useSelection:
                selection = self.inputLayer.selectedFeatures()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection))
                for f in selection:
                    featureId = f.id()
                    featGeometry = QgsGeometry(f.geometry())

                    pointsBefore += geomVertexCount(featGeometry)
                    newGeometry = featGeometry.simplify(self.tolerance)
                    pointsAfter += geomVertexCount(newGeometry)

                    self.inputLayer.changeGeometry(featureId, newGeometry)
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
            else:
                vProvider = self.inputLayer.dataProvider()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount())
                f = QgsFeature()
                fit = vProvider.getFeatures()
                while fit.nextFeature(f):
                    featureId = f.id()
                    featGeometry = QgsGeometry(f.geometry())

                    pointsBefore += geomVertexCount(featGeometry)
                    newGeometry = featGeometry.simplify(self.tolerance)
                    pointsAfter += geomVertexCount(newGeometry)

                    self.inputLayer.changeGeometry(featureId, newGeometry)
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break

        # cleanup
        if self.inputLayer.isEditable():
            self.inputLayer.endEditCommand()

        if shapeFileWriter is not None:
            del shapeFileWriter

        if not interrupted:
            self.emit(SIGNAL("processingFinished( PyQt_PyObject )"), (pointsBefore, pointsAfter))
        else:
            self.emit(SIGNAL("processingInterrupted()"))

    def runDensify(self):
        self.mutex.lock()
        self.stopMe = 0
        self.mutex.unlock()

        interrupted = False

        shapeFileWriter = None

        isPolygon = self.inputLayer.geometryType() == QGis.Polygon

        if self.writeShape:
            # prepare writer
            vProvider = self.inputLayer.dataProvider()
            shapeFields = vProvider.fields()
            crs = vProvider.crs()
            wkbType = self.inputLayer.wkbType()
            if not crs.isValid():
                crs = None
            shapeFileWriter = QgsVectorFileWriter(self.outputFileName, self.outputEncoding, shapeFields, wkbType, crs)
            featureId = 0

            if self.useSelection:
                selection = self.inputLayer.selectedFeatures()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection))
                for f in selection:
                    featGeometry = QgsGeometry(f.geometry())
                    attrMap = f.attributes()
                    newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon)

                    feature = QgsFeature()
                    feature.setGeometry(newGeometry)
                    feature.setAttributes(attrMap)
                    shapeFileWriter.addFeature(feature)
                    featureId += 1
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
            else:
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount())
                f = QgsFeature()
                fit = vProvider.getFeatures()
                while fit.nextFeature(f):
                    featGeometry = QgsGeometry(f.geometry())
                    attrMap = f.attributes()
                    newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon)

                    feature = QgsFeature()
                    feature.setGeometry(newGeometry)
                    feature.setAttributes(attrMap)
                    shapeFileWriter.addFeature(feature)
                    featureId += 1
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
        else: # modify existing shapefile
            if not self.inputLayer.isEditable():
                self.inputLayer.startEditing()

            self.inputLayer.beginEditCommand("Densify line(s)")

            if self.useSelection:
                selection = self.inputLayer.selectedFeatures()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), len(selection))
                for f in selection:
                    featureId = f.id()
                    featGeometry = QgsGeometry(f.geometry())
                    newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon)

                    self.inputLayer.changeGeometry(featureId, newGeometry)
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break
            else:
                vProvider = self.inputLayer.dataProvider()
                self.emit(SIGNAL("rangeCalculated( PyQt_PyObject )"), vProvider.featureCount())
                f = QgsFeature()
                fit = vProvider.getFeatures()
                while fit.nextFeature(f):
                    featureId = f.id()
                    featGeometry = QgsGeometry(f.geometry())
                    newGeometry = densifyGeometry(featGeometry, int(self.tolerance), isPolygon)

                    self.inputLayer.changeGeometry(featureId, newGeometry)
                    self.emit(SIGNAL("featureProcessed()"))

                    self.mutex.lock()
                    s = self.stopMe
                    self.mutex.unlock()
                    if s == 1:
                        interrupted = True
                        break

        # cleanup
        if self.inputLayer.isEditable():
            self.inputLayer.endEditCommand()

        if shapeFileWriter is not None:
            del shapeFileWriter

        if not interrupted:
            self.emit(SIGNAL("processingFinished( PyQt_PyObject )"), None)
        else:
            self.emit(SIGNAL("processingInterrupted()"))

    def stop(self):
        self.mutex.lock()
        self.stopMe = 1
        self.mutex.unlock()

        QThread.wait(self)
