#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 	$Id$
licence={}
licence['en']="""\
uicilibris version %s:

a program to harvest a book from mediawiki contents

Copyright (C)2011 Georges Khaznadar <georgesk@ofset.org>

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, see
<http://www.gnu.org/licenses/>.
"""

licence['fr']=u"""\
uicilibris version %s:

un programme moisonner des livres à partir de contenus d'un mediawiki

Copyright (C) 2011 Georges Khaznadar <georgesk@ofset.org>

Ce projet est un logiciel libre : vous pouvez le redistribuer, le
modifier selon les terme de la GPL (GNU Public License) dans les
termes de la Free Software Foundation concernant la version 2 ou
plus de la dite licence.

Ce programme est fait avec l'espoir qu'il sera utile mais SANS
AUCUNE GARANTIE. Lisez la licence pour plus de détails.

<http://www.gnu.org/licenses/>.
"""

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from Ui_uicilibris import Ui_MainWindow
from waitDialog import spinWheelWaitDialog
import version, wikiParser, w2book, w2beamer, w2article, export
import sys, re, StringIO, time, tempfile, subprocess, os.path
import uiciautotemplates

locale="C" # this global variable may be redefined later

class w2mMainWindow(QMainWindow):
    
    def __init__(self, parent, argv, locale="C"):
        """
        The constructor
        @param parent a parent window
        @param argv a list of arguments
        """
         ######QT
        QMainWindow.__init__(self)
        self.windowTitle="UICI LIBRIS"
        QWidget.__init__(self, parent)
        self.locale=locale
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui_connections()
        self.wikiIndex=None
        self.latexSource=""
        self.wikiSource=""
        self.latexComp=None
        self.wb=None
        self.fragment=QString("")
        self.setPdfProductsEnabled(False)
        self.ui.searchCombo.addItem("")
        self.ui.searchCombo.addItem("Warning:")
        self.ui.searchCombo.addItem("Error:")
        self.style=None
        self.initTargetBox()
        self.threadLinksToLatex=None
        return

    def targets(self):
        """
        @return a dictionary targets for the expansion wiki->latex;
        each target is the key to
        a tuple (QString, parser class, boolean oldWiki2beamer)
        """
        return {"0book"   : (QApplication.translate("uicilibris", "Style = Wiki to Book", None, QApplication.UnicodeUTF8), w2book, False),
                "1article": (QApplication.translate("uicilibris", "Style = Wiki to Article", None, QApplication.UnicodeUTF8),w2article,False),
                "2beamer" : (QApplication.translate("uicilibris", "Style = Wiki to Beamer", None, QApplication.UnicodeUTF8),w2beamer, True),
                }

    def autoParser(self):
        """
        returns automatically the right parser based on the target selected.
        @return a parser class
        """
        return self.targets()[self.style][1]

    def autoOldStyle(self):
        """
        returns automatically the value of "old-style", which refers to the
        style inherited from wiki2beamer original source
        @return True if wiki2beamer's old style must be used.
        """
        return self.targets()[self.style][2]

    
    def initTargetBox(self):
        """
        initializes the contents of the target comboBox
        """
        dico=self.targets()
        for k in sorted(dico.keys()):
            self.ui.targetBox.addItem(dico[k][0])
        self.ui.targetBox.setCurrentIndex(0)
        self.style=sorted(dico.keys())[0]

    def setPdfProductsEnabled(self, state):
        """
        enable/disable the buttons which depend from files output by pdflatex
        @param state: the new state of those elements
        """
        self.ui.viewPdfButton.setEnabled(state)
        self.ui.hackButton.setEnabled(state)
        self.ui.gotoLogButton.setEnabled(state)
        # allow exports even if the source could not be compiled to a valid PDF
        # self.ui.exportButton.setEnabled(state)
        if not state:
            self.ui.logArea.clear()
        
    def ui_connections(self):
        """
        Connects signals with methods
        """
        self.connect(self.ui.action_Quit, SIGNAL('triggered()'), self.close)
        self.connect(self.ui.actionUser_manual, SIGNAL('triggered()'), self.displayManual)
        self.connect(self.ui.action_About, SIGNAL('triggered()'), self.about)
        self.connect(self.ui.text2latexButton, SIGNAL('clicked()'), self.toLatex)
        self.connect(self.ui.links2latexButton, SIGNAL('clicked()'), self.linksToLatex)
        self.connect(self.ui.baEditButton, SIGNAL('clicked()'), self.getWikiIndex)
        self.connect(self.ui.targetBox, SIGNAL('currentIndexChanged(int)'), self.newStyle)
        self.connect(self, SIGNAL("latexready"), self.feedIntoLatexArea)
        self.connect(self, SIGNAL("pdfready"), self.registerPdf)
        self.connect(self, SIGNAL("wikiAddress"), self.newWikiAddress)
        self.connect(self, SIGNAL("imgAddress"),  self.incCompileProgress)
        self.connect(self, SIGNAL("errMsg"),  self.displayErr)
        self.connect(self, SIGNAL("notYetImplemented"),  self.warnNotYetImplemented)
        self.connect(self.ui.runPdflatexButton, SIGNAL('clicked()'), self.runPdfLatex)
        self.connect(self.ui.viewPdfButton, SIGNAL('clicked()'), self.viewPdfLatex)
        self.connect(self.ui.hackButton, SIGNAL('clicked()'), self.hack)
        self.connect(self.ui.gotoLogButton, SIGNAL('clicked()'), self.gotoLog)
        self.connect(self.ui.exportButton, SIGNAL('clicked()'), self.export)
        self.connect(self.ui.searchCombo, SIGNAL('editTextChanged(QString)'), self.search)
        self.connect(self.ui.searchNextButton, SIGNAL('clicked()'), self.searchNext)
        ### connect drop methods
        self.ui.wikiDropArea.__class__.dragEnterEvent = self.wa_dragEnterEvent
        self.ui.wikiDropArea.__class__.dropEvent = self.wa_dropEvent
        return

    #### wiki drop area methods

    def wa_dragEnterEvent(self, event):
        event.acceptProposedAction()
        return
    
    httpRegexp=re.compile(r"(http://.*)/.*/\S+")
    
    def wa_dropEvent(self, event):
        mimeData = event.mimeData()
        if mimeData.hasText():
            text=unicode(mimeData.text())
            m=w2mMainWindow.httpRegexp.match(text)
            if m:
                # get the web page's source
                self.wikiIndex, text=wikiParser.getWikiContents(text)
                text=re.sub(r'&lt;\[\s*autotemplate\s*\]', '<[ autotemplate ]', text)
                self.ui.wikiDropArea.setPlainText(text)
                self.ui.wikiIndex.setText(self.wikiIndex)
            else:
                self.ui.wikiDropArea.setPlainText(text)
        event.acceptProposedAction()
        return

    def closeEvent(self, event):
        """
        reimplements the close event: cleans up temporary dirs
        """
        if self.latexComp != None:
            del self.latexComp
        QMainWindow.closeEvent(self, event)

    def newStyle(self, index):
        """
        callback function invoked when the index has changed in
        self.ui.targetBox; modifies self.style
        """
        self.style=sorted(self.targets().keys())[index]

    def about(self):
        """
        displays the about dialog
        """
        global locale
        if locale[:2]=="fr":
            l="fr"
        else:
            l="en"
        msg=licence[l] %version.version
        QMessageBox.information(None, QApplication.translate("uicilibris", "About", None, QApplication.UnicodeUTF8), msg)
        return

    def displayManual(self):
        """
        displays the manual
        """
        cmd="(sensible-browser /usr/share/uicilibris/guide/index.html &)"
        subprocess.call(cmd, shell=True)
        return

    def search(self, fragment):
        """
        makes a search in the error tab
        @param fragment a QString to search
        """
        self.ui.logArea.moveCursor(QTextCursor.StartOfWord)
        self.fragment=fragment
        if self.fragment.length() > 0:
            self.ui.logArea.find(self.fragment)
        
    def searchNext(self):
        """
        search the next occurence of a fragment if it is defined
        """
        if self.fragment.length() > 0:
            self.ui.logArea.find(self.fragment)
        if self.fragment.length() > 4 and self.ui.searchCombo.findText(self.fragment) < 0:
            self.ui.searchCombo.addItem(self.fragment)

    def setImages(self, imageSet):
        """
        register the set of images to be used with one document
        @param imageSet a set of images filenames in unicode format
        """
        self.imageSet=imageSet
        return

    def toLatex(self):
        """
        turns the selected contents of the first tab into Latex code which is
        fed into the second tab. If nothing is selected previously, the whole
        contents are selected.
        """
        self.wikiSource=self.getAreaSelection(self.ui.wikiDropArea)
        self.ui.latexCodeArea.clear()
        self.toLatexWait=spinWheelWaitDialog(self, self.stopToLatex, title=QApplication.translate("uicilibris", "Expanding to LaTeX", None, QApplication.UnicodeUTF8))
        self.toLatexWait.show()
        self.toLThread=toLatexThread(self)
        self.toLThread.start()

    def stopToLatex(self):
        """
        terminates the thread of LaTeX expansion if it is still running
        """
        if self.toLThread.isRunning():
            self.toLThread.terminate()

    def dropAreaText(self):
        """
        @return the text contained in the UI's drop area
        """
        return self.getAreaSelection(self.ui.wikiDropArea)
    
    def linksToLatex(self):
        """
        turns the selected contents of the first tab into Latex code which is
        fed into the second tab. The first tab is supposed to provide
        a series of links to wiki pages. If nothing is selected previously, the whole
        contents are selected.
        """
        self.inputText=self.dropAreaText()
        self.autoTemplates=uiciautotemplates.collectAutoTemplates(self.inputText.split("\n"))
        self.ui.latexCodeArea.clear()
        if self.wikiIndex==None:
            self.getWikiIndex()
        wikiAddresses=re.findall("\[\[([^\]]+)\]\]", self.inputText)
        self.currentWikiAddress=0 # number of addresses processed
        self.progressL2L=QProgressDialog(QApplication.translate("uicilibris","Connection to the mediawiki ...", None, QApplication.UnicodeUTF8),
                                         QApplication.translate("uicilibris", "Cancel", None, QApplication.UnicodeUTF8),
                                         0, len(wikiAddresses)+1, self)
        self.progressL2L.setWindowTitle(QApplication.translate("uicilibris", "Links to LaTeX processing", None, QApplication.UnicodeUTF8))
        self.progressL2L.show()
        self.connect(self.progressL2L, SIGNAL("canceled()"), self.killThreadLinksToLatex)
        if self.threadLinksToLatex==None:
            # avoid starting this thread twice
            self.threadLinksToLatex=l2lThread(self)
            self.threadLinksToLatex.start()

    def getAreaSelection(self, area):
        """
        gets the text selectet in a text area. Some recoding is
        done to process end of paragraphs (unicode chatrs u'\u2029')
        @param area a QTextEdit instance
        @return the selected text in unicode format, or all the text
        """
        cursor=area.textCursor()
        if not cursor.hasSelection():
            area.selectAll()
            cursor=area.textCursor()
        text=cursor.selectedText()
        text=text.replace(u'\u2029', '\n')
        return unicode(text)

    def getWikiIndex(self):
        """
        inputs self.wikiIndex
        """
        wikiIndex, ok = QInputDialog.getText (self, u"Enter the base address of a mediawiki", u"Base address of a MediaWiki:")
        if ok:
            self.wikiIndex=unicode(wikiIndex)
            self.ui.wikiIndex.setText(wikiIndex)
        return


    def killThreadLinksToLatex(self):
        """
        stops the thread self.threadLinksToLatex
        """
        self.threadLinksToLatex.terminate()
        
    def newWikiAddress(self, a):
        """
        increments the progress bar in self.progressL2L
        and changes the text of the label
        @param a the wiki address just processed
        """
        self.currentWikiAddress+=1
        self.progressL2L.setValue(self.currentWikiAddress)
        self.progressL2L.setLabelText(QApplication.translate("uicilibris", "processing '%1' ...", None, QApplication.UnicodeUTF8).arg(a))

    def displayErr(self, msg):
        """
        appends a lin in the Error area
        @param msg an error message
        """
        self.ui.errorArea.append(msg)

    def warnNotYetImplemented(self, msg):
        """
        warns about a feature not yet implemented
        """
        QMessageBox.warning (self, QApplication.translate("uicilibris","Feature not yet implemented", None, QApplication.UnicodeUTF8),
                             QApplication.translate("uicilibris", "The feature '%1' is not yet implemented. Report a bug to the developers.", None, QApplication.UnicodeUTF8).arg(msg) )
        return
    
    def feedIntoLatexArea(self, progress=None):
        """
        sets the contents of the latex area
        @param progress must be True when a progressbar is used.
        """
        text=("%s" %self.wb).decode("utf-8")
        self.ui.latexCodeArea.setPlainText(text)
        self.ui.tabWidget.setCurrentIndex(1)
        if progress == "compiling":
            self.currentWikiAddress+=1
            self.progressL2L.setValue(self.currentWikiAddress)
            self.progressL2L.setLabelText(u"Finishing translation ...")
        elif progress=="endL2L":
            self.progressL2L.close()
        elif progress=="endToLatex":
            self.toLatexWait.close()
        
    def registerPdf(self, lc):
        """
        registers a recently compiles PDF file and displays log data
        in the log panel. Closes eventual progress monitors
        @param lc a latexComp object
        """
        self.latexComp=lc
        if os.path.exists(self.latexComp.pdfFileName):
            self.setPdfProductsEnabled(True)
        else:
            self.ui.hackButton.setEnabled(True)
            QMessageBox.warning (self, QApplication.translate("uicilibris","Pdflatex failed", None, QApplication.UnicodeUTF8),
                                 QApplication.translate("uicilibris", "For some reason, Pdflatex failed.<b />Try to hack around.", None, QApplication.UnicodeUTF8) )
        logInput=open(self.latexComp.logFileName,"r")
        try:
            log=logInput.read().decode("utf-8")
        except:
            logInput.seek(0)
            log=logInput.read().decode("ISO-8859-1")
            pass
        logInput.close
        self.ui.logArea.setPlainText(log)
        self.compileProgress.close()
        
    def runPdfLatex(self):
        """
        runs PdfLatex with the contents available in self.ui.latexCodeArea
        """
        if self.latexComp != None:
            del (self.latexComp)
            self.latexComp=None
        self.setPdfProductsEnabled(False)
        self.currentImage=0
        self.compileProgress=QProgressDialog(QApplication.translate("uicilibris", "Getting images ...", None, QApplication.UnicodeUTF8),
                                             QApplication.translate("uicilibris", "Cancel", None, QApplication.UnicodeUTF8), 0, self.wb.imageCount()+1, self)
        self.compileProgress.setWindowTitle(QApplication.translate("uicilibris", "LaTeX compilation", None, QApplication.UnicodeUTF8))
        self.compileProgress.show()
        self.connect(self.compileProgress, SIGNAL("canceled()"), self.stopPdfLatex)
        self.latexSource=unicode(self.ui.latexCodeArea.toPlainText())
        self.lcThread=latexCompileThread(self)
        self.lcThread.start()

    def incCompileProgress(self, info):
        """
        deals with a text information to deliver to self.compileProgress
        """
        self.currentImage+=1
        self.compileProgress.setValue(self.currentImage)
        self.compileProgress.setLabelText(QApplication.translate("uicilibris", "getting '%1' ...", None, QApplication.UnicodeUTF8).arg(info))
        return

    def stopPdfLatex(self):
        """
        terminates the thread of Latex compilation if it is still running
        """
        if self.lcThread.isRunning():
            self.lcThread.terminate()

    def viewPdfLatex(self):
        """
        launches a subprocess to view the PDF file if any
        """
        if self.latexComp and os.path.exists(self.latexComp.pdfFileName):
            cmd="(evince %s &)" %self.latexComp.pdfFileName
            subprocess.call(cmd, shell=True)
        else:
            QMessageBox.warning(self, QApplication.translate("uicilibris", "Cannot open PDF file", None, QApplication.UnicodeUTF8),
                                QApplication.translate("uicilibris", "There is no reachable PDF file. Compile the LaTeX source, or check the error tab.", None, QApplication.UnicodeUTF8))

    def hack(self):
        """
        launches gnome-terminal and opens a shell in self.tmpDir
        """
        if self.latexComp!=None:
            tmpdir=self.latexComp.tmpdir
            cmd="(cd %s; gnome-terminal --title='%s' &)" %(tmpdir, "Uici Libris Terminal (%s)" %tmpdir)
            subprocess.call(cmd, shell=True)
        else:
            QMessageBox.warning(self, QApplication.translate("uicilibris", "Cannot find the temporary files", None, QApplication.UnicodeUTF8),
                                QApplication.translate("uicilibris", "There is no temporary files. Please run Pdflatex.", None, QApplication.UnicodeUTF8))
        return

    def gotoLog(self):
        """
        raises the log panel
        """
        self.ui.tabWidget.setCurrentIndex(2)
        return

    def export(self):
        """
        starts a dialog to export the content of the cache to another
        enabled mediawiki. If the dialog succeeds, goes on with exportation.
        """
        d=export.Dialog(self)
        d.show()
       
        

