#============================================================================ 
#Name        : builders.py 
#Part of     : Helium 

#Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
#All rights reserved.
#This component and the accompanying materials are made available
#under the terms of the License "Eclipse Public License v1.0"
#which accompanies this distribution, and is available
#at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
#Initial Contributors:
#Nokia Corporation - initial contribution.
#
#Contributors:
#
#Description:
#===============================================================================

"""
This modules contains the archive builders.
An archive builder is a class that is able to use data from a configuration
and generate a set of shell commands.
"""
import archive.tools
import archive.selectors
import archive.mappers
import archive.scanners
import logging
import pathaddition.relative
import buildtools
import os
import codecs
import fileutils

_logger = logging.getLogger('archive')
_logger.setLevel(logging.INFO)

class ArchivePreBuilder(buildtools.PreBuilder):
    """ Processes an archive build specification. """
    def __init__(self, config_set, config_name, writerType='ant', index = None):
        buildtools.PreBuilder.__init__(self, config_set)
        self.configs = config_set.getConfigurations()
        self.spec_name = config_name
        self.index = index
        self.writerType = writerType
        self.listToFindPrefix = []

    def build_manifest(self, config):
        """ Generate a manifest file from the a configuration. """
        _logger.info('Processing archive config: ' + config['name'])
        _scanners = archive.scanners.get_scanners(config.get_list('scanners', ['default']), config)
        
        content_list = {}
    
        if not os.path.exists(config['temp.build.dir']):
            os.makedirs(config['temp.build.dir'])
        manifest_file_path = os.path.abspath(os.path.join(config['temp.build.dir'], config['name'] + '_includefile.txt'))
        out = codecs.open(manifest_file_path, 'w+', 'utf-8')
        
        # zip.root.dir can be set to root.dir so that when zipping from another dir,
        # the manifest is relative to that dir
        (_, root_dir) = os.path.splitdrive(os.path.normpath(config.get('zip.root.dir', config['root.dir'])))
        _logger.info("   * Scanning")
        for scanner in _scanners:
            _logger.debug("Scanner %s" % scanner)
            for subpath in scanner.scan():
                (_, subpath) = os.path.splitdrive(subpath)
                if pathaddition.relative.abs2rel(subpath, root_dir):
                    _logger.debug(subpath)
                    subpath = subpath[len(root_dir):]
                    if subpath.startswith(os.sep):
                        subpath = subpath[1:]
                # normpath is to remove any occurances of "..\.." before checking for duplicates
                subpath = os.path.normpath(subpath)
                if subpath not in content_list:
                    out.write(u"".join([subpath, u'\n']))
                    content_list[subpath] = True
    
        out.close()
        return manifest_file_path

    def manifest_to_commands(self, config, manifest):
        """ Generate return a command list. Commands are stored in a two dimension array."""
        _logger.info("   * Generating commands")
        tool = archive.tools.get_tool(config['archive.tool'])
        mapper_name = 'default'
        if config.has_key('mapper'):
            mapper_name = config['mapper']
        mapper = archive.mappers.get_mapper(mapper_name, config, tool)
        return mapper.create_commands(manifest)
    
    def create_command_list(self, commands):
        """ Convert a two dimensions array of command to a CommandList object. """
        stages = buildtools.CommandList()
        newstage = False
        for cmds_stage in commands:
            _logger.debug("Stage: %s" % cmds_stage)
            for cmd in cmds_stage:
                stages.addCommand(cmd, newstage)
                newstage = False
            newstage = True
        return stages
    
    def writeTopLevel(self, build_file_path, output_path, xml_file):
        """Creates a build tool config makefile that executes archieve build."""
        config_name_list = []
        for config in self.configs:
            config_name_list.append(config['name'])
            if not os.path.exists(config['archives.dir']):
                os.makedirs(config['archives.dir'])
            
        writer = buildtools.get_writer(self.writerType, build_file_path)
        writer.writeTopLevel(config_name_list, self.spec_name, output_path, xml_file)
        writer.close()

    def getCommonUncRoots(self, uncPaths):
        """ Get common UNC roots.
            The following [r'\\server1\foo\bar', r'\\server2\foo', r'\\server1\foo'] input
            will return [r'\\server1\foo\', r'\\server2\foo'].
        """
        commonRoots = {}
        uncPaths = [os.path.normpath(p_path) for p_path in uncPaths]
        # sorting the input by alphabetical order, so smaller roots 
        # are coming first.
        uncPaths.sort()
        for p_path in uncPaths:
            common_path = "\\\\"
            for p_comp in p_path[2:].split(os.sep):
                common_path = common_path + p_comp + os.sep
                if common_path in commonRoots:
                    break
            else:
                commonRoots[common_path] = 1
        return commonRoots.keys()

    def getPrefix(self, dir_, commonUncRoots):
        """get prefix"""
        for root in commonUncRoots:
            if dir_.startswith(root):
                return root
        raise Exception("Could not find root for %s." % dir_)
    
    def checkRootDirValue(self, builder, parse_xml_file, build_drive, config_type):
        """Checks UNC path in root.dir and adds the substituted drive into EMAKEROOT."""
        substDrives = []
        if build_drive:
            substDrives.append(build_drive + os.sep)
        
        # Read all the config's root.dir to get UNC Path if any
        # Of course this is only on windows platform
        if os.sep == '\\':
            for config in self.configs:
                (drive, root_dir) = os.path.splitdrive(os.path.abspath(os.path.normpath(config['root.dir'])))
                _logger.debug("drive=%s, root_dir=%s" % (drive, root_dir))
                if drive == "":
                    self.listToFindPrefix.append(root_dir)
        
            commonUncRoots = self.getCommonUncRoots(self.listToFindPrefix)
            _logger.debug("Common roots %s" % (commonUncRoots))
            driveMapping = {}
            for root in commonUncRoots:
                _logger.info("Substing %s" % (root))
                driveMapping[root] = self.substUncPath(root)
                _logger.debug("%s subst as %s" % (root, driveMapping[root]))
                substDrives.append(driveMapping[root] + os.sep)

            for config in self.configs:
                (drive, root_dir) = os.path.splitdrive(os.path.abspath(os.path.normpath(config['root.dir'])) + os.sep) 
                if drive == "":
                    for root in driveMapping:
                        if root_dir.startswith(root):
                            config['root.dir'] = os.path.normpath(driveMapping[root] + os.sep + root_dir[len(root):len(root_dir)])
                            _logger.info("Updated %s in %s" % (root_dir, config['root.dir']))
                            config['unsubst.dir'] = driveMapping[root]
                            break
                    else:
                        _logger.error("Could not find root for %s." % root_dir)
                elif drive != build_drive:
                    if config['root.dir'] not in substDrives:
                        substDrives.append(config['root.dir'])
        else:
            for config in self.configs:
                if config['root.dir'].startswith('\\\\'):
                    _logger.error("UNC path are not supported under this platform: %s" % (config['root.dir']))
        builder.writeToXML(parse_xml_file, self.configs, config_type)
        return os.path.pathsep.join(substDrives)
       

    def substUncPath(self, path):
        """substitute UNC path"""
        freedrive = fileutils.get_next_free_drive()
        fileutils.subst(freedrive, path)
        return freedrive

    def cleanupSubstDrives(self):
        """ Read all the config's root.dir to get UNC Path if any"""
        drives = {}
        for config in self.configs:
            _logger.debug("Checking configuration...")
            _logger.debug("unsubst.dir: %s" % 'unsubst.dir' in config)
            _logger.debug("drives: %s" % drives)
            if 'unsubst.dir' in config and not config['unsubst.dir'] in drives:
                _logger.debug("Found drive to unsubst %s" % (config['unsubst.dir']))
                self.unSubStituteDrives(config['unsubst.dir'])
                drives[config['unsubst.dir']] = config['unsubst.dir']
                    
    def unSubStituteDrives(self, drive):
        """un-substitute Drives"""
        _logger.info("Unsubsting %s" % (drive))
        fileutils.unsubst(drive)
        
    def write(self, outputname):
        """Creates a build tool configuration file that executes archive build operations.

        The input to each archive build operation is an includefile that lists
        all the files to be included in the archive. These text files are
        generated before the build file by scanning the filesystem.
        """
        stages = buildtools.CommandList()

        commands = []
        if self.index > len(self.configs):
            raise Exception("index not found in configuration")
        config = self.configs[self.index]
        stages = self.manifest_to_commands(config, self.build_manifest(config))
                
        # merging the commands            
        while len(commands) < len(stages):
            commands.append([])
        for i in range(len(stages)):
            commands[i].extend(stages[i])

        writer = buildtools.get_writer(self.writerType, outputname)
        writer.write(self.create_command_list(commands))
        writer.close()
