# -*- 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 sys
import os
import io
import traceback

from sirilic.lib.tools import init_language
from sirilic.lib.tools import mkdirs
from sirilic.lib.constantes import DSLR, RGB, IMAGE, OFFSET, DARK, FLAT, DFLAT
from sirilic.lib.constantes import NB_IODF, TYPENAME
from sirilic.lib.database import WEIGHING_NB_STARS, WEIGHING_FWHM
from sirilic.lib.database import WEIGHING_NOISE, WEIGHING_NB_IMAGES
from sirilic.lib.constantes import MASTERDIR, TEMPDIR, GROUPDIR, SRCDIR
from sirilic.lib.database import STACK_MEAN
from sirilic.lib.database import REJ_MAD
from sirilic.lib.constantes import NoLayer
from sirilic.lib.constantes import COMPATIBILITY_SIRIL

init_language()


# ==============================================================================
class CSsfWriter:
    def __init__(self):
        self.fd = None
    # --------------------------------------------------------------------------

    def write(self, chaine):
        try:
            if sys.platform.startswith('win32'):
                chaine = chaine.rstrip('\r')
            self.fd.write(chaine)
        except Exception as e:    # pylint: disable=broad-except
            print("error CSsfWriter::write() : " + str(e))

    def comment(self, text="", space=" "):
        self.write("#" + space + text + "\n")

    def command(self, cmd="", arguments=" "):
        self.write(cmd + " " + arguments + "\n")

    def hline(self, car='#', space=''):
        cnt = 80 - 1 - len(space)
        self.write("#" + space + car * cnt + "\n")

    def title(self, text, cr=0):
        # self.comment()
        self.comment(text)
        # self.comment()
        self.cr(cr)

    def cr(self, cnt=1):
        if cnt > 0:
            self.write("\n" * cnt)