class latexComp(QObject):
    """
    implements a process to compile LaTeX files and take in account
    the log file produced
    """
    def __init__(self, uici, parent=None, cbInfo=None, coding="utf-8"):
        """
        the constructor
        @param uici uici.wb must be a w2book instance
        @param parent a parent for the QObject structure
        @param cbInfo a callback function to deal with information
        @param coding the system to use when writing a text file.
        Defaults to "utf-8"
        """
        QObject.__init__(self, parent)
        self.tmpdir=tempfile.mkdtemp(prefix="uici_")
        self.texFilename= os.path.join(self.tmpdir,"out.tex")
        self.pdfFileName= os.path.join(self.tmpdir,"out.pdf")
        self.auxFileName= os.path.join(self.tmpdir,"out.aux")
        self.logFileName= os.path.join(self.tmpdir,"out.log")
        outfile=open(self.texFilename, "w")
        outfile.write(uici.latexSource.encode(coding))
        outfile.close()
        uici.wb.getImages(self.tmpdir, cbInfo)

    def __del__(self):
        """
        the destructor takes care of the temporary directory
        """
        import subprocess
        cmd="rm -rf %s" %self.tmpdir
        subprocess.call(cmd, shell=True)

    def compile(self):
        """
        launches the compilation of the source file
        """
        cmd="cd %s; pdflatex -shell-escape -interaction=nonstopmode out.tex" %self.tmpdir
        compileAgain=True
        while compileAgain:
            existAux=os.path.exists(self.auxFileName)
            pipe=subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            self.logOutput=pipe.communicate()
            compileAgain = not existAux or len(self.labelWarning())>0

    def labelWarning(self):
        """
        @return the warning when cross-references are not fine
        """
        return re.findall(r"LaTeX Warning: Label\(s\) may have changed. Rerun to get cross-references right.", self.logOutput[0])

