# -*- coding: UTF-8 -*-
# ==============================================================================

# ------------------------------------------------------------------------------
# Project: pysiril ( Python SiriL )
#
# This is a library to interface python to Siril.
# ------------------------------------------------------------------------------
#    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"
# ------------------------------------------------------------------------------
from __future__ import absolute_import
__all__=["Addons"]
import sys
import os
import stat
import re
import time
import io
import glob
import subprocess
import traceback
import shutil

path_exec=os.path.dirname(  __file__ )

if not path_exec :
    path_exec='.'
sys.path.append(path_exec + os.sep + 'lib')

# ------------------------------------------------------------------------------
class Addons:
    def __init__(self, sirilpipe ):

        self.siril       = sirilpipe
        self.tr          = self.siril.tr

    # --------------------------------------------------------------------------
    def CreateSeqFile( self, filename, nb_images, start=1 ) :
        """ Create a sequence file

        Parameters:

            - filename : full filename of sequence

            - nb_images : images number of sequence

            - start     : start index of first image

        Return : None
        """
        sequence_name=os.path.basename(os.path.splitext(filename)[0])

        with io.open(filename, 'w', newline='') as fd :
            fd.write('#Siril sequence file. Contains list of files (images), selection, and registration data\n')
            fd.write("#S 'sequence_name' start_index nb_images nb_selected fixed_len reference_image version\n")
            fd.write("S '" + sequence_name + "' " + str(start) + " " + str(nb_images) + " " + str(nb_images) + " 5 -1 1\n")
            fd.write("L -1\n")
            for num in range(0, nb_images):
                fd.write("I " + str(start+num) + " 1\n")

    # --------------------------------------------------------------------------
    def GetSeqType(self, seqname ):
        """ Get sequence type

        Parameters:

            - seqname : full filename of sequence

        Return : "Error", "Avi", "Ser", "Fitseq", "Fit"
        """
        with io.open(seqname, 'r', newline='') as fd :
            lines= fd.readlines()
            for line in lines :
                if line[0] == "#" : continue
                if line[0] == "S" : continue
                if line[0] == "T" :
                    if line[1] == "S" : return  "Ser"
                    if line[1] == "A" : return  "Avi"
                    if line[1] == "F" : return  "Fitseq"
                return "Fit"
        return "Error"

    # --------------------------------------------------------------------------
    def GetSeqFile(self, seqname ):
        """ Get sequence type

        Parameters:

            - seqname : full filename of sequence

        Return : Struct of  sequence  or None
        """
        seq={}
        with io.open(seqname, 'r', newline='') as fd :
            lines = fd.readlines()
            nblines = len(lines)
            cnt=2
            if nblines < 3     : return None
            if lines[0][0] != "#" : return None
            if lines[1][0] != "#" : return None
            if lines[cnt][0] != "S" : return None
            try :
                SS,sequence_name, start_index, nb_images, nb_selected, fixed_len, reference_image, version = lines[cnt].split()
                seq["sequence_name"]   = sequence_name
                seq["start_index"  ]   = int(start_index)
                seq["nb_images"    ]   = int(nb_images)
                seq["nb_selected"  ]   = int(nb_selected)
                seq["fixed_len"    ]   = int(fixed_len)
                seq["reference_image"] = int(reference_image)
                seq["version"]         = version.strip()
                seq["sequence_type"]   = "Fit"
                cnt+=1
                if lines[cnt][0] == "T" :
                    if lines[cnt][1] == "S" : seq["sequence_type"] =  "Ser"
                    if lines[cnt][1] == "A" : seq["sequence_type"] =  "Avi"
                    if lines[cnt][1] == "F" : seq["sequence_type"] =  "Fitseq"
                    cnt += 1
                if lines[cnt][0] == "U" :
                    numstr = lines[cnt].split()[1].strip()
                    seq["upscale_at_stacking"] = float(numstr)
                    cnt += 1

                if lines[cnt][0] == "L" :
                    numstr = lines[cnt].split()[1].strip()
                    seq["nb_layers"] = int(numstr)
                    cnt += 1

                img=[]
                for ii in range( seq["nb_images" ] ):
                    if lines[cnt][0] != "I" :
                        return None
                    II, numstr1,numstr2 = lines[cnt].split()
                    img.append({ "filenum" :int(numstr1), "incl" : int(numstr2) })
                    cnt += 1
                seq["images"] = img

                seq["reg"] = {}
                while cnt < nblines and lines[cnt][0] == "R"  :
                    name= lines[cnt][0:2]
                    regparams=[]
                    for ii in range( seq["nb_images" ] ):
                        layername,shiftx,shifty,fwhm,weighted_fwhm,roundness,quality=lines[cnt].strip().split()
                        if name  !=  layername :
                            Exception("layer is not coherent")
                        regparam = {}
                        regparam["shiftx"]        = float(shiftx)
                        regparam["shifty"]        = float(shifty)
                        regparam["fwhm"]          = float(fwhm)
                        regparam["weighted_fwhm"] = float(weighted_fwhm)
                        regparam["roundness"]     = float(roundness)
                        regparam["quality"]       = float(quality)
                        regparams.append(regparam)
                    seq["reg"][name] = regparams
                    cnt += 1

                seq["stat"] = {}
                while cnt < nblines and lines[cnt][0] == "M"  :
                    name= lines[cnt][0:2]
                    stats=[]
                    for ii in range( seq["nb_images" ] ):
                        (layername,total,ngoodpix,mean,median,sigma,avgDev,
                        mad,sqrtbwmv,location,scale,min,max,normValue,bgnoise)=lines[cnt].strip().split()
                        if name  !=  layername.split('-')[0] :
                            Exception("layer is not coherent")
                        if ii  !=  layername.split('-')[1] :
                            Exception("number is not coherent")
                        imgstat = {}
                        imgstat["total"]        = int(total)
                        imgstat["ngoodpix"]     = int(ngoodpix)
                        imgstat["mean"]         = float(mean)
                        imgstat["median"]       = float(median)
                        imgstat["sigma"]        = float(sigma)
                        imgstat["avgDev"]       = float(avgDev)
                        imgstat["mad"]          = float(mad)
                        imgstat["sqrtbwmv"]     = float(sqrtbwmv)
                        imgstat["location"]     = float(location)
                        imgstat["scale"]        = float(scale)
                        imgstat["min"]          = float(min)
                        imgstat["max"]          = float(max)
                        imgstat["normValue"]    = float(normValue)
                        imgstat["bgnoise"]      = float(bgnoise)
                        stats.append(imgstat)
                    seq["stat"][name] = stats
                    cnt += 1

            except Exception as e :
                print("\n**** ERROR *** " +  str(e) + "\n" )
                print(traceback.format_exc())
                return None
        return seq

    # --------------------------------------------------------------------------
    def CopyLink(self,src, dst, bCopyMode=False):
        """ Copy or Link

        Parameters:

            - src : source file

            - dst : destination file

            - bCopyMode : True = Copy mode / False = Link mode

        Return: True = Success / False = Aborted
        """
        if src == dst :
            self.tr.warning(_('Skip') + ' ' + src + " == " + dst + '\n')
            return True

        if  bCopyMode == True :
            TypeAction=_('Copy')
        else:
            TypeAction=_('Link')

        self.tr.info( TypeAction + ' ' + src + " -> " + dst + '\n')
        try:
            if os.path.exists(dst) :
                os.remove(dst)
            if bCopyMode == True :
                shutil.copy2(src, dst )
            else:
                if sys.platform.startswith('win'):
                    subprocess.check_call('mklink "%s" "%s"' % ( dst, src ), shell=True)
                else:
                    os.symlink( src , dst )
        except Exception as e :
            self.tr.error(_("... aborted")+"\n")
            self.tr.error("*** Addons::CopyLink() "+ str(e)+"\n")
            if sys.platform.startswith('win') and bCopyMode == False :
                self.tr.error("***" + _("Symbolic Link Error on Windows: "))
                self.tr.error(_('Check if the "Developer Mode" option is enabled')+"\n")
            return False
        return True

    # --------------------------------------------------------------------------
    def GetFilesExpanded(self, list_files = None) :
        """ Expand the files list

            example: ./*.fit replace with all fit file of current folder

        Parameters:

            - list_files : the files list to expand

        Return: expanded files list
        """
        list_files=self.ConvPathSeparator(list_files)
        if list_files is None :
            return []
        if type(list_files) is str :
             list_files=[list_files]

        liste_expanded=[]
        for xx in list_files :
            yy=glob.glob( xx )
            for zz in yy:
                liste_expanded.append(zz)
        liste_expanded = set(liste_expanded)
        return list(liste_expanded)

    # --------------------------------------------------------------------------
    def GetNbFiles(self, list_files ) :
        """ Expand the files list and count the file number

            example: ./*.fit replace with all fit file of current folder

        Parameters:

            - list_files : the files list to count

        Return: file number
        """
        liste_expanded = self.GetFilesExpanded(list_files)
        return len(liste_expanded)

    # --------------------------------------------------------------------------
    def CheckExtFile(self,list_source_files):
        """ Check the extension coherence of files list

        Parameters:

            - list_source_files : files list to check

        Return: None if incoherent extension else the extension file
        """
        ext0 = os.path.splitext(list_source_files[0])[1].lower()
        for ff in list_source_files :
            ext = os.path.splitext(ff)[1].lower()
            if ext0 != ext :
                self.tr.error(list_source_files)
                self.tr.error( "inchoherent extension: " + ext0 + " !=" +ext )
                return None
        return ext0

    # --------------------------------------------------------------------------
    def CheckOverwrite(self,file):
        """ Check if the file exists and ask if you want overwrite

        Parameters:

            - file : file to check

        Return: True = not overwrite / False = overwrite
        """
        cr=None
        if os.path.exists(file) :
            time.sleep(1)
            self.tr.warning( _( "Are you sure you want to overwrite image files? ") + _("yes") + "/" + _("no") +" " )
            value=input()
            if value.lower()[0] != "y" :
                self.tr.error(_("... aborted")+"\n")
                cr=True
            else:
                cr=False
        return cr

    # --------------------------------------------------------------------------
    def NumberImages(self,list_source_files, directory_destination, basename, start=1,
                     extDest=".fit", bCopyMode=False, bOverwrite = False, preserveorder=False):
        """ Number images

        Parameters:

            - list_source_files     : files list to number

            - directory_destination : folder destination

            - basename              : basename of numbered images

            - start                 : number of first image ( default=1)

            - extDest               : extension of destination file (only for fit file)

            - bCopyMode             : True = Copy mode / False = Link mode (default)

            - bOverwrite            : True = check overwite / False = no check (default)

            - preserveorder         : False = sort the files by creation data / True = keep the list ordered (if a list is passed)

        Return: True = Success / False = Abort
        """

        list_source_files     = self.ConvPathSeparator(list_source_files)
        directory_destination = self.ConvPathSeparator(directory_destination)

        counter=start
        fmt="{0:05d}"
        if type(list_source_files) is str :
            list_source_files = [ list_source_files ]
        prefixDestFile = os.path.join(directory_destination,basename)
        if not preserveorder:
            srcfiles = self.GetFilesExpanded(list_source_files)
        else:
            srcfiles = list_source_files
        srcExt   = self.CheckExtFile(srcfiles)
        if srcExt == None :
            self.tr.error(_("incoherent extension") +" ... " + _("skipping"))
            return 0

        # Gestion des RAW APN
        if self.IsFitExt(srcExt) == False :
            # on conserve l'extension dans le cas des raw
            extDest =  srcExt

        # Tri par date de creation
        if not preserveorder:
            srcfiles.sort(key=os.path.getctime)
        cnt_file=0
        for srcfile in srcfiles :
            numero = fmt.format(counter)
            destfile = prefixDestFile + numero + extDest
            if bOverwrite == True :
                cr =self.CheckOverwrite( destfile)
                if cr == None :
                    pass
                elif cr == False :
                    bOverwrite = False
                else:
                    return 0
            if not self.CopyLink( srcfile, destfile, bCopyMode) :
                return 0
            counter=counter+1
            cnt_file = cnt_file + 1

        self.CreateSeqFile(prefixDestFile + ".seq" , cnt_file , start = start )
        return cnt_file

    # --------------------------------------------------------------------------
    def Path_conv(self, path ):
        """ Normalize a pathname by collapsing redundant separators and up-level references """
        return os.path.normpath( path )

    # --------------------------------------------------------------------------
    def MkDirs(self,name):
        """ Normalize a folder pathname and create the folders if not exist """
        path= self.Path_conv(name)
        if not os.path.exists(path):
            os.makedirs(path)

    def ConvPathSeparator(self, path ):
        if path is None :
            return []
        liste_conv=[]
        sep1='/'
        sep2='\\'
        if os.sep == '/' :
            sep2='/'
            sep1='\\'
        if type(path) is str :
            return path.replace(sep1,sep2)
        path_conv=[]
        for xx in path :
            path_conv.append(xx.replace(sep1,sep2))
        return path_conv
    # --------------------------------------------------------------------------
    def IsFitExt(self,extension ):
        """ Check if it's fit extension

        Return: True = Success / False = Abort
        """
        extension = extension.lower()
        return (extension == ".fit" ) or  (extension == ".fts")  or (extension == ".fits")

    # --------------------------------------------------------------------------
    def CleanFolder(self,folder, ext_list=None , prefix=None) :
        """ Clean the folder of intermediate fit files
        Parameters:

            - ext_list     : extension list of deleted file (defaul=[ ".fit",".fits", ".fts",".seq"] )

            - prefix       : prefix of deleted file ( default: None )

        Return: None
        """
        self.tr.info(_('Cleaning : ')  + folder)

        if not os.path.exists( folder ):
            self.tr.info(_("directory doesn't exist")+" ... "+_("skipping"))
            return None

        if ext_list == None :
            ext_list=[ ".fit",".fits", ".fts",".seq"]

        if prefix == None :
            prefix=[""]

        for pp in prefix:
            for ext in ext_list :
                self.remove_file( folder + os.sep + pp +  "*" + ext )

    # --------------------------------------------------------------------------
    def remove_file( self,pattern ):
        """ Remove all files given by pattern
        Return: None
        """
        liste=glob.glob( pattern )
        liste.sort()
        for fic in liste:
            cmd= "Remove "+ fic
            self.tr.info(cmd + '\n')
            try:
                # fix : symbolic link
                if os.path.islink(fic):
                    os.unlink(fic)
                else:
                    os.remove(fic)
            except Exception as e :
                self.tr.warning("... "+ _("aborted"))
                self.tr.warning("*** Addons::remove_file() " + str(e)+"\n")

    # ---------------------------------------------------------------------------
    def GetSirilPrefs(self) :
        """ Read Siril configuration file and returns its contents

        Parameters: None

        Return : a dictionnary containing the different blocks of config file as nested dictionnaries

        """
        if sys.platform.startswith('win32'):
            prefpath=os.path.join(os.getenv('LOCALAPPDATA'),'Siril')
        if sys.platform.startswith('darwin') :
            prefpath="/Library/Application Support/Siril"
        if sys.platform.startswith('linux') :
            prefpath=os.path.join(os.environ["HOME"], ".config/siril")

        settings={}

        cfgfile=os.path.join(prefpath,'siril.config')
        if not(os.path.isfile(cfgfile)): #in case of version 0.9.12
            cfgfile=os.path.join(prefpath,'siril.cfg')
            if not(os.path.isfile(cfgfile)):
                self.tr.warning("*** Addons::GetSirilPrefs() - Could not locate Siril configuration file\n")
                return settings


        try:
            with open(cfgfile,'r') as f:
                lines = list(line for line in (l.strip() for l in f) if line)

            test=''.join(lines)
            sets = re.findall(';(.*?\{.*?})', test)
            for s in sets:
                grp,rest=s.split(':',1)
                grp=grp.strip()
                vals=re.findall('(.*?);', rest.replace('{',''))
                res={}
                for v in vals:
                    k,z=v.replace(' ','').split('=',1)
                    res[k]=z
                settings[grp]=res

            for l in lines: #in case it is not the first line
                if l.startswith('working-directory'):
                    l=l.replace(';','')
                    k,z=l.replace(' ','').split('=',1)
                    z=z.replace('\\\\','\\') #special case for Windows
                    settings[k]=z
                    break

        except Exception as e :
            self.tr.warning("*** Addons::GetSirilPrefs() " + str(e)+"\n")

        return settings

    # ---------------------------------------------------------------------------
    def ReadFITSHeader(self,filename,dbg=False) :
        """ Read and returns the FITS Header as a dictionnary

        Parameters:

            - filename  : the path to the fits file which header is required

            - dbg       : flag to print the contents of the dictionnary (default= False)

        Return : a dictionnary containing the keys of FITS header (all values as str) + additional filename field

        """
        HDLEN=36
        BLOCKLEN=80
        fitsheader={}

        _,ext=os.path.splitext(filename)
        if not (self.IsFitExt(ext)):
                self.tr.warning("*** Addons::ReadFITSHeader() - The specified file {:s} does not have a valid FITS extension\n".format(filename))
                return fitsheader

        def readblock(fp,i,imax,outdict):
            foundEND=False
            while i<imax:
                test=fp.read(BLOCKLEN).decode("utf-8", "strict")
                if '=' in test:
                    head,val=test.split('=')
                    try:
                        val,_=val.rsplit('/',1)
                    except:
                        pass
                    val=val.replace('\'','')
                    val=val.replace('/','-')
                    outdict[head.strip()]=val.strip()
                if test.startswith('END'):
                    foundEND=True
                    return outdict,foundEND
                i+=1
            return outdict,foundEND


        fitsheader['filename']=filename
        with open(filename,'rb') as fits:
            n=0
            foundEND=False
            while not foundEND:
                fitsheader,foundEND=readblock(fits,n,HDLEN*(n+1),fitsheader)
                if fitsheader['NAXIS']=='0':
                    foundEND=False
                n+=1
        if dbg:
            for k in fitsheader.keys():
                print(k+' = '+fitsheader[k])

        return fitsheader