# ==============================================================================
class CBasicSirilScript(CSsfWriter):
    # global counter
    _StepCounter = 0

    def __init__(self, log, sirilprefs, db_images, glob_prefs):
        self.log = log
        self.prefs = sirilprefs
        self.db = db_images
        self.props = glob_prefs

        self.extSrc = ".fit"
        self.extDest = self.extSrc
        if self.prefs.Get('compress'):
            self.extDest += ".fz"
        self.dev = self.prefs.Get('siril_dev')
        self.avancement = ""
        self.compatibility = COMPATIBILITY_SIRIL[self.dev].strip()
        self.compatibility = ".".join(self.compatibility.split('.')[0:3])
        self.quote = '"'
        self.force32bit = False
        self.imagedirabs = ""
        self.imagedir = ""
        CSsfWriter.__init__(self)
    # --------------------------------------------------------------------------

    def chdir(self, dirname=".", cmnt="", cr=0):
        if len(cmnt) > 0:
            self.comment(cmnt)

        self.write('cd ' + self.quote +
                   dirname.replace('\\', '/') + self.quote + "\n")
        self.cr(cr)

    def updir(self, cr=0):
        self.chdir("..", cr=cr)

    def copy(self, dst, src):
        self.command("load", self.quote + src + self.quote)
        self.command("save", self.quote + dst + self.quote)

    def copy_multiple(self, list_dst, src):
        self.command("load", self.quote + src + self.quote)
        for dst in list_dst:
            self.command("save", self.quote + dst + self.quote)

    def savetif(self, imagename, cond=True):
        if cond is True:
            self.command("savetif", self.quote + imagename + self.quote)

    def init_progress(self, workdir, objectname, layername, session):
        self.avancement = os.path.join(objectname, layername, session)
        self.imagedirabs = os.path.join(workdir, self.avancement)
        self.imagedir = os.path.join(".", self.avancement)
        self.avancement = self.avancement + ": "

    def progress(self, step_cur, maj=True):
        CBasicSirilScript._StepCounter = CBasicSirilScript._StepCounter + 1
        step_new = self.avancement + step_cur
        self.write("#TAG#{ [" + str(CBasicSirilScript._StepCounter) + "] " + step_new + " ...\n")
        self.comment()
        if maj:
            self.avancement = step_new + ", "

    def end_progress(self):
        self.write("#\n#TAG#} [" + str(CBasicSirilScript._StepCounter) + "]\n\n")

    def getStepNumber(self):
        return CBasicSirilScript._StepCounter

    def ClearStepNumber(self):
        CBasicSirilScript._StepCounter = 0

    def isColor(self, layername):
        return (layername == DSLR) or (layername == RGB)

    def isDuoBand(self, layername):
        return (layername == "cHaO3") or (layername == "cS2O3")

    def getDuoBandName(self, layername):
        duoband = layername[1:]
        redname = duoband + "_" + duoband[:2]
        o3name = duoband + "_" + duoband[2:]
        return (duoband, redname, o3name)

    def isO3DuoBand(self, duoband_layername):
        return duoband_layername[5:] == "O3"

    def getNolayerDuoBand(self, layername):
        return {"O3": NoLayer[layername], "RED": NoLayer[layername] - 1}

    def isBayerMatrix(self, layername):
        return (self.isColor(layername) or (layername == "cHa") or
                self.isDuoBand(layername) or (layername == "cGreen"))

    def isDeBayerised(self, layername):
        return ((layername == "dL") or (layername == "dR") or
                (layername == "dG") or (layername == "dB") or
                (layername == "dHa") or (layername == "dHb") or
                (layername == "dS2") or (layername == "dO3"))

    def isMono(self, layername):
        return ((layername == "L") or (layername == "R") or
                (layername == "G") or (layername == "B") or
                (layername == "Ha") or (layername == "Hb") or
                (layername == "S2") or (layername == "O3"))

    def isDSLR_Raw(self, layername):
        cond = ((layername == DSLR) or (layername == "cHa") or
                self.isDuoBand(layername) or (layername == "cGreen") or
                self.isDeBayerised(layername))
        return cond

    # --------------------------------------------------------------------------
    def header_file(self, nom_script, force32b):
        with io.open(nom_script, "w", newline='', encoding="utf-8") as self.fd:
            self.hline('#')
            self.title(_("Don't edit : generated by ") + sys.argv[0])
            self.title(_("Compatibility with") +
                       " Siril: Version >= " + self.compatibility)

            self.command('requires ' + self.compatibility)
            self.cr()

            self.comment(_("uncomment next line to force") +
                         " " + _("Work Directory"))
            self.write("# cd " + self.quote +
                       self.prefs.Get('workdir') + self.quote)
            self.cr(cnt=2)

            precision = '32' if self.prefs.Get('float32b') else '16'
            if force32b is True and precision == '16':
                precision = '32'
                self.comment(_("detection duoband : 32-bit"
                               " forcing for pixmath normalization"))
            self.command('set' + precision + 'bits')
            self.cr()

            if self.prefs.Get('compress'):
                self.command('setcompress',
                             '1 -type=' + self.prefs.Get('compress_type') +
                             " " + str(self.prefs.Get('compress_quantif')))
            else:
                self.command('setcompress', '0')
            self.cr()

            self.command('setext', self.extSrc[1:])
            self.cr()

            if self.prefs.Get('nbcpu') != 0:
                self.command('setcpu', str(self.prefs.Get('nbcpu')))
                self.cr()
            if self.prefs.Get('nbmem') != 0:
                self.command('setmem', str(self.prefs.Get('nbmem')))
                self.cr()

            self.command('setfindstar reset')
            self.cr()

    # --------------------------------------------------------------------------
    def option_filter(self, filt):
        filt_param = " "
        if self.check_param_filt(filt['fwhm'], 20.0):
            filt_param += "-filter-fwhm=" + filt['fwhm'] + " "
        if self.check_param_filt(filt['wfwhm'], 20.0):
            filt_param += "-filter-wfwhm=" + filt['wfwhm'] + " "
        if self.check_param_filt(filt['round'], 1.0):
            filt_param += "-filter-round=" + filt['round'] + " "
        if self.check_param_filt(filt['quality'], 100.0):
            filt_param += "-filter-quality=" + filt['quality'] + " "
        if self.check_param_filt(filt['bkg'], 100.0):
            filt_param += "-filter-bkg=" + filt['bkg'] + " "
        if self.check_param_filt(filt['nbstars'], 100.0):
            filt_param += "-filter-nbstars=" + filt['nbstars'] + " "
        if filt_param == " ":
            filt_param = ""
        return filt_param

    # --------------------------------------------------------------------------
    def mk_stack_param(self, iodf, layername):
        if not iodf["files"]:
            return ""
        stack = iodf['stack']
        stack_param = " " + stack['type'] + " "
        if stack['type'] == STACK_MEAN:
            rej = stack['reject']
            rejtype = rej['type'].lower()
            if rej['type'] == REJ_MAD:
                rejtype[0] = "a"
            stack_param += "{0} {1:5.3f} {2:5.3f} ".format(
                rejtype[0], rej['low'], rej['high'])

        weighing = stack['weighing']
        stack_param += "-weight_from_noise " if weighing == WEIGHING_NOISE else ''
        stack_param += "-weight_from_nbstack " if weighing == WEIGHING_NB_IMAGES else ''
        stack_param += "-weight_from_wfwhm " if weighing == WEIGHING_FWHM else ''
        stack_param += "-weight_from_nbstars " if weighing == WEIGHING_NB_STARS else ''

        stack_param += "-nonorm " if stack['norm'] == "no" else "-norm=" + \
            stack['norm'] + " "
        stack_param += "-fastnorm " if self.props['FastNorm'] and stack['norm'] != "no" else ""

        if iodf['type'] == IMAGE:
            stack_param += self.option_filter(stack['filter'])

        if self.isColor(layername):
            stack_param += "-rgb_equal " if stack['RGBequa'] else ""

        return stack_param

    def mk_reg_param(self, image, layername):
        register_opt = ""
        reg = image['register']
        transf = reg['transfo']
        if reg['interpo'] == "none":
            transf = "shift"
        if transf:
            register_opt += " -transf=" + transf + " "
        if reg['interpo']:
            register_opt += " -interp=" + reg['interpo'][0:2].lower() + " "

        if reg['noclamp']:
            if reg['interpo'][0:2].lower() == "cu" or reg['interpo'][0:2].lower() == "la":
                register_opt += " -noclamp "

        if reg['minpairs']:
            register_opt += " -minpairs=" + reg['minpairs'] + " "
        if reg['maxstars']:
            register_opt += " -maxstars=" + reg['maxstars'] + " "
        if reg['norot']:
            register_opt += " -norot "
        if reg['2pass']:
            register_opt += " -2pass "
        if reg['noout']:
            register_opt += " -noout "
        if reg['selected']:
            register_opt += " -selected "
        if reg['nostarslist']:
            register_opt += " -nostarslist "

        if self.isColor(layername):
            register_opt += " -layer=" + reg['layerstar'][6] + " "

        return register_opt

    def mk_seqapplyreg_param(self, image, layername):
        sreg = image['seqreg']
        if sreg['enable'] is False:
            return ''
        seqapplyreg_opt = ''

        if sreg['autoframing']:
            seqapplyreg_opt = " -framing=" + sreg['autoframing'].lower() + " "
        if sreg['interpo']:
            seqapplyreg_opt += " -interp=" + sreg['interpo'][0:2].lower() + " "

        if self.isColor(layername):
            seqapplyreg_opt += " -layer=" + sreg['layerstar'][6] + " "

        seqapplyreg_opt += self.option_filter(sreg['filter'])

        return seqapplyreg_opt

    # --------------------------------------------------------------------------
    def check_param_filt(self, value, val_max):
        if value is None:
            return False
        if value == "0%":
            return False
        if value == "100%":
            return False
        if value == "":
            return False
        if value[-1] == "%":
            if self.isFloat(value[:-1]) is False:
                return False
            nombre = float(value[:-1])
            if nombre > 100:
                return False
            if nombre < 0:
                return False
        else:
            if self.isFloat(value[:-1]) is False:
                return False
            nombre = float(value[:-1])
            if nombre < 0:
                return False
            if nombre > val_max:
                return False
        return True

    # --------------------------------------------------------------------------
    def isFloat(self, value):
        try:
            float(value)
            return True
        except ValueError:
            return False
    # --------------------------------------------------------------------------

    def platesolve(self, opt_platesolve, seqname, pcc=False):
        plate = opt_platesolve

        if plate['mode'] == 0:
            return
        if pcc is True:
            if plate['catalog'] != "nomad" and plate['catalog'] != "apass":
                plate['catalog'] = "nomad"

        options = " "
        if plate['center']:
            options += plate['center'] + " "
        if plate['noflip']:
            options += "-noflip "
        if plate['force']:
            options += "-platesolve "
        if plate['downscale']:
            options += "-downscale "
        if plate['localasnet'] and pcc is False:
            options += "-localasnet "
        if plate['limitmag']:
            options += "-limitmag=" + plate['limitmag'] + " "
        if plate['focal'] > 0:
            options += "-focal=" + str(plate['focal']) + " "
        if plate['pixelsize'] > 0.0:
            options += "-pixelsize=" + str(plate['pixelsize']) + " "
        if plate['catalog'] != 'auto':
            options += "-catalog=" + plate['catalog'] + " "

        cmd = "platesolve "
        if plate['mode'] > 1:
            cmd = "seq" + cmd + seqname
        if plate['mode'] == 1 and pcc is True:
            cmd = "pcc "

        self.command(cmd + options)
        if plate['mode'] > 1:
            return "ps_"  # TODO : prochaine version (>1.3) ce n'est plus nécessaire
        else:
            self.command("save", seqname)
            return ""

    # --------------------------------------------------------------------------
    def SetFindStar(self, image, _layername):
        findstar = image['findstar']
        if findstar['enable'] is False:
            self.command("setfindstar")
            return
        self.progress("SetFindStar", maj=False)
        options = " -sigma={0:3.1f}".format(findstar['sigma'])
        options += " -roundness={0:3.2f}".format(findstar['roundness'])
        options += " -radius={0:3.1f}".format(findstar['radius'])
        options += " -convergence={0:3.2f}".format(findstar['convergence'])
        if findstar['focal'] > 0:
            options += " -focal={0:5.1f}".format(findstar['focal'])
        if findstar['pixelsize'] > 0:
            options += " -pixelsize={0:3.2f}".format(findstar['pixelsize'])
        if findstar['solver'] == "gaussian":
            options += " -gaussian"
        else:
            options += " -moffat -minbeta={0:3.1f}".format(findstar['minbeta'])
        if findstar['relax']:
            options += " -relax=on"
        else:
            options += " -relax=off"
        options += " -minA={0:3.2f}".format(findstar['minA'])
        options += " -maxA={0:3.2f}".format(findstar['maxA'])

        self.command("setfindstar " + options)
        self.end_progress()

    # --------------------------------------------------------------------------
    def calibrate(self, cond, pp, output, offset=None, dark=None, flat=None, options=""):
        if cond:
            masters = ""
            if offset is not None:
                masters += " -bias=" + offset
            if dark is not None:
                masters += " -dark=" + dark
            if flat is not None:
                masters += " -flat=" + flat
            if ((masters != "") or (options != "")):
                self.command("calibrate", output.lower() + masters + options)
                pp = "pp_" + pp
        return pp

    # --------------------------------------------------------------------------
    def stacking(self, pp, imagename, out_imagename, stack_param):
        self.command("stack", pp + imagename.lower() +
                     stack_param + " -out=" + out_imagename)

    # --------------------------------------------------------------------------
    def subsky(self, pp, subsky, typename):
        if subsky['degree'] != 0 or subsky['rbf']:
            prop_subsky = ""
            if subsky['rbf']:
                prop_subsky = "-rbf"
                if subsky['smooth']:
                    prop_subsky += " -smooth=" + str(subsky["smooth"])
            else:
                prop_subsky = str(subsky["degree"])
            if subsky['samples']:
                prop_subsky += " -samples=" + str(subsky["samples"])
            if subsky['tolerance']:
                prop_subsky += " -tolerance=" + str(subsky["tolerance"])
            self.command("seqsubsky", pp + typename + " " + prop_subsky)
            pp = 'bkg_' + pp
        return pp

