#!/usr/bin/env python2
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
                        print_function)

__license__ = 'GPL v3'
__copyright__ = '2016, 2017, 2018, Doitsu'

# PyQt libraries
from PyQt5.Qt import QTextEdit, QDockWidget, QApplication, QAction, QFileDialog, QMessageBox, QDialog, QListWidget, QVBoxLayout, QListWidgetItem, QDialogButtonBox, Qt
from PyQt5 import QtCore, QtGui

# Calibre libraries
from calibre.gui2.tweak_book.plugin import Tool
from calibre.utils.config import JSONConfig
from calibre.constants import iswindows, islinux, isosx

# standard libraries
import zipfile
import re, os, locale, sys, tempfile
import os.path
from os.path import expanduser, basename

# DiapDealer's temp folder code
from contextlib import contextmanager

@contextmanager
def make_temp_directory():
    import tempfile
    import shutil
    temp_dir = tempfile.mkdtemp()
    yield temp_dir
    shutil.rmtree(temp_dir)

# get the windows codepage
if iswindows:
    os_encoding = locale.getpreferredencoding()
else:
    os_encoding = 'UTF-8'
       
# jar wrapper for epubcheck
def jarWrapper(*args):
    import subprocess
    startupinfo = None
    
    # stop the windows console popping up every time the prog is run
    if iswindows:
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = subprocess.SW_HIDE
    
    process = subprocess.Popen(list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
    ret = process.communicate()
    returncode = process.returncode
    return ret, returncode
    
class DemoTool(Tool):
    
    #: Set this to a unique name it will be used as a key
    name = 'epub-check'

    #: If True the user can choose to place this tool in the plugins toolbar
    allowed_in_toolbar = True

    #: If True the user can choose to place this tool in the plugins menu
    allowed_in_menu = True

    def create_action(self, for_toolbar=True):
        # Create an action, this will be added to the plugins toolbar and
        # the plugins menu
        ac = QAction(get_icons('images/icon.png'), 'Run EpubCheck', self.gui)  # noqa
        if not for_toolbar:
            # Register a keyboard shortcut for this toolbar action. We only
            # register it for the action created for the menu, not the toolbar,
            # to avoid a double trigger
            self.register_shortcut(ac, 'epub-check-tool', default_keys=('Ctrl+Shift+Alt+E',))
        ac.triggered.connect(self.ask_user)
        return ac

    def ask_user(self):
        #--------------------------
        # display busy cursor
        #--------------------------
        QApplication.setOverrideCursor(Qt.WaitCursor)
    
        #----------------------------------------
        # get  Java language preference
        #----------------------------------------
        prefs = JSONConfig('plugins/EpubCheck')
        locale = None
        close_cb = False
        clipboard_copy  = False
        if 'locale' in prefs and prefs['locale'] in ['en', 'de', 'es', 'fr', 'it', 'ja', 'nl']:
            locale = prefs['locale']
        if 'close_cb' in prefs:
            close_cb = prefs['close_cb'] 
        if 'clipboard_copy' in prefs:
            clipboard_copy = prefs['clipboard_copy']            
        #-----------------------------------------------------
        # create a savepoint
        #----------------------------------------------------
        self.boss.add_savepoint('Before: EpubCheck')
        
        #--------------------------------------------------------------------
        # create a dictionary that maps names to relative hrefs
        #--------------------------------------------------------------------
        epub_mime_map = self.current_container.mime_map 
        epub_name_to_href = {}
        for href in epub_mime_map:
            epub_name_to_href[os.path.basename(href)] = href
        
        #--------------------------------------------------------------------
        # create temp directory and unpack epubcheck files
        #--------------------------------------------------------------------
        with make_temp_directory() as td:
            # write current container to temporary epub
            epub_path = os.path.join(td, 'temp.epub')
            self.boss.commit_all_editors_to_container()
            self.current_container.commit(epub_path)
            
            # unpack epubcheck files
            with zipfile.ZipFile(self.plugin.plugin_path, 'r') as zf:
                zf.extractall(td)
                epc_path = os.path.join(td, 'epubcheck.jar')

                # ensure you have execute rights for unix based platforms
                if isosx or islinux:
                    os.chmod(epc_path, 0o744)
                
                # define epubcheck command line parameters
                args = ['java', '-jar', epc_path, '--version', '--usage', epub_path]
                # display message in a different language
                if locale is not None:
                    args.extend( ['--locale', locale])
                
                # run epubcheck
                result, returncode = jarWrapper(*args)
                stdout = result[0].decode(os_encoding)
                stderr = result[1].decode(os_encoding)
                stderr += stdout

        #--------------------------------------------
        # process output
        #--------------------------------------------
        if returncode != 0:
            # copy to clipboard
            if clipboard_copy:
                QApplication.clipboard().setText(stderr)
        
            # errors found; parse messages
            error_messages = []
           
            # get each line of the messages
            if iswindows:
                epc_errors = stderr.split('\r\n')
            else:
                epc_errors = stderr.split('\n')
            
            for line in epc_errors:

                #---------------------------------------------------------------
                # process only errors, warnings and info messages
                #----------------------------------------------------------------
                if line.startswith(('ERROR', 'WARNING', 'FATAL', 'INFO', 'USAGE')):

                    # replace colon after Windows drive letter with a placeholder to simplify colon based parsing
                    if iswindows:
                        drive_letter = tempfile.gettempdir()[0] + ':'
                        line = re.sub(drive_letter, 'C^',  line)
                        line = re.sub(drive_letter.lower(), 'C^',  line)

                    # split message by colons
                    err_list = line.split(':')

                    # check for colons in error message
                    if len(err_list) > 3:
                        # merge list items
                        err_list[2:len(err_list)] = [':'.join(err_list[2:len(err_list)])]
                        
                    # get error code e.g. FATAL(RSC-016) or ERROR(RSC-005) 
                    err_code = err_list[0]

                    # get message
                    msg = err_list[2].strip()

                    if iswindows:
                        msg = msg.replace('C^', drive_letter)

                    # get file name, line/column numbers
                    linenumber = None
                    colnumber = None
                    line_pos = re.search('\((-*\d+),(-*\d+)*\)', err_list[1])

                    if line_pos:
                        # get file name and line/column numbers
                        filename = re.sub('\(-*\d+,-*\d+\)', '',  err_list[1])

                        if int(line_pos.group(1)) != -1:
                            linenumber = line_pos.group(1)
                        if int(line_pos.group(2)) != -1:
                            colnumber = line_pos.group(2)
                    else:
                        # get file name only
                        filename = err_list[1]

                    # remove folder information from file name
                    if iswindows:
                        filename = os.path.basename(re.sub('C^', drive_letter, filename))
                    if isosx:
                        # suggested by wrCisco
                        filename = ".".join((filename.split('.')[-2], filename.split('.')[-1]))
                    else:
                        # Linux
                        filename = os.path.basename(filename)
                    
                    # get relative file path 
                    if filename in epub_name_to_href:
                        filepath = epub_name_to_href[filename]
                    else:
                        filepath = 'NA'

                    # assemble error message
                    message = os.path.basename(filepath)
                    if linenumber:
                        message += ' Line: ' + linenumber
                    else:
                        message += ' '
                    if colnumber:
                        message += ' Col: ' + colnumber + ' '
                    message += err_code + ': ' +  msg 
                        
                    #--------------------------------------------------------------------------------------------------------------
                    # save error information in list (filepath, line number, err_code, filename, error message)
                    #--------------------------------------------------------------------------------------------------------------
                    error_messages.append((filepath, linenumber, colnumber, err_code, message))

            if error_messages != []:
                #---------------------------------------------------------------
                # auxiliary routine for loading the file into the editor
                #---------------------------------------------------------------
                def GotoLine(item):
                    # get list item number
                    current_row = listWidget.currentRow()
                    
                    # get error information
                    filepath, line, col, err_code, message = error_messages[current_row]
                 
                    # go to the file
                    if not os.path.basename(filepath).endswith('NA'):
                        self.boss.commit_all_editors_to_container()
                        self.boss.edit_file(filepath)
                        editor = self.boss.gui.central.current_editor
                        if editor is not None and editor.has_line_numbers and line is not None:
                            if col is not None:
                                editor.editor.go_to_line(int(line), col=int(col) - 1)
                            else:
                                editor.current_line = int(line)
                        else:
                            QMessageBox.information(self.gui, "Unknown line number", "EpubCheck didn't report a line number for this error.")
                    else:
                        QMessageBox.information(self.gui, "Unknown file name", "EpubCheck didn't report the name of the file that caused this error.")

                #------------------------------------------------------------------------------------------------
                # remove existing EpubCheck/FlightCrew docks and close Check Ebook dock
                #------------------------------------------------------------------------------------------------
                for widget in self.gui.children():
                    if isinstance(widget, QDockWidget) and widget.objectName() == 'epubcheck-dock':
                        #self.gui.removeDockWidget(widget)
                        #widget.close()
                        widget.setParent(None)
                    if isinstance(widget, QDockWidget) and widget.objectName() == 'check-book-dock' and close_cb == True:
                        widget.close()
                        
                #----------------------------------
                # define dock widget layout
                #----------------------------------
                listWidget = QListWidget()
                l = QVBoxLayout()
                l.addWidget(listWidget)
                dock_widget = QDockWidget(self.gui)
                dock_widget.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
                dock_widget.setObjectName('epubcheck-dock')
                dock_widget.setWindowTitle('EpubCheck')
                dock_widget.setWidget(listWidget)
                
                #--------------------------------------------
                # add error messages to list widget
                #--------------------------------------------
                for error_msg in error_messages:
                    filename, line, col, err_code, message = error_msg
                    item = QListWidgetItem(message)
                    
                    # select background color based on severity
                    if err_code.startswith(('ERROR', 'FATAL')):
                        bg_color = QtGui.QBrush(QtGui.QColor(255, 230, 230))
                    elif err_code.startswith('WARNING'):
                        bg_color = QtGui.QBrush(QtGui.QColor(255, 255, 230))
                    else:
                        bg_color = QtGui.QBrush(QtGui.QColor(224, 255, 255))
                    item.setBackground(QtGui.QColor(bg_color))
                    listWidget.addItem(item)
                listWidget.itemClicked.connect(GotoLine)
                
                # add dock widget to the dock
                self.gui.addDockWidget(Qt.TopDockWidgetArea, dock_widget)
                
            # hide busy cursor
            QApplication.restoreOverrideCursor()
        else:
            #------------------------------------------------------------------------------------------------
            # remove existing EpubCheck/FlightCrew docks and close Check Ebook dock
            #------------------------------------------------------------------------------------------------
            for widget in self.gui.children():
                if isinstance(widget, QDockWidget) and widget.objectName() == 'epubcheck-dock':
                    #self.gui.removeDockWidget(widget)
                    #widget.close()
                    widget.setParent(None)
                if isinstance(widget, QDockWidget) and widget.objectName() == 'check-book-dock' and close_cb == True:
                    widget.close()
            
            #----------------------------------
            # define dock widget layout
            #----------------------------------
            textbox = QTextEdit()
            l = QVBoxLayout()
            l.addWidget(textbox)
            dock_widget = QDockWidget(self.gui)
            dock_widget.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
            dock_widget.setObjectName('epubcheck-dock')
            dock_widget.setWindowTitle('EpubCheck')
            textbox.setText(stdout)
            dock_widget.setWidget(textbox)
            self.gui.addDockWidget(Qt.TopDockWidgetArea, dock_widget)
        
            # hide busy cursor
            QApplication.restoreOverrideCursor()
