#============================================================================ 
#Name        : rom.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 implements rombuilders.
"""
import logging
import os
import sys
import shutil
import types
from version import Version
import re
import escapeddict
import imaker

# Uncomment this line to enable logging in this module, or configure logging elsewhere
#logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("rom")


def get_abstract_parents(config):
    """ Create from a config element a list of parent
        that are abstract (not buildable).
    """
    result = []
    while (config.parent != None):
        if config.parent.abstract != None:
            result.append(config.parent)
        config = config.parent
    return result

def read_file_content(filename):
    """ Read the whole file content.
    """
    ftr = open(filename, "r")
    content = ftr.read()
    ftr.close()
    return content

def escape_string(string, config):
    """ Escape a string recursively.
    """
    #data = escapeddict.EscapedDict(config)
    #string = re.sub(r'\${(?P<name>[._a-zA-Z0-9]+)}', r'%(\g<name>)s', string)
    #return string % data
    return config.interpolate(string)

def get_makefile_target(text):
    """ Retrieve the target name of a step
    """
    result = re.search(r"^(?P<target>.+?)\s*:", text, re.M)
    if (result != None):
        return result.groupdict()['target']
    raise Exception("Could'nt determine target name")

def remove_duplicates(array):
    """ Remove dusplicates values from an array. """
    elements = {}
    for element in array: elements[element] = element
    return elements.keys()

def get_product_path_bsf(product):
    """ Get product path using the BSF. """
    import bsf
    # read product hierarchy
    bsfs = bsf.read_all()
    return bsfs[product].get_path()
    
def get_product_path_var(product):
    """ Get product path using the new tool. """
    return imaker.get_product_dir(product)

class IMakerRomBuilder:
    """ Configuration Builder for iMaker tool.
        This tool generate a makefile.
    """
    
    def __init__(self, config, product, usevar=False):
        self._config = config
        self._product = product

    def process_my_traces(self, config):
        """ Generates a mytraces.txt file under \epoc32 based on the <mytraces/>
            XML sub-elements defined for the image.
        """
        sys.stdout.flush()
        if config.has_key('mytraces.binaries') and len(str(config['mytraces.binaries'])) > 0:
            mytracestxt = escape_string(config['mytraces.file'], config)
            logger.debug("Writing %s file" % mytracestxt)
            binaries = config['mytraces.binaries']
            traces_file = open(mytracestxt, 'w')
            for binary in binaries:
                traces_file.write(str(binary) + "\n")
            traces_file.close()

    def build(self):
        """ Generate the makefile from xml configuration.
            That method should be splitted....it's to long!!!
        """
        configs = self._config.getConfigurations(self._product)
        if (len(configs) > 0):
            
            # creating additional targets
            targets = {}
            
            master_filename = configs[0]['main.makefile.template']
            
            outputfilename = os.path.basename(master_filename)
            if configs[0].has_key('output.makefile.filename'):
                outputfilename = configs[0]['output.makefile.filename']
            
            filename = "%s/%s" % (get_product_path_var(self._product), outputfilename)
            output = open(filename, "w+")
            output.write("# DO NOT EDIT - FILE AUTOMATICALLY GENERATED\n")
            output.write("# HELIUM variant configuration tool (C) Nokia - 2007\n\n")
            mkdefine = '__' + re.sub(r'[^\w]', '_', os.path.basename(outputfilename)).upper() + '__'
            output.write("ifndef %s\n" % mkdefine)
            output.write("%s := 1\n\n" % mkdefine)
            master_template = read_file_content(master_filename)
            output.write(configs[0].interpolate(master_template) + "\n")
                        
            for config in configs:
                # generating traces
                #self.process_my_traces(config)
                                                 
                
                if config.type == None:
                    raise Exception("Type not defined for configuration '%s'" % config.name)
                
                # generate makefile targets from templates
                if config.has_key("%s.makefile.template" % config.type):
                    template = read_file_content(config["%s.makefile.template" % config.type])
                    image_types = config['image.type']
                    if not isinstance(config['image.type'], types.ListType):
                        image_types = [config['image.type']]
                    for romtype in image_types:
                        config['image.type'] = romtype
                        out = config.interpolate(str(template))
                        output.write(out+"\n")
                        subtargets = [get_makefile_target(out)]
                        for parent in get_abstract_parents(config):
                            if not targets.has_key(parent.name):
                                targets[parent.name] = {}
                                targets[parent.name]['parent'] = parent
                                targets[parent.name]['subtargets'] = []
                            targets[parent.name]['subtargets'].extend(subtargets)
                            targets[parent.name]['subtargets'] = remove_duplicates(targets[parent.name]['subtargets'])
                            subtargets = [parent.name]
                    config['image.type'] = image_types
                else:
                    # Do not raise error anymore when template is not found. 
                    print "WARNING: Could not find template for %s (%s)" % (config.name,"%s.makefile.template" % config.type)
                    #raise Exception("Could not find template for %s (%s)" % (config.name,"%s.makefile.template" % config.type))
               
                    
            output.write("###############################################################################\n")
            output.write("# Generated group target\n")
            output.write("###############################################################################\n\n")
            for target in targets.keys():
                if targets[target]['parent']['build.parallel'] == 'true':
                    output.write("%s: %s\n\n" % (target, " ".join(targets[target]['subtargets'])))
                    output.write("%s-dryrun:\n" % target)
                    output.write("\t@$(CALL_TARGET) -n %s\n\n" % target)
                else:
                    output.write("%s-dryrun:\n" % target)
                    for subtarget in targets[target]['subtargets']:
                        output.write("\t@$(CALL_TARGET) -n %s\n" % subtarget)
                    output.write("\n")
                    output.write("%s:\n" % target)
                    output.write("\t@echo === %s started\n" % target)
                    for subtarget in targets[target]['subtargets']:
                        output.write("\t$(CALL_TARGET) %s\n" % subtarget)
                    output.write("\t@echo === %s finished\n" % target)
                    output.write("\n")
            output.write("\nendif # %s\n" % mkdefine)
            output.close()
            print "File %s has been generated." % filename
        else:
            raise Exception("Could not find configuration: '%s'" % self._product)


class RomBuilder:
    """ Builder that create roms using makefpsx.
    """
    def __init__(self, configs):
        self.configs = configs

    def build(self):
        """ Go throught the config and build each roms.
        """
        for config in self.configs:
            for k in sorted(config.keys()):
                value = config[k]
                if isinstance(value, types.UnicodeType):
                    value = value.encode('ascii', 'ignore')
                print k + ': ' + str(value)
            image = Image(config)
            image.build()
            print '======================================'
            print

class Image:
    """ An Image object represents a ROM image, or .fpsx file.
    """

    def __init__(self, config):
        """ Initialise the Image object.
        """
        self.config = config

    def build(self):
        """ Main method that handles the whole sequence of building the rom and
            moving all related files to the correct location.
        """
        self._create_destination()
        self._process_cmt()
        self._write_version()
        self._process_my_traces()
        self._callrommake()
        self._clean_mytraces()
        self._move_image_files()
        print

    def _create_destination(self):
        """ Creates the destination directory of the ROM files if it does not exist.
        """
        dest = self.config['rom.output.dir']
        if not os.path.exists( dest ):
            os.makedirs( dest )

    def _process_cmt(self):
        """ Copies the CMT image under \epoc32 and to the destination directory of
            the ROM image, if the image will include the CMT.
        """
        # Check if a CMT is needed
        if self.config['image.nocmt'] != 'true':
            dest = self.config['rom.output.dir']
            logger.debug("Copying " + self.config['cmt'] + " to " + dest)
            shutil.copy( self.config['cmt'], dest )
            logger.debug("Copying " + self.config['cmt'] + " to " + self.config['rommake.cmt.path'])
            shutil.copy( self.config['cmt'], self.config['rommake.cmt.path'] )

    def _write_version(self):
        """ Generates the version text files that define the version of the ROM image.
            These are in UTF16 little endian (Symbian) format.
        """
        Version('sw', self.config).write()
        Version('langsw', self.config).write()
        Version('model', self.config).write()

    def _process_my_traces(self):
        """ Generates a mytraces.txt file under \epoc32 based on the <mytraces/>
            XML sub-elements defined for the image.
        """
        sys.stdout.flush()
        if self.config.has_key('mytraces.binaries'):
            logger.debug("Writing mytraces.txt file")
            binaries = self.config['mytraces.binaries']
            traces_file = open( str(self.config['rommake.mytraces.file']), 'w' )
            for binary in binaries:
                traces_file.write( str(binary) + "\n" )
            traces_file.close()
        else:
            self._clean_mytraces()

    def _callrommake(self):
        """ Calls the make_fpsx.cmd to build a ROM image.
        """
        logger.debug("Building rom image: " + str(self))
        sys.stdout.flush()

        args = [str(self.config['rommake.command']),
                '-hid',
                str(self.config['rommake.hwid']),
                '-p',
                str(self.config['rommake.product.name']),
                '-iby',
                str(self.config['image.iby']),
                '-type',
                str(self.config['image.type']),
                '-traces',
                '-verbose',
                '-target',
                self.config['rom.output.dir'],
                '-o' + str(self)]
        if 'rommake.args' in self.config:
            extra_args = str(self.config['rommake.args']).split( ' ' )
            args += extra_args
        logger.debug("with args: " + str(args))
        os.chdir(os.path.dirname(str(self.config['rommake.command'])))
        os.spawnv(os.P_WAIT, str(self.config['rommake.command']), args)

    def _clean_mytraces(self):
        logger.debug("Removing mytraces.txt file")
        if os.path.exists( str( self.config['rommake.mytraces.file'] ) ):
            os.remove( str( self.config['rommake.mytraces.file'] ) )
    
    def _move_image_files(self):
        os.chdir( self.config['rom.output.dir'] )
        if not( os.path.isdir('temp') ):
            os.mkdir( 'temp' )
        if not( os.path.isdir('logs') ):
            os.mkdir( 'logs' )
        if not( os.path.isdir('obys') ):
            os.mkdir( 'obys' )
        
        for element in os.listdir('.'):
            if( os.path.isfile(element) ):
                if( element.endswith('.img') or element.endswith('.bin') or element.endswith('.bb5') ):
                    shutil.move( element, 'temp' )
                if( element.endswith('.log') or element.endswith('.dir') or element.endswith('.symbol') ):
                    shutil.move( element, 'logs' )
                if( element.endswith('.oby') ):
                    shutil.move( element, 'obys' )

                
    # Returns the name of this ROM image
    def __str__(self):
        """ Returns the filename of the image file once copied to the
            \*_flash_images directory.
        """
        # Get the unique build ID for these ROM image names
        name = str(self.config['rom.id']) + '_' + self.config['image.type']

        # Add a flag if the ROM is textshell
        if self.config['image.ui'] == 'text':
            name += "_text"

        # Add a flag if the image does not a CMT
        if self.config['image.nocmt'] == 'true':
            name += "_nocmt"

        # Add any differentiating name extension if present3
        if self.config['image.name.extension'] != '':
            name += '_' + self.config['image.name.extension']

        return name
