#
# 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 "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: 
#
'''
Generator classes
'''


import re
import os
import sys
import logging
import subprocess
import shutil

ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
from cone.public import utils, exceptions

class InvalidInputFileException(RuntimeError):
    """
    Exception thrown in case of an invalid input file list.
    """
    pass

class OutputGenerator(object):
    def __init__(self,outputpath,**kwargs):
        self._configuration = None
        self._subpath = ''
        self._contentpath = ''
        self._command = ''
        self._inputs = []
        for arg in kwargs.keys():
            setattr(self, arg, kwargs[arg])
        self._outputpath = outputpath

    def __str__(self):
        return "Generator for output %s: %s"  % (self.path,self.get_command())

    def generate(self, context=None):
        command = self.get_command()
        if command:
            return command.execute()
        else:
            return 0

    def get_outputpath(self):
        """
        Get the confml ref value from configuration if the outputpath is actually a ref
        """
        if self._outputpath and ConfmlRefs.is_confml_ref(self._outputpath):
             oref = ConfmlRefs.get_confml_ref(self._outputpath)
             opath = self.configuration.get_default_view().get_feature(oref).get_value()
             if opath == None: 
                 logging.getLogger('cone.imageml').warning('Output path not set.')
                 return self._outputpath 
                 #raise exceptions.NotBound("Output path reference has no value %s" % oref)
             (drive,opath) = os.path.splitdrive(opath)
             opath = utils.resourceref.norm(opath)
             opath = utils.resourceref.remove_begin_slash(opath)
             return opath
        else:
             return self._outputpath

    def set_outputpath(self, value): 
        self._outputpath = value

    def del_outputpath(self): 
        self._outputpath = None

    def get_subpath(self):
        return self._subpath

    def set_subpath(self, value): 
        self._subpath = value

    def del_subpath(self): 
        self._subpath = None

    def get_inputs(self): 
        return self._inputs

    def set_inputs(self, value): 
        self._inputs = value

    def del_inputs(self): 
        self._inputs = []

    def get_configuration(self): 
        return self._configuration

    def set_configuration(self, value): 
        self._configuration= value
        for input in self.inputs:
            input.configuration = self.configuration

    def del_configuration(self): 
        self._configuration= None

    @property
    def path(self):
        return utils.resourceref.join_refs([self.subpath, self.outputpath])

    def get_command(self):
        (_,ext) = os.path.splitext(self.path)
        if ext == '.mbm':
            return BmconvCommand(self)
        elif ext == '.mif':
            return MifconvCommand(self)
        elif ext == '.gif':
            return CopyCommand(self)
        else:
            return None
    
    def get_refs(self):
        refs = []
        for input in self.inputs:
            refs.extend(input.get_refs())
        return refs

    configuration = property(get_configuration, set_configuration, del_configuration)
    inputs = property(get_inputs, set_inputs, del_inputs)
    outputpath = property(get_outputpath, set_outputpath, del_outputpath)
    subpath = property(get_subpath, set_subpath, del_subpath)

class Command(object):
    def __init__(self,generator):
        self._generator = generator
        self._workdir = 'conversion_workdir'

    def execute(self):
        """ Execute this command """
        pass

    def get_command(self, input_files):
        """ return the command as an array """
        return []

    def create_workdir(self, input_files):
        """
        Extract the necessary input files from storage to a working directory
        @param input_files: The input files (a list of InputFile objects) 
        """
        if not os.path.exists(self._workdir):
            os.makedirs(self._workdir)
        
        for file in input_files:
            self.import_to_work(file.filename)

    def clean_workdir(self):
        """
        Clean up working directory 
        """
        if os.path.exists(self._workdir):
            shutil.rmtree(self._workdir)

    def import_to_work(self,storage_filename):
        """
        Convert a storage filename to a work filename
        """
        workfile = self.workfilename(storage_filename)
        res = self._generator.configuration.get_resource(storage_filename,"rb")
        workfile = open(workfile,"wb")
        workfile.write(res.read())
        res.close()
        workfile.close()

    def workfilename(self,filename):
        """
        Convert a storage filename to a work filename
        """
        (_,workname) = os.path.split(filename)
        return os.path.join(self.workdir,workname)

    def quote_needed(self,str):
        """
        Add quotes around str if it has spaces
        """
        if str.split(' ',1) > 1:
            return '"%s"' % str
        else:
            return str
        
    @property
    def tool(self):
        return ''

    @property
    def generator(self):
        return self._generator

    @property
    def workdir(self):
        return self._workdir
    
    def _get_filtered_input_files(self):
        """
        Get the list of InputFile objects and with ignored
        (optional empty or invalid files) entries filtered out.
        
        Raise InvalidInputFileException if the input file list is invalid.
        """
        # Get all input files
        input_files = []
        for input in self.generator.inputs:
            input_files.extend(input.files)
        
        # Check if all are empty
        all_empty = True
        for file in input_files:
            if not file.is_empty():
                all_empty = False
                break
        if all_empty:
            return []
        
        # Create the filtered list
        result = []
        for file in input_files:
            if file.is_empty():
                if file.is_optional():
                    # Optional file is empty: no error
                    pass
                else:
                    raise InvalidInputFileException("Input file empty but not optional")
            else:
                if not file.is_valid():
                    raise InvalidInputFileException("Invalid input file: '%s'" % file.path)
                else:
                    result.append(file)
        return result 