# ==============================================================================


class CScriptBuilder(CBasicSirilScript):
    def __init__(self, log, nom_script, sirilprefs, db_images, glob_prefs):
        CBasicSirilScript.__init__(
            self, log, sirilprefs, db_images, glob_prefs)
        self.script = nom_script
        self.last_processed_image = ""
        self.arbre = None

    # --------------------------------------------------------------------------
    def Build(self):
        if self.CheckInitialisedSession():
            return None
        self.ClearStepNumber()
        self.last_processed_image = ""
        self.arbre = self.db.GetArborescence()
        force32bits = False
        for vv in self.arbre.values():
            if "cHaO3" in vv.keys():
                force32bits = True
            if "cS2O3" in vv.keys():
                force32bits = True
        self.header_file(self.script, force32bits)
        self.BuildScriptPart1()
        self.Merge()
        self.BuildScriptPart2()
        return self.last_processed_image

    # --------------------------------------------------------------------------
    def CheckInitialisedSession(self):
        liste_keystr = self.db.GetKeyStrSorted()
        flag = False
        for keystr in liste_keystr:
            if self.db.Item_IsInitialised(keystr) == "uninitialized":
                self.log.insert(_("Error") + " " + keystr +
                                " " + _("uninitialized") + '\n')
                flag = True
        if flag:
            self.log.insert(
                _("Info") + " : " + _("action => intialize or delete empty sessions") + '\n')

        return flag
    # --------------------------------------------------------------------------

    def BuildScriptPart1(self):
        liste_keystr = self.db.GetKeyStrSorted()
        for keystr in liste_keystr:
            script_img = CBuildImage(
                self.log, self.prefs, self.db, self.props, keystr)
            outputs = script_img.Build(self.script)
            if outputs is not None:
                for output in outputs:
                    self.last_processed_image = output[1]
                    xx, yy, zz = self.db.Split(keystr)
                    self.arbre[xx][yy][zz].append(output[0])
    # --------------------------------------------------------------------------

    def BuildScriptPart2(self):
        script_multi = CMultisessionScript(
            self.log, self.prefs, self.db, self.props)
        last_processed_image_multi = script_multi.Build(
            self.script, self.arbre)
        if last_processed_image_multi is not None:
            self.last_processed_image = last_processed_image_multi

    # --------------------------------------------------------------------------
    def Merge(self):
        if not self.props['multisession']:
            return
        try:
            arbre = self.arbre
            if len(arbre.keys()) == 0:
                return
            # fusion ou pas ?
            fusion_flag = False
            for imgname, layers in arbre.items():
                for layername, sessions in layers.items():
                    if len(sessions) < 2:
                        continue
                    fusion_flag = True
            if not fusion_flag:
                return
            # fusion
            with io.open(self.script, "a", newline='', encoding="utf-8") as self.fd:
                self.hline('#')
                self.progress(_("Merge"))
                for imgname, layers in arbre.items():
                    for layername, sessions in layers.items():
                        if len(sessions) < 2:
                            continue
                        dirpath = os.path.join(imgname, layername, GROUPDIR)
                        mkdirs(os.path.join(self.prefs.Get('workdir'), dirpath))
                        dirpath = os.path.join(".", dirpath)
                        self.chdir(dirpath, _("Group Directory"))
                        list_file = "merge "
                        for session, param in sessions.items():
                            img = param[1] + TYPENAME[IMAGE].lower()
                            list_file = list_file + " ../" + session + \
                                "/" + TYPENAME[IMAGE] + '/' + img
                        self.command(list_file, "image_group")
                        self.chdir('../../..')
                self.end_progress()
        except Exception as e:   # pylint: disable=broad-except
            self.log.insert("***" + _("error") + " " +
                            _("merge") + " : " + str(e) + '\n')


