# -*- mode:Python -*-
#
# This file is part of the Coriolis Software.
# Copyright (c) Sorbonne Université 2008-2021, All Rights Reserved
#
# +-----------------------------------------------------------------+ 
# |                   C O R I O L I S                               |
# |          T o o l c h a i n   B u i l d e r                      |
# |                                                                 |
# |  Author      :                   Jean-Paul Chaput               |
# |  E-mail      :            Jean-Paul.Chaput@lip6.fr              |
# | =============================================================== |
# |  Python      :   "./builder/Configuration.py"                   |
# +-----------------------------------------------------------------+


import sys
import re
import os
import os.path
import datetime
import subprocess
from   .        import ErrorMessage
from   .Project import Project


class Configuration ( object ):

    PrimaryNames = \
        [ 'confFile'   , 'projects', 'standalones'
        , 'gitHash'    , 'gitMethod', 'revDate'
        , 'projectDir' , 'rootDir'
        , 'packageName', 'packageVersion', 'packageExcludes', 'packageProjects'
        , 'osType'     , 'libSuffix', 'buildMode', 'libMode', 'enableShared'
        ]
    SecondaryNames = \
        [ 'rpmbuildDir' , 'debbuildDir'   , 'tmppathDir'  , 'tarballDir'
        , 'archiveDir'  , 'sourceDir'     , 'osDir'       , 'buildDir'
        , 'installDir'  , 'bootstrapDir'  , 'specFileIn'  , 'specFile'
        , 'debianDir'   , 'debChangelogIn', 'debChangelog', 'sourceTarBz2'
        , 'binaryTarBz2', 'distribPatch'
        ]

    def __init__ ( self ):
        self._confFile         = None
        self._projects         = []
        self._standalones      = []
        self._revDate          = datetime.date.today().strftime('%Y%m%d')
        self._gitHash          = "x"
        self._gitMethod        = None
        self._projectDir       = 'coriolis-2.x'
        self._rootDir          = os.path.join ( os.environ["HOME"], self._projectDir )
        self._packageName      = None
        self._packageVersion   = None
        self._packageExcludes  = []
        self._packageProject   = []
        self._enableShared     = 'ON'
        self._buildMode        = 'Release'

       # Secondary (derived) variables.
       # Setup by _updateSecondary().
        self._guessOs()
        self._updateSecondary()
        return

    def __setattr__ ( self, attribute, value ):
        if attribute in Configuration.SecondaryNames:
            print( ErrorMessage( 1, 'Attempt to write in read-only attribute "{}" in Configuration.' \
                                    .format(attribute) ))
            return
        if attribute[0] == '_':
            self.__dict__[attribute] = value
            return
        if   attribute == 'rootDir': value = os.path.expanduser(value)
        elif attribute == 'enableShared' and value != 'ON': value = 'OFF'
        self.__dict__['_'+attribute] = value
        self._updateSecondary()
        return

    def __getattr__ ( self, attribute ):
        if attribute[0] != '_': attribute = '_'+attribute
        if not attribute in self.__dict__:
            raise ErrorMessage( 1, 'Configuration has no attribute <%s>.'%attribute )
        return self.__dict__[attribute]

    def _updateSecondary ( self ):
        if self._enableShared == "ON": self._libMode = "Shared"
        else:                          self._libMode = "Static"

       #self._rootDir      = os.path.join ( os.environ["HOME"], self._projectDir )
        self._rpmbuildDir  = os.path.join ( self._rootDir    , "rpmbuild" )
        self._debbuildDir  = os.path.join ( self._rootDir    , "debbuild" )
        self._tmppathDir   = os.path.join ( self._rpmbuildDir, "tmp" )
        self._tarballDir   = os.path.join ( self._rootDir    , "tarball" )
        self._archiveDir   = os.path.join ( self._tarballDir , "%s-%s.%sgit%s" % (self._packageName
                                                                                 ,self._packageVersion
                                                                                 ,self._revDate
                                                                                 ,self._gitHash) )
        self._sourceDir    = os.path.join ( self._rootDir    , "src" )
        self._bootstrapDir = os.path.join ( self._sourceDir  , "coriolis/bootstrap" )
        self._osDir        = os.path.join ( self._rootDir
                                          , self._osType
                                          , "%s.%s" % (self._buildMode,self._libMode) )
        self._buildDir     = os.path.join ( self._osDir, "build" )
        self._installDir   = os.path.join ( self._osDir, "install" )
        self._specFileIn     = os.path.join ( self._bootstrapDir, "%s.spec.in"%self._packageName )
        self._specFile       = os.path.join ( self._bootstrapDir, "%s.spec"   %self._packageName )
        self._debianDir      = os.path.join ( self._bootstrapDir, "debian" )
        self._debChangelogIn = os.path.join ( self._debianDir   , "changelog.in" )
        self._debChangelog   = os.path.join ( self._debianDir   , "changelog" )
        self._sourceTarBz2   = "%s-%s.%sgit%s.tar.bz2"            % (self._packageName
                                                                    ,self._packageVersion
                                                                    ,self._revDate
                                                                    ,self._gitHash)
        self._binaryTarBz2   = "%s-binary-%s.%sgit%s-1.el7.tar.bz2" % (self._packageName
                                                                      ,self._packageVersion
                                                                      ,self._revDate
                                                                      ,self._gitHash)
        self._distribPatch   = os.path.join ( self._sourceDir, "bootstrap", "%s-for-distribution.patch"%self._packageName )
        return

    def _guessOs ( self ):
        self._libSuffix         = None
        self._osEL9             = re.compile (".*Linux.*(el9|al9).*x86_64.*")
        self._osSlsoc7x_64      = re.compile (".*Linux.*(el7|slsoc7).*x86_64.*")
        self._osSlsoc6x_64      = re.compile (".*Linux.*(el6|slsoc6).*x86_64.*")
        self._osSlsoc6x         = re.compile (".*Linux.*(el6|slsoc6).*")
        self._osSLSoC5x_64      = re.compile (".*Linux.*el5.*x86_64.*")
        self._osSLSoC5x         = re.compile (".*Linux.*(el5|2.6.23.13.*SoC).*")
        self._osFedora_64       = re.compile (".*Linux.*fc.*x86_64.*")
        self._osFedora          = re.compile (".*Linux.*fc.*")
        self._osLinux_64        = re.compile (".*Linux.*x86_64.*")
        self._osLinux           = re.compile (".*Linux.*")
        self._osFreeBSD8x_amd64 = re.compile (".*FreeBSD 8.*amd64.*")
        self._osFreeBSD8x_64    = re.compile (".*FreeBSD 8.*x86_64.*")
        self._osFreeBSD8x       = re.compile (".*FreeBSD 8.*")
        self._osDarwin          = re.compile (".*Darwin.*")
        self._osCygwinW7_64     = re.compile (".*CYGWIN_NT-6\.1.*x86_64.*")
        self._osCygwinW7        = re.compile (".*CYGWIN_NT-6\.1.*i686.*")
        self._osCygwinW8_64     = re.compile (".*CYGWIN_NT-6\.[2-3].*x86_64.*")
        self._osCygwinW8        = re.compile (".*CYGWIN_NT-6\.[2-3].*i686.*")
        self._osCygwinW10_64    = re.compile (".*CYGWIN_NT-10\.[0-3].*x86_64.*")
        self._osCygwinW10       = re.compile (".*CYGWIN_NT-10\.[0-3].*i686.*")

        uname  = subprocess.Popen ( ["uname", "-srm"], stdout=subprocess.PIPE )
        lines  = uname.stdout.readlines()
        osLine = lines[0].decode( 'ascii' )
        if self._osEL9.match(osLine):
            self._osType    = "Linux.el9"
            self._libSuffix = "64"
        elif self._osSlsoc7x_64.match(osLine):
            self._osType    = "Linux.el7_64"
            self._libSuffix = "64"
        elif self._osSlsoc6x_64.match(osLine):
            self._osType    = "Linux.slsoc6x_64"
            self._libSuffix = "64"
        elif self._osSlsoc6x   .match(osLine): self._osType = "Linux.slsoc6x"
        elif self._osSLSoC5x_64.match(osLine):
            self._osType    = "Linux.SLSoC5x_64"
            self._libSuffix = "64"
        elif self._osSLSoC5x   .match(osLine): self._osType = "Linux.SLSoC5x"
        elif self._osFedora_64 .match(osLine):
            self._osType    = "Linux.fc_64"
            self._libSuffix = "64"
        elif self._osFedora    .match(osLine): self._osType = "Linux.fc"
        elif self._osLinux_64  .match(osLine):
            self._osType    = "Linux.x86_64"
            if os.path.exists("/usr/lib64/"):
                self._libSuffix = "64"
        elif self._osLinux     .match(osLine): self._osType = "Linux.i386"
        elif self._osDarwin    .match(osLine): self._osType = "Darwin"
        elif self._osFreeBSD8x_amd64.match(osLine):
            self._osType    = "FreeBSD.8x.amd64"
            self._libSuffix = "64"
        elif self._osFreeBSD8x_64.match(osLine):
            self._osType    = "FreeBSD.8x.x86_64"
            self._libSuffix = "64"
        elif self._osFreeBSD8x .match(osLine): self._osType = "FreeBSD.8x.i386"
        elif self._osCygwinW7_64.match(osLine):
            self._osType = "Cygwin.W7_64"
            self._libSuffix = "64"
        elif self._osCygwinW7.match(osLine): self._osType = "Cygwin.W7"
        elif self._osCygwinW8_64.match(osLine):
            self._osType = "Cygwin.W8_64"
            self._libSuffix = "64"
        elif self._osCygwinW8.match(osLine): self._osType = "Cygwin.W8"
        elif self._osCygwinW10_64.match(osLine):
            self._osType = "Cygwin.W10_64"
            self._libSuffix = "64"
        elif self._osCygwinW10.match(osLine): self._osType = "Cygwin.W10"
        else:
            uname = subprocess.Popen ( ["uname", "-sr"], stdout=subprocess.PIPE )
            self._osType = uname.stdout.readlines()[0][:-1]

            print( '[WARNING] Unrecognized OS: "{}."'.format(osLine[:-1]) )
            print( '          (using: "{}")'.format(self._osType) )

        if self._libSuffix == '64' and not os.path.exists('/usr/lib64'):
            self._libSuffix = None
        return

    def getPrimaryIds   ( self ): return Configuration.PrimaryNames
    def getSecondaryIds ( self ): return Configuration.SecondaryNames
    def getAllIds       ( self ): return Configuration.PrimaryNames + Configuration.SecondaryNames

    def register ( self, project ):
        for registered in self._projects:
            if registered.getName() == project.getName():
                print( ErrorMessage( 0, 'Project "{}" is already registered (ignored).'.format(project.getName()) ))
                return
        self._projects += [ project ]
        return

    def getProject ( self, name ):
        for project in self._projects:
            if project.getName() == name:
                return project
        return None

    def getToolProject ( self, name ):
        for project in self._projects:
            if project.hasTool(name):
                return project
        return None

    def load ( self, confFile ):
        moduleGlobals = globals()

        if not confFile:
            print( 'Making an educated guess to locate the configuration file:' )
            locations = [ os.path.abspath(os.path.dirname(sys.argv[0]))
                        , os.environ['HOME']+'/coriolis-2.x/src/coriolis/bootstrap'
                        , os.environ['HOME']+'/coriolis/src/coriolis/bootstrap'
                        , '/users/outil/coriolis/coriolis-2.x/src/coriolis/bootstrap'
                        , self._rootDir+'/src/coriolis/bootstrap'
                        ]

            for location in locations:
                self._confFile = location + '/build.conf'
                print( '  "{}"'.format(self._confFile) )

                if os.path.isfile(self._confFile): break
            if not self._confFile:
                ErrorMessage( 1, 'Cannot locate any configuration file.' ).terminate()
        else:
            print( 'Using user-supplied configuration file:' )
            print( '  "{}"'.format(confFile) )

            self._confFile = confFile
            if not os.path.isfile(self._confFile):
                ErrorMessage( 1, 'Missing configuration file:', '<%s>'%self._confFile ).terminate()

        print( 'Reading configuration from:' )
        print( '  "{}"'.format(self._confFile) )
        try:
            exec( open(self._confFile).read(), globals() )
        except Exception as e:
            ErrorMessage( 1, 'An exception occured while loading the configuration file:'
                           , '<%s>\n' % (self._confFile)
                           , 'You should check for simple python errors in this file.'
                           , 'Error was:'
                           , '%s\n' % e ).terminate()

        if 'projects' in moduleGlobals:
            entryNb = 0
            for entry in moduleGlobals['projects']:
                entryNb += 1
                if not 'name' in entry:
                    raise ErrorMessage( 1, 'Missing project name in project entry #%d.' % entryNb )
                if not 'tools' in entry:
                    raise ErrorMessage( 1, 'Missing tools list in project entry #%d (<%s>).' \
                                           % (entryNb,entry['name']) )
                if not isinstance(entry['tools'],list):
                    raise ErrorMessage( 1, 'Tools item of project entry #%d (<%s>) is not a list.' \
                                           % (entryNb,entry['name']) )
                if not 'repository' in entry:
                    raise ErrorMessage( 1, 'Missing project repository in project entry #%d.' \
                                           % entryNb )

                self.register( Project(entry['name'],entry['tools'],entry['repository']) )
        else:
            ErrorMessage( 1, 'Configuration file is missing the "project" symbol.'
                           , '"{}"'.format(self._confFile) ).terminate()

        if 'projectdir' in moduleGlobals:
            self.projectDir = moduleGlobals['projectdir']
        if 'svnconfig' in moduleGlobals:
            svnconfig = moduleGlobals['svnconfig']
            if 'method' in svnconfig: self._svnMethod = svnconfig['method']
        if 'package' in moduleGlobals:
            package = moduleGlobals['package']
            if 'name'     in package: self.packageName    = package['name']
            if 'version'  in package: self.packageVersion = package['version']
            if 'excludes' in package:
                if not isinstance(package['excludes'],list):
                    raise ErrorMessage( 1, 'Excludes of package configuration is not a list.')
                self._packageExcludes = package['excludes']
            if 'projects' in package:
                if not isinstance(package['projects'],list):
                    raise ErrorMessage( 1, 'Projects to package is not a list.')
                self._packageProjects = package['projects']
        return

    def show ( self ):
        print( 'CCB Configuration:' )
        if self._gitMethod:
            print( '  Git Method: "{}"'.format(self._gitMethod) )
        else:
            print( '  Git Method not defined, will not be able to push/pull.' )
        for project in self._projects:
            print( '  project:{0:>15} repository:"{1}"' \
                   .format( '"{}"'.format(project.getName()), project.getRepository() ))
            toolOrder = 1
            for tool in project.getTools():
                print( '{0}{1:02}:"{2}"'.format( ' '*26, toolOrder, tool ))
                toolOrder += 1
            print
        return