class BmconvCommand(Command):
    def __init__(self,generator):
        super(BmconvCommand, self).__init__(generator)

    def execute(self):
        """
        Execute the command in the current working directory
        """
        input_files = self._get_filtered_input_files()
        if len(input_files) == 0: return 0
        self.create_workdir(input_files)
        
        if not os.path.exists(os.path.dirname(self.generator.path)):
            os.makedirs(os.path.dirname(self.generator.path))
        
        command = self.get_command(input_files)
        p = subprocess.Popen(command,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        
        # Wait for the process to return
        out, err = [ e.splitlines() for e in p.communicate() ]
        for outl in out:
            if outl not in err:
                logging.getLogger('cone.bmconv').info(outl)
        for outl in err:
            logging.getLogger('cone.bmconv').error(outl)
        if p.returncode != 0:
            logging.getLogger('cone.bmconv').error("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command)))
        else:
            logging.getLogger('cone.bmconv').info("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command)))
        if p.returncode == 0:
        	self.clean_workdir()
        return p.returncode 

    def get_command(self, input_files):
        command = [self.tool]
        """ Add palette file """
        if hasattr(self._generator,'palette'):
            command.append('/p%s' % os.path.abspath(self.generator.palette))

        """ Add output file """
        """ Add output file as compressed if needed """
        if self.rom:
            if self.compress:
                command.append('/s')
            else:
                command.append('/r')
        else:
            pass
        command.append(os.path.normpath(self.generator.path))
        
        
        for inputfile in input_files:
            depth = ''
            if inputfile.depth:
                depth = '/%s' % inputfile.depth
            command.append('%s%s' % (depth,self.workfilename(inputfile.filename)))
        return command

    @property
    def tool(self):
        if hasattr(self._generator,'tool'):
            return os.path.abspath(self._generator.tool)
        elif hasattr(self._generator, 'tooldir'):
            return os.path.abspath(os.path.join(self._generator.tooldir, 'bmconv'))
        else:
            return 'bmconv'

    @property
    def rom(self):
        if hasattr(self._generator,'rom') and self._generator.rom.lower() == 'true':
            return True
        else:
            return False

    @property
    def compress(self):
        if hasattr(self._generator,'compress') and self._generator.compress.lower() == 'true':
            return True
        else:
            return False

class MifconvCommand(Command):
    def __init__(self,generator):
        super(MifconvCommand, self).__init__(generator)

    def execute(self):
        """
        Execute the command in the current working directory
        """
        input_files = self._get_filtered_input_files()
        if len(input_files) == 0: return 0
        self.create_workdir(input_files)
        
        runenv = None
        runshell = True
        if os.path.dirname(self.tool):
            runenv = {}
            runenv['path'] = os.path.dirname(self.tool)
            runshell = True
        if not os.path.exists(os.path.dirname(self.generator.path)):
            os.makedirs(os.path.dirname(self.generator.path))
        
        command = self.get_command(input_files)
        p = subprocess.Popen(command,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             env=runenv,
                             shell=runshell)
        
        # Wait for the process to return
        out, err = [ e.splitlines() for e in p.communicate() ]
        for outl in out:
            if outl not in err:
                logging.getLogger('cone.mifconv').info(outl)
        for outl in err:
            logging.getLogger('cone.mifconv').error(outl)
        if p.returncode != 0:
            logging.getLogger('cone.mifconv').error("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command)))
        else:
            logging.getLogger('cone.mifconv').info("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command)))
        if p.returncode == 0:
	        self.clean_workdir()
        return p.returncode 

    def get_command(self, input_files):
        command = [self.tool]
        
        """ Add output file """
        command.append(os.path.normpath(self.generator.path))
        
        """ Add temp_path """
        command.append("/t%s" % self.temppath)
        
        # Add tool directory if given
        if hasattr(self._generator,'tooldir'):
            command.append('/S%s' % os.path.abspath(self.generator.tooldir))
        
        """ Get input files """
        for inputfile in input_files:
            depth = 'c8'
            if inputfile.depth:
                depth = inputfile.depth
            command.append('/%s' % depth)
            command.append( '%s' % self.workfilename(inputfile.filename))
        return command

    @property
    def tool(self):
        if hasattr(self._generator,'tool'):
            return os.path.abspath(self._generator.tool)
        elif hasattr(self._generator, 'tooldir'):
            return os.path.abspath(os.path.join(self._generator.tooldir, 'mifconv'))
        else:
            return 'mifconv'

    @property
    def temppath(self):
        if hasattr(self._generator,'temp'):
            return os.path.abspath(self._generator.temp)
        else:
            return self.workdir

class CopyCommand(object):
    def __init__(self,generator):
        self._generator = generator

    def execute(self):
        pass

    @property
    def tool(self):
        return 'copy'

class InputFile(object):
    def __init__(self,path,**kwargs):
        self.configuration = None
        self._depth = None
        for arg in kwargs.keys():
            if arg == 'depth':
                # Special handling for depth ('depth' is a property that
                # expands refs using '_depth' as the base)
                self._depth = kwargs[arg]
            else:
                setattr(self, arg, kwargs[arg])
        self._path= path

    def get_input(self): 
        """ 
        Get the confml ref value from configuration if the outputpath is actually a ref 
        """
        if self._path and self.configuration is not None:
            dview = self.configuration.get_default_view()
            def expand(ref, index):
                value = dview.get_feature(ref).get_original_value()
                if value is None:   return ''
                else:               return value
            return utils.expand_delimited_tokens(self._path, expand)
        else:
            return self._path

    def set_input(self, value): self._path = value

    def del_input(self): self._path = None

    @property 
    def type(self):
        return 'file'
    
    @property
    def depth(self):
        if self._depth and self.configuration:
            dview = self.configuration.get_default_view()
            return utils.expand_refs_by_default_view(self._depth, dview)
        else:
            return self._depth or ''

    @property 
    def files(self):
        """ 
        Return a list of file names 
        """
        return [self]
        
    @property 
    def filename(self):
        """ 
        Return a the path to the layer specific filename
        """ 
        if self.configuration and self.path:
            content = self.configuration.layered_content().flatten()
            inputpath = self.path
            return content.get(inputpath)
        else:
            return self.path

    path = property(get_input, set_input, del_input, "The input 'path'.")
    
    def is_valid(self):
        """
        Return whether the input file is valid (not empty
        and exists in project content).
        """
        return not self.is_empty() and self.filename
    
    def is_empty(self):
        """
        Return whether the input file is empty.
        """
        return self.path in ('', None)
    
    def is_optional(self):
        """
        Return whether the input file is optional.
        """
        return hasattr(self, 'optional') \
            and self.optional.lower() in ('1', 't', 'true', 'yes', 'y')
    
    def get_refs(self):
        return utils.extract_delimited_tokens(self._path)
    
    def __repr__(self):
        return "InputFile(path=%r, optional=%r)" % (self._path, self.is_optional())

class InputDir(InputFile):
    def __init__(self,path,**kwargs):
        super(InputDir,self).__init__(path,**kwargs)
        self._files = []
        self._include = None
        self._exclude = None

    def get_include(self): 
        return self._include.get('pattern',[])

    def set_include(self, value): 
        self._include = value

    def del_include(self): 
        self._include = None

    def get_exclude(self):
        return self._exclude.get('pattern',[])

    def set_exclude(self, value): 
        self._exclude = value

    def del_exclude(self): 
        self._exclude = None

    @property 
    def type(self): 
        return 'dir'

    @property 
    def files(self):
        """ 
        Return a list of file names under this directory definition
        """ 
        if self.configuration:
            inputlist = []
            content = self.configuration.layered_content().flatten()
            contentfiles = content.keys()
            
            folderfiles = utils.resourceref.filter_resources(contentfiles, "^%s" % self.path)
            for inputfilter in self.include:
                folderfiles = utils.resourceref.filter_resources(folderfiles, inputfilter)
            for excludefilter in self.exclude:
                folderfiles = utils.resourceref.neg_filter_resources(folderfiles, excludefilter)
            folderfiles.sort()
            for filename in folderfiles:
                inputlist.append(InputFile(filename, **self.__dict__))
            return inputlist
        else:
            return []

    include = property(get_include, set_include, del_include)
    exclude = property(get_exclude, set_exclude, del_exclude)



class ConfmlRefs(object):
    
    ref_pattern = re.compile('^\$\{(.*)\}$')

    @classmethod
    def is_confml_ref(cls, variableref):
        """
        
        Returns true if the given variable ref is a confml reference
        """
        return cls.ref_pattern.match(variableref) != None

    @classmethod
    def get_confml_ref(cls, variableref):
        """
        
        Returns true if the given variable ref is a confml reference
        """
        matchref = cls.ref_pattern.match(variableref)
        if matchref:
            return matchref.group(1)
        else:
            return None