# ==============================================================================
# CONSTRUCTION DU SCRIPT  DE PRETRAITEMENT DE L'IMAGE (DARK/OFFSET/FLAT)
class CBuildImage(CBasicSirilScript):
    def __init__(self, log, sirilprefs, db_images, glob_prefs, keystr):
        CBasicSirilScript.__init__(
            self, log, sirilprefs, db_images, glob_prefs)

        self.layer_is_multi = self.db.IsMultiSession(keystr)
        obj_iodf = self.db.GetItem(keystr)
        self.objectname, self.layername, self.session = keystr.split("/")

        self.offset = obj_iodf.GetDbIodf(OFFSET)
        self.dark = obj_iodf.GetDbIodf(DARK)
        self.darkflat = obj_iodf.GetDbIodf(DFLAT)
        self.flat = obj_iodf.GetDbIodf(FLAT)
        self.image = obj_iodf.GetDbIodf(IMAGE)

        self.bseqmode = self.prefs.Get('seqmode')
        self.bSavetif = self.prefs.Get('savetif')
        self.workdir = self.prefs.Get('workdir')
        self.multi = self.props['multisession']

        self.ena_stack = ((self.multi is False) or
                          (self.props['stack_intermed'] is True) or
                          (self.layer_is_multi is False))

        self.last_processed_image = ""
        self.pp1 = None

        self.ena = [obj_iodf.GetIodf(
            ii).IsInitialised() != '' for ii in range(NB_IODF)]
        self.enaLib = [obj_iodf.GetIodf(ii).IsLib() for ii in range(NB_IODF)]
        self.IsMultiImage = [obj_iodf.GetIodf(
            ii).IsMultiImage() for ii in range(NB_IODF)]
        self.fix_fujix = self.image['fix_fujix']

        # PRECALCUL DES VARIABLES
        dir_master = "../" + MASTERDIR
        self.master_offset = dir_master + "/master-" + \
            self.offset['typename'][:-1].lower() + self.extDest
        self.master_dark = dir_master + "/master-" + \
            self.dark['typename'][:-1].lower() + self.extDest
        self.master_darkflat = dir_master + "/master-" + \
            self.darkflat['typename'][:-1].lower() + self.extDest
        self.master_flat = dir_master + "/master-" + \
            self.flat['typename'][:-1].lower() + self.extDest

        if obj_iodf.GetIodf(OFFSET).IsSynthetic():
            self.master_offset = '"' + \
                obj_iodf.GetIodf(OFFSET).GetValueSynthetic() + '"'

        self.register_opt = self.mk_reg_param(self.image, self.layername)
        self.regdrizzle = " -drizzle " if self.image["register"]['drizzle'] else ""
        self.seqdrizzle = " -drizzle " if self.image["seqreg"]['drizzle'] else ""
        cfaequa = " -equalize_cfa " if self.props['CFAequa'] else ""
        self.cfa = " -cfa " + cfaequa if self.isColor(self.layername) else ""

        self.seqapplyreg_opt = self.mk_seqapplyreg_param(
            self.image, self.layername)

        if self.isColor(self.layername):
            self.cfa = self.cfa + " -debayer "

        if (self.layername == "cHa") or (self.layername == "cHaO3") or (self.layername == "cS2O3") or (self.layername == "cGreen"):
            self.cfa = " -cfa " + cfaequa

        self.dark_opt = " -opt " if self.image["stack"]['DarkOpt'] else ""
        self.init_progress(self.workdir, self.objectname,
                           self.layername, self.session)

    # --------------------------------------------------------------------------
    # AJOUT DANS LE SCRIPT LE PRETRAITEMENT POUR UNE IMAGE
    def Build(self, nom_script):
        try:
            with io.open(nom_script, "a", newline='', encoding="utf-8") as self.fd:
                self.header()
                self.copylib_already_processed()
                self.offset_processing()
                self.dark_processing()
                self.darkflat_processing()
                self.flat_processing()
                out = self.light_processing()
                self.chdir('../../..')
        except Exception:   # pylint: disable=broad-except
            self.log.insert(_("Error") + " : " + traceback.format_exc() + '\n')
            out = None
        return out

    # --------------------------------------------------------------------------
    def header(self):
        self.hline('#')
        self.title(_('Automating: layer ') +
                   self.layername + _(' of ') + self.session)
        self.cr()
        self.chdir(self.imagedir, _("Object Directory"), 1)

    # --------------------------------------------------------------------------
    # COPIE DES LIBRAIRIES : Resultat d'un script precedent
    def copylib_already_processed(self):
        cond = self.offset['pp']['copylib'] and self.enaLib[OFFSET] and self.ena[OFFSET]
        self.copy_lib(cond, self.offset)

        cond = self.dark['pp']['copylib'] and self.enaLib[DARK] and self.ena[DARK]
        self.copy_lib(cond, self.dark)

        cond = self.flat['pp']['copylib'] and self.enaLib[FLAT] and self.ena[FLAT]
        self.copy_lib(cond, self.flat)

        cond = self.darkflat['pp']['copylib'] and self.enaLib[DFLAT] and self.ena[DFLAT]
        self.copy_lib(cond, self.darkflat)

    # --------------------------------------------------------------------------
    def offset_processing(self):
        cond = (not self.enaLib[OFFSET]) and self.ena[OFFSET]
        if cond is False:
            return

        self.preambule(self.offset, self.IsMultiImage[OFFSET])
        stack_param = self.mk_stack_param(self.offset, self.layername)
        self.stacking("", self.offset['typename'],
                      self.master_offset, stack_param)

        self.postambule()

    # --------------------------------------------------------------------------
    def dark_processing(self):
        cond = (not self.enaLib[DARK]) and self.ena[DARK]
        if cond is False:
            return

        self.preambule(self.dark, self.IsMultiImage[DARK])

        cond_pp = (self.ena[OFFSET] and self.dark['pp']['suboffset'])
        offset_file = self.master_offset if cond_pp else None
        pp = self.calibrate(
            cond_pp, "", self.dark['typename'], offset=offset_file)

        stack_param = self.mk_stack_param(self.dark, self.layername)
        self.stacking(pp, self.dark['typename'], self.master_dark, stack_param)

        self.postambule()

    # --------------------------------------------------------------------------
    def darkflat_processing(self):
        cond = (not self.enaLib[DFLAT]) and self.ena[DFLAT]
        if cond is False:
            return

        self.preambule(self.darkflat, self.IsMultiImage[DFLAT])

        cond_pp = (self.ena[OFFSET] and self.darkflat['pp']['suboffset'])
        offset_file = self.master_offset if cond_pp else None
        pp = self.calibrate(
            cond_pp, "", self.darkflat['typename'], offset=offset_file)

        stack_param = self.mk_stack_param(self.darkflat, self.layername)
        self.stacking(pp, self.darkflat['typename'],
                      self.master_darkflat, stack_param)

        self.postambule()

    # --------------------------------------------------------------------------
    def flat_processing(self):
        cond = (not self.enaLib[FLAT]) and self.ena[FLAT]
        if cond is False:
            return

        self.preambule(self.flat, self.IsMultiImage[FLAT])

        cond = self.ena[OFFSET] and self.flat['pp']['suboffset']
        master_offset = self.master_offset if cond else None
        master_darkflat = self.master_darkflat if self.ena[DFLAT] else None
        cond = cond or self.ena[DFLAT]
        pp = self.calibrate(cond, "", self.flat['typename'],
                            offset=master_offset, dark=master_darkflat)

        stack_param = self.mk_stack_param(self.flat, self.layername)
        self.stacking(pp, self.flat['typename'], self.master_flat, stack_param)

        self.postambule()

    # --------------------------------------------------------------------------
    def light_processing(self):
        cond = (not self.enaLib[IMAGE]) and self.ena[IMAGE]
        if cond is False:
            return None

        self.preambule(self.image, self.IsMultiImage[IMAGE], _("PROCESSING"))

        options_pp = " -fix_xtrans" if self.fix_fujix else ""

        pp = ""
        master_offset = self.master_offset if self.ena[OFFSET] and self.image['pp']['suboffset'] else None
        master_dark = self.master_dark if self.ena[DARK] else None
        master_flat = self.master_flat if self.ena[FLAT] else None
        cond = ((self.cfa != "") or (self.ena[OFFSET] and
                self.image['pp']['suboffset']) or self.ena[DARK] or
                self.ena[FLAT])

        cosm = self.image['cosmetic']
        if cosm['enable'] and master_dark is not None:
            if cosm['bpm'] != '':
                options_pp = " -cc=bpm " + cosm['bpm']
            else:
                options_pp = " -cc=dark {0:5.3f} {1:5.3f}".format(
                    cosm['cold'], cosm['hot'])

        pp = self.calibrate(cond, pp, self.image['typename'],
                            offset=master_offset,
                            dark=master_dark,
                            flat=master_flat,
                            options=self.cfa + self.dark_opt + options_pp)

        self.postambule()

        output = []
        self.last_processed_image = ""
        if self.ena_stack:
            self.SetFindStar(self.image, self.layername)

            duo_name = ''
            typename = self.image['typename'].lower()
            if (self.layername == "cHa"):
                self.progress("extract Ha", maj=False)
                self.chdir(self.image['typename'])
                self.command("seqextract_Ha", pp + typename)
                self.updir()
                pp = "Ha_" + pp
                self.regdrizzle = " -drizzle "
                self.seqdrizzle = " -drizzle "
                self.end_progress()
            if (self.layername == "cGreen"):
                self.progress("extract Green", maj=False)
                self.chdir(self.image['typename'])
                self.command("seqextract_Green", pp + typename)
                self.updir()
                pp = "Green_" + pp
                self.regdrizzle = " -drizzle "
                self.seqdrizzle = " -drizzle "
                self.end_progress()
            if self.isDuoBand(self.layername):
                (duoband, redname, o3name) = self.getDuoBandName(self.layername)
                self.progress("extract " + duoband, maj=False)
                self.chdir(self.image['typename'])
                self.command("seqextract_HaOIII", pp + typename + " -resample=ha")
                self.updir()
                self.end_progress()
                self.pp1, self.last_processed_image = self.register_stack(
                    pp, redname)
                if not self.multi:
                    self.pp1 = None
                duo_name = o3name
            pp, self.last_processed_image = self.register_stack(pp, duo_name)

        self.cr()
        if not self.multi:
            pp = None
        last_processed_ext = None
        if self.last_processed_image is not None:
            last_processed_ext = self.last_processed_image + self.extDest
        output.append([pp, last_processed_ext])
        return output

    # --------------------------------------------------------------------------
    def preambule(self, iodf, IsMultiImage=False, msg=_("MASTER BUILDING")):
        name = iodf['typename'][:-1]
        self.progress(name.lower())
        self.title(name + " " + msg)
        self.chdir(iodf['typename'])
        if not IsMultiImage:
            self.convert_fit(iodf['typename'])

    # --------------------------------------------------------------------------
    def postambule(self):
        self.updir()
        self.end_progress()

    # --------------------------------------------------------------------------
    def register_stack(self, pp, duo_name):
        last_processed_image = None
        layername = self.layername
        noLayer = NoLayer[layername]
        sep = ""
        prefixOut = "../"
        if duo_name != "":
            noLayer = self.getNolayerDuoBand(layername)
            sep = "."
            prefixOut = ""
            (duoname, red_o3) = duo_name.split("_")
            layername = "c" + duoname
            if red_o3 == 'O3':
                layername += "_0002"
                pp = "OIII_" + pp
            else:
                layername += "_0001"
                pp = "Ha_" + pp

        regdrizzle = self.regdrizzle
        seqdrizzle = self.seqdrizzle

        self.progress("register" + sep + duo_name)
        self.chdir(self.image['typename'])
        typename = self.image['typename'].lower()

        pp = self.subsky(pp, self.image["subsky"], typename)

        if self.image['platesolve']['mode'] == 3:
            pp = self.platesolve(self.image['platesolve'], pp + typename) + pp

        self.command("register", pp + typename +
                     regdrizzle + self.register_opt)

        if self.seqapplyreg_opt != '':
            self.command("seqapplyreg", pp + typename +
                         seqdrizzle + self.seqapplyreg_opt)
        else:
            if self.register_opt.find('-2pass') != -1:
                self.command("seqapplyreg", pp + typename +
                             regdrizzle)  # regdrizzle dans ce cas

        if self.register_opt.find('-norot') == -1:
            pp = "r_" + pp

        if self.image['platesolve']['mode'] == 2:
            pp = self.platesolve(self.image['platesolve'], pp + typename) + pp

        self.updir()
        self.end_progress()

        self.progress("stack" + sep + duo_name)
        self.chdir(self.image['typename'])
        stack_param = self.mk_stack_param(self.image, duo_name)
        if duo_name != "":
            stack_param += " -output_norm"

        self.command("stack", pp + typename + stack_param +
                     " -out=" + prefixOut + layername)

        if self.image['platesolve']['mode'] == 1:
            self.command("load", prefixOut + layername)
            self.platesolve(
                self.image['platesolve'], prefixOut + layername,
                pcc=self.isColor(layername))

        if duo_name != "":
            self.comment(_("and flip if required"))
            self.command("mirrorx_single ", layername)
            if self.isO3DuoBand(duo_name):
                duoband = duo_name[:4]
                redname = duo_name[:2]
                self.comment(
                    _("Align the result images, small shifts and chromatic aberrations can occur"))
                self.command("register c" + duoband + " -transf=shift -interp=none")
                red = "r_c" + duoband + "_0001"  # red = S2 or Ha
                o3 = "r_c" + duoband + "_0002"
                self.command("load ", red)
                redname = duoband + "_" + redname
                self.command("save", "../c" + redname)
                self.copy_final_image("c" + redname, noLayer['RED'])
                self.comment(_("Renorm O3 to " + redname + " using PixelMath"))
                self.command("pm", " $" + o3 + "$*mad($" + red + "$)/mad($" +
                             red + "$)-mad($" + red + "$)/mad($" + red +
                             "$)*median($" + o3 + "$)+median($" + red + "$)")
                self.command("save", "../c" + duo_name)
                last_processed_image = self.copy_final_image("c" + duo_name, noLayer['O3'])
        else:
            last_processed_image = self.copy_final_image(layername, noLayer)

        self.updir()
        self.end_progress()

        return pp, last_processed_image

    # --------------------------------------------------------------------------
    def copy_final_image(self, layername, noLayer):
        last_processed_image = None
        self.comment(_("Copy") + " " + layername + " " +
                     _("to") + " C%02d" % (noLayer,))
        basedir = "../../../"
        if self.layer_is_multi is True:
            session = "-" + self.session  # evite les ecrasements
        else:
            session = ""
            last_processed_image = os.path.join(
                self.workdir, self.objectname, self.objectname + "_" + layername)

        names = (basedir + TEMPDIR + "/C%02d" % (noLayer,) + session,
                 basedir + self.objectname + "_" + layername + session)
        self.copy_multiple(src="../" + layername, list_dst=names)
        self.savetif(basedir + self.objectname + "_" +
                     layername + session, self.bSavetif)
        return last_processed_image

    # --------------------------------------------------------------------------
    def copy_lib(self, cond, lib):
        if cond:
            libname = lib['files'][0].replace('\\', '/')
            typename = lib['typename'][:-1]
            mastername = "master-" + typename.lower() + self.extDest

            self.comment(_("COPY THE") + " " + typename + " " +
                         _("LIBRARY INTO THE MASTER DIRECTORY"))
            self.chdir(MASTERDIR)
            self.copy(src=libname, dst=mastername)
            self.updir()

    # --------------------------------------------------------------------------
    # Conversion des images en fit
    def convert_fit(self, imagename):
        options = ' -out=..'
        if self.bseqmode == 1:
            options = " -fitseq" + options
        if self.bseqmode == 2:
            options = " -ser" + options
        self.command("cd " + SRCDIR)
        self.command("convert", imagename.lower() + options)
        self.updir()