class l2lThread(QThread):
    """
    a class to run linksToLatex on the behalf of a main window
    it will accept an url and a text for its initialization,
    and will return a processed text upon thread's termination.
    """
    def run(self):
        """
        turns the contents of the parent's first tab into Latex code which is
        fed into the second tab. The first tab is supposed to provide
        a series of links to wiki pages
        """
        p = self.parent().autoParser()
        if p:
            wb=p.wiki2(report=False, parent=self.parent(), latexReadyParam="compiling")
            wb.reloadCacheIndirect(self.parent().inputText, cbInfo=self.cbInfo)
            self.parent().emit(SIGNAL("latexready"), "compiling")
        else:
            self.parent().emit(SIGNAL("notYetImplemented"),self.parent().style)
            self.parent().emit(SIGNAL("latexready"), "endL2L")
        self.parent().threadLinksToLatex=None # allow triggering this thread later


    def cbInfo(self, info):
        """
        a callback function to display progress information
        """
        self.parent().emit(SIGNAL("wikiAddress"), info)

    def reportErr(self, msg):
        """
        a call back function to send message to the error tab in the main window
        """
        self.parent().emit(SIGNAL("errMsg"), msg)

class toLatexThread(QThread):
    """
    a class to expand wiki code to Latex source
    """
    def run(self):
        p = self.parent().autoParser()
        if p:
            wb=p.wiki2(report=False, parent=self.parent(), latexReadyParam="endToLatex")
            wb.parseWikiSource()
        else:
            self.parent().emit(SIGNAL("notYetImplemented"),self.parent().style)
            self.parent().emit(SIGNAL("latexready"), "endToLatex")
        return
            
        
