# -*- coding: UTF-8 -*-
'''
================================================================================
Project: SiriL-ic ( SiriL Image Companion )

This script structures the SiriL work folder into a subfolder, copies the
astronomical images into the subfolders, and builds the associated SiriL
script. It can also group scripts.
================================================================================
   Author:  M27trognondepomme <pebe92 (at) gmail.com>

This program is provided without any guarantee.

The license is  LGPL-v3
For details, see GNU General Public License, version 3 or later.
                       "https://www.gnu.org/licenses/gpl.html"
================================================================================
'''
import os
import sys
import re
import subprocess
import threading
import ctypes
import locale

from sirilic.lib import tools, constantes
from ast import literal_eval


# ==============================================================================
def get_siril_cli_exe(siril_exe):
    if siril_exe.find('siril-cli') == -1:
        if sys.platform.startswith('darwin'):
            siril_exe = siril_exe.replace('MacOS/siril', 'Resources/bin/siril-cli')
        else:
            siril_exe = siril_exe.replace('siril', 'siril-cli')
    return siril_exe


# ==============================================================================
def get_siril_gui_exe(siril_exe):
    if sys.platform.startswith('darwin'):
        siril_exe = siril_exe.replace('Resources/bin/siril-cli', 'MacOS/siril')
    else:
        siril_exe = siril_exe.replace('siril-cli', 'siril')
    return siril_exe


