''' 
 Module.py

 This file stores the generic implementation of the bake options. e.g. how 
 the download works, independently of the technology/repository used to  
 store the code.
''' 

import copy
import os
import re
import sys
import shutil

from bake.FilesystemMonitor import FilesystemMonitor
from bake.Exceptions import TaskError
from bake.Utils import ColorTool
from bake.ModuleSource import SystemDependency

class ModuleDependency:
    """ Dependency information. """
    
    def __init__(self, name, optional = False):
        self._name = name
        self._optional = optional
        
    def name(self):
        return self._name
    
    def is_optional(self):
        return self._optional

class Module:
    followOptional = None
    FAIL = 0
    OK = 1

    def __init__(self, name, 
                 source,
                 build,
                 dependencies = [],
                 built_once = False,
                 installed = []):
        self._name = name
        self._dependencies = copy.copy(dependencies)
        self._source = source
        self._build = build
        self._built_once = built_once
        self._installed = installed


    @property
    def installed(self):
        """ Returns if the module was already installed or not. """
        return self._installed
    @installed.setter
    def installed(self, value):
        """ Stores the given value on the module installed option. """
        self._installed = copy.copy(value)

    def _directory(self):
        return self._name

    def _do_download(self, env, source, name, forceDownload):
        """ Recursive download function, do the download for each 
        target module. """
        
        srcDirTmp = name
        if source.attribute('module_directory').value :
            srcDirTmp = source.attribute('module_directory').value
            
        env.start_source(name, srcDirTmp)
        rt = source.check_version(env)
        
        if forceDownload:
            try: # when forced download, removes the repository if it exists
                if os.path.isdir(env.srcdir):
                    shutil.rmtree(env.srcdir)
            except OSError as e:
                env._logger.commands.write('Could not remove source files'
                                            ' %s for module: %s \n Error: %s\n' % 
                                            (env.srcdir, env._module_name, 
                                             str(e)))
        
        if os.path.isdir(env.srcdir):
            env.end_source()
        else:
            try:
                source.download(env)
            finally:
                env.end_source()
        for child, child_name in source.children():
            self._do_download(env, child, os.path.join(name, child_name))


    def printResult(self, env, operation, result):
        """Prints the result of the operation."""
        colorTool = ColorTool()
        resultStr = "OK"
        color=colorTool.OK
        if result == self.FAIL:
            resultStr = "Problem"
            color=colorTool.FAIL
            
        if env._logger._verbose > 0:
            print
            colorTool.cPrintln(color, " >> " + operation + " " + 
                                    self._name + " - " +resultStr)
        else:
            colorTool.cPrintln(color, resultStr)

    def download(self, env, forceDownload):
        """ General download function. """
        
        if self._build.attribute('supported_os').value :
            if not self._build.check_os(self._build.attribute('supported_os').value) : 
                import platform
                osName = platform.system().lower()
                (distname,version,ids)=platform.linux_distribution()
                print('    Downloading, but this module works only on \"%s\"' 
                      ' platform(s), %s is not supported for %s %s %s %s' % 
                      (self._build.attribute('supported_os').value, 
                       self._name, platform.system(), distname,version,ids))
            
        try:
            self._do_download(env, self._source, self._name, forceDownload)
            if isinstance(self._source, SystemDependency):
                self.printResult(env, "Dependency ", self.OK)
            else:
                self.printResult(env, "Download", self.OK)
            return True
        except TaskError as e:
            if isinstance(self._source, SystemDependency):
                self.printResult(env, "Dependency ", self.FAIL)
            else:
                self.printResult(env, "Download", self.FAIL)
            env._logger.commands.write(e.reason+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()           
            return False
        except:
            if isinstance(self._source, SystemDependency):
                self.printResult(env, "Install", self.FAIL)
            else:
                self.printResult(env, "Download", self.FAIL)
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()
            return False


    def _do_update(self, env, source, name):
        """ Recursive update function, do the update for each 
        target module. """
        
        srcDirTmp = name
        if source.attribute('module_directory').value :
            srcDirTmp = source.attribute('module_directory').value
            
        env.start_source(name, srcDirTmp)
        
        try:
            source.update(env)
        finally:
            env.end_source()
        for child, child_name in source.children():
            self._do_update(env, child, os.path.join(name, child_name))

    def update(self, env):
        """ Main update function. """
        
        try:
            self._do_update(env, self._source, self._name)
            self.printResult(env, " Update ", self.OK)
            return True
        except TaskError as e:
            self.printResult(env, " Update ", self.FAIL)
            env._logger.commands.write(e.reason+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()           
            return False
        except:
            self.printResult(env, " Update ", self.FAIL)
            env._logger.commands.write(e.reason+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()
            return False
      
    def distclean(self, env):
        """ Main distclean source function, call the modules distclean. """
        
        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value

        env.start_build(self._name, srcDirTmp,
                        self._build.supports_objdir)
        if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir):
            env.end_build()
            return
        try:
            self._build.distclean(env)
            env.end_build()
            self._built_once = False
            print(" >> Distcleaning " + self._name + " - OK")
            return True
        except TaskError as e:
            print(" >> Distcleaning " + self._name + " - Problem")
            env._logger.commands.write(e.reason+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()           
            return False
        except:
            env.end_build()
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()
            return False
       
    def fullclean(self, env):
        """ Main full clean function, deletes the source and installed files. """
        
        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value
            
        env.start_build(self._name, srcDirTmp, True)
        print(" >> Removing source of " + self._name + ": " + env.srcdir)
        try: 
            shutil.rmtree(env.srcdir)
            print(" >> Source removed - OK ")
        except Exception as e:
            err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
            env._logger.commands.write("    > " + err +'\n')
#            print (e)
            pass

        if os.path.isdir(env.objdir):
            print(" >> Removing build: " + env.objdir)
            try: 
                shutil.rmtree(env.objdir)
                print(" >> Build removed - OK ")
            except Exception as e:
                err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
                env._logger.commands.write("    > " + err +'\n')
 
        if os.path.isdir(env.installdir):
            print(" >> Removing installation: " + env.installdir)
            try: 
                shutil.rmtree(env.installdir)
                print(" >> Installation removed - OK ")
            except Exception as e:
                err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
                env._logger.commands.write("    > " + err +'\n')
           
        return True
         

    def uninstall(self, env):
        """ Main uninstall function, deletes the installed files. """
         
        for installed in self._installed:
            try:
                os.remove(installed)
            except OSError:
                pass
            
        # delete directories where files were installed if they are empty
        dirs = [os.path.dirname(installed) for installed in self._installed]
        def uniq(seq):
            keys = {}
            for e in seq:
                keys[e] = 1
            return keys.keys()
        for d in uniq(dirs):
            try:
                os.removedirs(d)
            except OSError:
                pass
        self._installed = []

    def build(self, env, jobs, force_clean):
        """ Main build function. """
        
        # if there is no build we do not need to proceed 
        if self._build.name() == 'none' or self._source.name() == 'system_dependency':
            return True
        
        # delete in case this is a new build configuration
        # and there are old files around
        if force_clean:
            self.uninstall(env)
            if not self._built_once:
                self.clean(env)

        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value
            
        env.start_build(self._name, srcDirTmp,
                        self._build.supports_objdir)
        
        # setup the monitor
        monitor = FilesystemMonitor(env.installdir)
        monitor.start()

        if self._build.attribute('supported_os').value :
            if not self._build.check_os(self._build.attribute('supported_os').value) : 
                import platform
                osName = platform.system().lower()
                (distname,version,ids)=platform.linux_distribution()
                self.printResult(env, "Building", self.FAIL)
                print('    This module works only on \"%s\"' 
                      ' platform(s), %s is not supported for %s %s %s %s' % 
                      (self._build.attribute('supported_os').value, 
                       self._name, platform.system(), distname,version,ids))
                return

        if not os.path.isdir(env.installdir):
            os.mkdir(env.installdir)
        if self._build.supports_objdir and not os.path.isdir(env.objdir):
            os.mkdir(env.objdir)

        try:
            if not os.path.isdir(env.srcdir):
                raise TaskError('Source is not available for module %s: '
                            'directory %s not found.  Try %s download first.' % 
                            (env._module_name,env.srcdir, sys.argv[0]))

            if self._build.attribute('pre_installation').value != '':
                self._build.perform_pre_installation(env)
            self._build.threat_variables(env)
            self._build.build(env, jobs)
            self._installed = monitor.end()
            if self._build.attribute('post_installation').value != '':
                self._build.perform_post_installation(env)
            env.end_build()
            self._built_once = True
            self.printResult(env, "Built", self.OK)
            return True
        except TaskError as e:
            self.printResult(env, "Building", self.FAIL)
            env._logger.commands.write("   > " + e.reason+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()           
            env.end_build()
            return False
        except:
            self._installed = monitor.end()
            env.end_build()
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()
            return False

    def check_build_version(self, env):
        """ Checks the version of the selected build tool in the machine. """
        
        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value
            
        env.start_build(self._name, srcDirTmp,
                        self._build.supports_objdir)
        
        retval = self._build.check_version(env)
        env.end_build()
        return retval

    def is_downloaded(self, env):
        """ Checks if the source code is not already available. """
        
        srcDirTmp = self._name
        if self._source.name() is 'system_dependency' :
            return True
        
        if self._source.name() is 'none' :
            return True

        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value

        env.start_source(self._name,srcDirTmp)
        retval = os.path.isdir(env.srcdir)
        env.end_source()
        return retval


    def check_source_version(self, env):
        """ Checks if the version of the available version control tool. """

        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value

        env.start_source(self._name, srcDirTmp)
        retval = self._source.check_version(env)
        env.end_source()
        return retval


    def update_libpath(self, env):
        """ Makes it available for the next modules the present libpath. """
        
        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value
            
        env.start_build(self._name, srcDirTmp,
                        self._build.supports_objdir)
        env.add_libpaths([env._lib_path()])
        env.end_build()

    def clean(self, env):
        """ Main cleaning build option handler. """
        
        srcDirTmp = self._name
        if self._source.attribute('module_directory').value :
            srcDirTmp = self._source.attribute('module_directory').value

        env.start_build(self._name, srcDirTmp,
                        self._build.supports_objdir)
        if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir):
            env.end_build()
            return
        try:
            self._build.clean(env)
            env.end_build()
            self._built_once = False
            print(" >> Cleaning " + self._name + " - OK")
            return True
        except TaskError as e:
            print(" >> Cleaning " + self._name + " - Problem")
            err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
            env._logger.commands.write(err+'\n')
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()           
            return False
        except:
            env.end_build()
            if env.debug :
                import bake.Utils
                bake.Utils.print_backtrace()
            return False

    def is_built_once(self):
        return self._built_once
    def get_source(self):
        return self._source
    def get_build(self):
        return self._build
    def name(self):
        return self._name
    def dependencies(self):
        return self._dependencies