class latexCompileThread(QThread):
    """
    a class to compile a Latex source into a temporary directory
    when the thread terminates, it sends back an object with the
    necessary pointers to the compiled stuff
    """
    def run(self):
        """
        creates an object which owns a temporary directory,
        launches a compilation of Latex sources inside it,
        and returns this object upon completion
        """
        lc=latexComp(self.parent(), cbInfo=self.cbInfo)
        try:
            lc.compile()
        finally: #ensures that the signal is emitted even if the compilation fails inexpectedly
            self.parent().emit(SIGNAL("pdfready"), lc)

    def cbInfo(self, info):
        """
        a callback function to display progress information
        """
        self.parent().emit(SIGNAL("imgAddress"), info)

        
class latexViewThread(QThread):
    """
    a class to view a PDF file inside a thread
    """
    def __init__(self, parent, pdfFileName):
        QThread.__init__(parent)
        self.pdfFileName=pdfFileName
        
    def run(self):
        import subprocess
        cmd="evince %s" %self.pdfFileName
        subprocess.call(cmd, shell=True)
        self._exec()       
            
def run(argv):
    global locale
    app = QApplication(sys.argv)

    ###translation##
    locale = QLocale.system().name()
    qtTranslator = QTranslator()
    if qtTranslator.load("qt_" + locale):
        app.installTranslator(qtTranslator)
        
    appTranslator = QTranslator()
    if appTranslator.load("/usr/share/uicilibris/lang/uicilibris_" + locale):
        app.installTranslator(appTranslator)
    
    w = w2mMainWindow(None,argv,locale)
    w.show()
    sys.exit(app.exec_())
    

if __name__=='__main__':
    run(sys.argv)