# ==============================================================================
class SirilScript(threading.Thread):
    def __init__(self, log, siril_exe, script, last_processed_image,
                 bRunSiril, bDev):
        threading.Thread.__init__(self, name="ThSiril")
        siril_exe = get_siril_cli_exe(siril_exe)
        self.param = (log, siril_exe, script,
                      last_processed_image, bRunSiril, bDev)
        self.memolog = []

    # --------------------------------------------------------------------------
    def run(self):
        (log, siril_exe, script,
         last_processed_image, bRunSiril, bDev) = self.param

        if check_exe(log, siril_exe):
            return

        task = tools.GestionThread()
        task.SetRunning(self)

        if self.check_compatibility(log, siril_exe, bDev):
            return

        workdir = os.path.dirname(os.path.dirname(script))
        os.chdir(workdir)
        script_file = os.path.join(".", os.path.dirname(
            script), os.path.basename(script))
        script_cmd = [siril_exe, '-d', workdir, '-s', script_file]

        self.memolog = []
        if self.exec_command(log, script_cmd, cwd=workdir):
            log.AbortMsg()
            return

        self.resume(log)
        log.insert('\n')
        log.print_ligne('.')
        log.print_titre('.', _("SCRIPT : FINISHED"))
        log.print_ligne('.')
        log.insert('\n')
        log.SetFinishBar()

        if bRunSiril is True:
            run_alone(log, siril_exe, last_processed_image, workdir)
        log.SetFinishBar()

    # --------------------------------------------------------------------------
    def resume(self, log):
        log.print_ligne('#')
        log.insert(_('Summary') + ': \n')
        for line in self.memolog:
            display = ""
            if "log: #TAG#{ " in line:
                if not ("... Term" in line or "... Finish" in line):
                    display = line[12:]

            if "log: Total : " in line:
                display = "   * " + line[5:]

            if "log: Rejet des pixels dans le canal" in line:
                display = "   * " + line[5:]
            if "Pixel rejection in channel" in line:
                display = "   * " + line[5:]

            if "log: Estimation du bruit" in line:
                display = "   * " + line[5:]
            if "log: Background noise value" in line:
                display = "   * " + line[5:]

            if len(display) > 0:
                log.insert(display)
        log.print_ligne('#')

    # --------------------------------------------------------------------------
    def popen(self, log, command, strcwd=None):
        try:
            if sys.platform.startswith('win32'):
                SW_HIDE = 0
                info = subprocess.STARTUPINFO()
                info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                info.wShowWindow = SW_HIDE
                pipe_subprocess = subprocess.Popen(command, startupinfo=info,
                                                   stdout=subprocess.PIPE,
                                                   stderr=subprocess.PIPE,
                                                   stdin=subprocess.DEVNULL,
                                                   cwd=strcwd)
            else:
                pipe_subprocess = subprocess.Popen(command,
                                                   stdout=subprocess.PIPE,
                                                   stderr=subprocess.PIPE,
                                                   cwd=strcwd, bufsize=-1,
                                                   universal_newlines=False)
            return pipe_subprocess
        except Exception as e:   # pylint: disable=broad-except
            log.insert("*** pipe_subprocess.Popen() :" + str(e) + '\n')
            return None

    # --------------------------------------------------------------------------
    def check_compatibility(self, log, siril_exe, bDev):
        ''' verification de la compatibilite de Siril avec Sirilic
        '''
        try:
            rVers = literal_eval(
                "[" + constantes.COMPATIBILITY_SIRIL[bDev].replace('.', ',') +
                "]")
            command = [siril_exe, '--version']

            pipe_subprocess = self.popen(log, command)
            if pipe_subprocess is None:
                return True

            try:
                version = pipe_subprocess.communicate(timeout=15)[0]
            except subprocess.TimeoutExpired:
                log.insert("***" +
                           _("Timout expired : Problem to run 'Siril --version' ") +
                           '\n')
                pipe_subprocess.kill()
                return True
            except Exception as e:   # pylint: disable=broad-except
                log.insert(
                    "*** pipe_subprocess.communicate() :" + str(e) + '\n')
                return True

            # version=b'SiriL is started as MacOS application\n
            #           siril 0.99.4-19fa980\n' # pour check
            # version=b'siril 0.99.8.1-91f54865' # pour check
            version = (version.decode('utf8')).split('\n')
            found = False
            pattern = r'siril [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*'
            for vv in version:
                if re.search(pattern, vv):
                    found = True
                    version = vv.strip()
                    break
            if found is False:
                log.insert("*** Uncompatible version format :'" +
                           str(version) + "'\n")
                return True

            version_split = version.split(' ')
            # sirilname = version_split[0]
            number = version_split[1]
            log.insert("VERSION " + version + ' : ')
            (nMajor, nMinor, nRelease) = number.split('.')[0:3]
            nRelease = nRelease.split('-')
            if type(nRelease) is list:
                nRelease = nRelease[0]

            compatible = False
            if int(nMajor) > rVers[0]:
                compatible = True
            else:
                if int(nMajor) == rVers[0]:
                    if int(nMinor) > rVers[1]:
                        compatible = True
                    else:
                        if int(nMinor) == rVers[1]:
                            if int(nRelease) >= rVers[2]:
                                compatible = True

            if compatible:
                log.insert(_("Siril is compatible with Sirilic ") + '\n')
            else:
                log.insert(_("Siril is not compatible with Sirilic ") + '\n')
                log.insert(_("Sirilic requires : Siril ") + str(rVers[0]) +
                           '.' + str(rVers[1]) + '.' + str(rVers[2]) + '-' +
                           str(rVers[3]) + '\n')
                return True
        except Exception as e:   # pylint: disable=broad-except
            log.insert("*** " + _("Error: Siril don't work") +
                       " => " + siril_exe + '?\n')
            log.insert("*** " + str(e) + '\n')
            return True
        return False

    # --------------------------------------------------------------------------
    def raise_exception(self):
        if self.ident is None:
            return  # not started
        po_exit = ctypes.py_object(SystemExit)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(self.ident, po_exit)
        if res > 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(self.ident, 0)
            print('Exception raise failure')

    # --------------------------------------------------------------------------
    def exec_command(self, log, command, cwd):
        task = tools.GestionThread()
        log.insert("\n")

        pipe_subprocess = self.popen(log, command, cwd)
        if pipe_subprocess is None:
            return True

        task.SetPipe(pipe_subprocess)
        out_thread = StdoutThread(log, pipe_subprocess, self.memolog)
        err_thread = StderrThread(log, pipe_subprocess)
        err_thread.start()
        out_thread.start()
        out_thread.join()
        err_thread.join()
        # pour mettre ajour Pipe_subprocess.returncode
        pipe_subprocess.communicate()
        returncode = pipe_subprocess.returncode
        task.SetPipe(None)
        return task.IsAborted() or (returncode != 0)