# ==============================================================================
# CONSTRUCTION DU SCRIPT MULTISESSION D'UNE IMAGE ET ALIGNEMENT DES COUCHES COULEUR
class CMultisessionScript(CBasicSirilScript):
    def __init__(self, log, sirilprefs, db_images, glob_prefs):
        CBasicSirilScript.__init__(
            self, log, sirilprefs, db_images, glob_prefs)
        self.wplatesolveR = {}
        self.wsubsky = ""
        self.wplatesolve = ""
        self.register_opt = ""
        self.seqapplyreg_opt = ""

    # --------------------------------------------------------------------------
    def Build(self, nom_script, arbre):
        workdir = self.prefs.Get('workdir')
        retworkdir = self.prefs.Get('workdirreturn')
        bSavetif = self.prefs.Get('savetif')
        multisession = self.props['multisession']

        returndir = workdir

        last_processed_image = None

        with io.open(nom_script, "a", newline='', encoding="utf-8") as self.fd:
            self.hline('#')
            for objectname, layers in arbre.items():
                ccd_mono = 0
                no_align = False
                self.wplatesolveR = {'mode': 0}
                for layername, sessions in layers.items():
                    if not (self.isColor(layername)):
                        ccd_mono = ccd_mono + 1
                    if (len(sessions) != 1) and (multisession is False):
                        no_align = True
                    if (len(sessions) == 1) or (multisession is False):
                        continue
                    for values in sessions.values():
                        keystr = values[0]
                        obj_iodf = self.db.GetItem(keystr)
                        image = obj_iodf.GetDbIodf(IMAGE)
                        break

                    stack_param = self.mk_stack_param(image, layername)

                    self.wsubsky = image[r"subsky"]
                    self.wplatesolve = image["platesolve"]
                    if layername == "R":
                        self.wplatesolveR = self.wplatesolve

                    self.register_opt = self.mk_reg_param(image, layername)
                    regdrizzle = " -drizzle " if image['register']['drizzle'] else ""

                    self.seqapplyreg_opt = self.mk_seqapplyreg_param(
                        image, layername)
                    seqdrizzle = " -drizzle " if image['seqreg']['drizzle'] else ""

                    imagename = "image_group"
                    if self.prefs.Get('seqmode') == 0:
                        imagename += "_"

                    self.init_progress(workdir, objectname,
                                       layername, GROUPDIR)
                    self.hline('#')
                    self.chdir(self.imagedir, _("Image Directory"), cr=1)

                    self.SetFindStar(image, layername)

                    duo_name = ''
                    if (layername == "cHa"):
                        self.progress("extract Ha", maj=False)
                        self.command("seqextract_Ha", imagename)
                        imagename = "Ha_" + imagename
                        self.end_progress()
                    if self.isDuoBand(layername):
                        (duoband, redname, o3name) = self.getDuoBandName(layername)
                        self.progress("extract " + duoband, maj=False)
                        self.command("seqextract_HaOIII",
                                     imagename + " -resample=ha")
                        self.end_progress()
                        self.register_stack_grp(
                            objectname, layername, imagename, regdrizzle,
                            seqdrizzle, stack_param, redname)
                        duo_name = o3name
                    last_processed_image = self.register_stack_grp(
                        objectname, layername, imagename, regdrizzle,
                        seqdrizzle, stack_param, duo_name)
                    self.chdir("../../..")

                imagedir = os.path.join(".", objectname)
                self.avancement = objectname + " : "
                if (ccd_mono > 1) and (no_align is False):
                    ret = self.LRGB_compositage(
                        imagedir, objectname, layers, bSavetif)
                    if ret != "":
                        last_processed_image = ret

                returndir = imagedir

            self.avancement = "... "
            self.progress(_("Finished") + " ")

            returndir = os.path.join(".", os.path.basename(returndir))
            cmntdir = _("Object Directory")
            if retworkdir:
                returndir = workdir
                cmntdir = _("Work Directory")
            self.cr()
            self.chdir(returndir, cmntdir, cr=1)

            self.end_progress()

            if last_processed_image is not None:
                last_processed_image = last_processed_image.replace('\\', '/')
                self.comment(_("Last processed image") +
                             ":" + last_processed_image)

            self.command('setfindstar reset')
            self.cr()

            self.command("close")
            return last_processed_image

    def LRGB_compositage(self, imagedir, objectname, layers, bSavetif):
        last_processed_image = ""
        self.chdir(imagedir + "/" + TEMPDIR, _("Object Directory"), cr=1)

        mono = False
        color_filtred = False
        for layername in layers.keys():
            noLayer = NoLayer[layername]
            if noLayer >= 0:
                mono = True
            if noLayer < -2:
                color_filtred = True

        if mono is True:
            self.comment(_("Register CCD Layers"))
            self.progress("Register CCD Layers")
            self.command("register", "C -2pass")
            self.command("seqapplyreg", "C")

        if color_filtred is True:
            self.comment(_("Register OSC Layers"))
            self.progress("Register OSC Layers")
            self.command("register", "C- -2pass")
            self.command("seqapplyreg", "C-")

        for layername in layers.keys():
            last_processed_image = "final_" + objectname + "_" + layername
            noLayer = NoLayer[layername]
            if self.isDuoBand(layername):
                last_processed_image = last_processed_image + '_'
                duoband = layername[1:]
                redname = last_processed_image + duoband[:2]
                o3name = last_processed_image + duoband[2:]
                self.copy(src="r_C%2d" % (noLayer - 1,),
                          dst="../" + redname)
                self.savetif("../" + redname, bSavetif)

                self.copy(src="r_C%2d" % (noLayer,),
                          dst="../" + o3name)
                self.savetif("../" + o3name, bSavetif)

                last_processed_image = os.path.join(
                    imagedir, o3name + self.extDest)
            else:
                self.copy(src="r_C%02d" % (noLayer,),
                          dst="../" + last_processed_image)
                self.savetif("../" + last_processed_image, bSavetif)
                last_processed_image = os.path.join(
                    imagedir, last_processed_image + self.extDest)

        self.end_progress()

        LRGB = [False] * 4
        for layername in layers.keys():
            noLayer = NoLayer[layername]
            if noLayer < 0 or noLayer > 3:
                continue
            LRGB[noLayer] = True

        if LRGB[1] and LRGB[2] and LRGB[3]:
            self.comment(_("RGB/LRGB compositing"))
            self.progress("RGB compositing")
            self.comment(_("RGB compositing") + "...")

            param = " 0 0.92"
            self.command("load", "r_C02")
            self.command("linear_match", "r_C01" + param)
            self.command("save", "mr_C02")
            self.command("load", "r_C03")
            self.command("linear_match", "r_C01" + param)
            self.command("save", "mr_C03")
            self.command("load", "r_C01")
            self.command("save", "mr_C01")

            last_processed_image = "final_" + objectname + "_RGB"
            self.command(
                "rgbcomp", "mr_C01 mr_C02 mr_C03 -out=../" + last_processed_image)
            if self.wplatesolveR['mode'] >= 1:
                self.command("load", "../" + last_processed_image)
                self.platesolve(self.wplatesolveR, "../" +
                                last_processed_image, pcc=True)

            if LRGB[0]:
                self.comment(_("LRGB compositing") + "...")
                last_processed_image = "final_" + objectname + "_LRGB"
                self.command(
                    "rgbcomp", "-lum=r_C00 mr_C01 mr_C02 mr_C03 -out=../" + last_processed_image)
                if self.wplatesolveR['mode'] >= 1:
                    self.command("load", "../" + last_processed_image)
                    self.platesolve(self.wplatesolveR, "../" +
                                    last_processed_image, pcc=True)

            self.end_progress()
            last_processed_image = os.path.join(
                imagedir, last_processed_image + self.extDest)

        self.chdir("../..")
        return last_processed_image

    # --------------------------------------------------------------------------
    def register_stack_grp(self, objectname, layername, imagename, regdrizzle, seqdrizzle, stack_param, duo_name):
        last_processed_image = None
        sep = ""
        if duo_name != "":
            sep = "."
        self.progress("register" + sep + duo_name)
        pp = ''

        pp = self.subsky(pp, self.wsubsky, imagename)

        stackname = layername
        prefixOut = "../"
        if duo_name != '':
            stack_param += " -output_norm"
            prefixOut = ""
            (duoname, red_o3) = duo_name.split("_")
            stackname = "cGroup_" + duoname
            if red_o3 == 'O3':
                stackname += "_0002"
                pp = "OIII_" + pp
            else:
                stackname += "_0001"
                pp = "Ha_" + pp

        if self.wplatesolve['mode'] == 3:
            pp = self.platesolve(self.wplatesolve, pp + imagename) + pp

        self.command("register", pp + imagename +
                     regdrizzle + self.register_opt)

        if self.seqapplyreg_opt != '':
            self.command("seqapplyreg", pp + imagename +
                         seqdrizzle + self.seqapplyreg_opt)
        else:
            if self.register_opt.find('-2pass') != -1:
                self.command("seqapplyreg", pp + imagename +
                             regdrizzle)  # regdrizzle dans ce cas

        if self.register_opt.find('-norot') == -1:
            pp = "r_" + pp

        if self.wplatesolve['mode'] == 2:
            pp = self.platesolve(self.wplatesolve, pp + imagename) + pp

        self.end_progress()

        self.progress("stack" + sep + duo_name)
        noLayer = NoLayer[layername]

        self.command("stack", pp + imagename + stack_param +
                     " -out=" + prefixOut + stackname)

        if self.wplatesolve['mode'] == 1:
            self.command("load", prefixOut + stackname)
            self.platesolve(self.wplatesolve, prefixOut +
                            stackname, pcc=self.isColor(layername))

        if duo_name != "":
            self.comment(_("and flip if required"))
            self.command("mirrorx_single ", stackname)
            if self.isO3DuoBand(duo_name):
                noLayer = self.getNolayerDuoBand(layername)
                duoband = duo_name[:4]
                redname = duo_name[:2]
                self.comment(
                    _("Align the result images, small shifts and chromatic aberrations can occur"))
                self.command("register cGroup_" + duoband + " -transf=shift -interp=none")
                red = "r_cGroup_" + duoband + "_0001"  # red = Ha or S2
                o3 = "r_cGroup_" + duoband + "_0002"
                self.command("load ", red)
                redname = duoband + "-" + redname
                self.command("save", "../c" + redname)
                self.copy_final_image(objectname, "c" + redname, noLayer['RED'])
                self.comment(_("Renorm O3 to " + redname + " using PixelMath"))
                self.command("pm", " $" + o3 + "$*mad($" + red + "$)/mad($" +
                             red + "$)-mad($" + red + "$)/mad($" + o3 +
                             "$)*median($" + o3 + "$)+median($" + red + "$)")
                self.command("save", "../c" + duo_name)
                last_processed_image = self.copy_final_image(
                    objectname, "c" + duo_name, noLayer['O3'])
        else:
            last_processed_image = self.copy_final_image(
                objectname, layername, noLayer)

        self.end_progress()
        return last_processed_image

    def copy_final_image(self, objectname, stackname, noLayer):
        self.comment(_("Copy") + " " + stackname + " " +
                     _("to") + " C%02d" % (noLayer,))
        basedir = "../../"
        names = (basedir + objectname + "_" + stackname,
                 basedir + TEMPDIR + "/C%02d" % (noLayer,))
        self.copy_multiple(src="../" + stackname, list_dst=names)
        last_processed_image = os.path.join(
            self.imagedir, names[0] + self.extDest)
        return last_processed_image