# ==============================================================================
class StdoutThread(threading.Thread):
    def __init__(self, log, pipe, memolog):
        self.log = log
        self.pipe = pipe
        self.memolog = memolog
        threading.Thread.__init__(self)

    def run(self):
        pipe = self.pipe
        byte_buffer = b''
        byte = b'.'
        code = locale.getpreferredencoding()
        while (pipe.poll() is None) and (byte != b''):
            byte = pipe.stdout.read(1)
            byte_buffer = byte_buffer + byte
            if byte == b'\n':
                buffer = byte_buffer.decode(code, errors='ignore')
                buffer = buffer.rstrip() + '\n'
                if not buffer[0].isalpha():
                    buffer = buffer[9:-1]
                if buffer[:8] == "progress:"[0:8]:
                    self.log.addProgress(buffer[:-1])
                else:
                    self.log.insert(buffer)
                pos = buffer.find("log: ")
                if pos >= 0:
                    self.memolog.append(buffer[pos:])
                byte_buffer = b''


class StderrThread(threading.Thread):
    def __init__(self, log, pipe):
        self.log = log
        self.pipe = pipe
        threading.Thread.__init__(self)

    def run(self):
        pipe = self.pipe
        byte_buffer = b''
        byte = b'.'
        code = locale.getpreferredencoding()
        while (pipe.poll() is None) and (byte != b''):
            byte = pipe.stderr.read(1)
            byte_buffer = byte_buffer + byte
            if byte == b'\n':
                buffer = byte_buffer.decode(code, errors='ignore')
                buffer = buffer.rstrip()
                if buffer.find("Reading sequence failed,"
                               " file cannot be opened:") == 0:
                    buffer = "~~~ warning:" + buffer + '\n'
                elif not re.search(r'[0-9][0-9]*: running', buffer):
                    buffer = "*** " + buffer + '\n'
                else:
                    buffer = "... info: " + buffer + '\n'

                self.log.insert(buffer)
                byte_buffer = b''


# ==============================================================================
def check_exe(log, siril_exe):
    if not os.path.exists(siril_exe):
        log.insert("***" + _("Error: siril don't exist") +
                   " => " + siril_exe + '?\n')
        return True

    if not os.path.isfile(siril_exe):
        log.insert("***" + _("Error: siril isn't a file") +
                   " => " + siril_exe + '?\n')
        return True

    if not os.access(siril_exe, os.X_OK):
        log.insert("***" + _("Error: siril isn't a executable") +
                   " => " + siril_exe + '?\n')
        return True

    return False


# ==============================================================================
def run_alone(log, siril_exe, filename=None, strcwd=None):
    siril_exe = get_siril_gui_exe(siril_exe)
    if check_exe(log, siril_exe):
        return

    if (filename is not None):
        log.insert("### " + _("last processed image") +
                   " => " + filename + ' ###\n')

    log.print_titre('.', _("Run: ") + siril_exe +
                    " ... " + _("waiting a few seconds"))

    try:
        cmd = [siril_exe]
        if (filename is not None):
            if (len(filename) > 0):
                cmd.append(filename)
        subprocess.Popen(cmd, cwd=strcwd)
    except Exception as e:   # pylint: disable=broad-except
        log.insert("***" + _("Error: Siril don't work") +
                   " => " + siril_exe + '?\n')
        log.insert("***  in run_alone(), subprocess.Popen() " + str(e) + '\n')
        return
