#
# 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: 
#

import os
import sys
import logging
from optparse import OptionParser, OptionGroup
import cone_common
import time
from os import path 
from cone.public import api, plugin, utils, exceptions
import generation_report
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))

VERSION = '1.0'


def main():    
    parser = OptionParser(version="%%prog %s" % VERSION)
    
    parser.add_options(cone_common.COMMON_OPTIONS)
    
    parser.add_option("-c", "--configuration",\
                        dest="configuration",\
                        help="defines the name of the configuration for the action",\
                        metavar="CONFIG")

    parser.add_option("-p", "--project",\
                       dest="project",\
                       help="defines the location of current project. Default is the current working directory.",\
                       default=".",\
                       metavar="STORAGE")
    
    gen_group = OptionGroup(parser, 'Generate options',
                    'The generate function will create target files from a specific configuration.'\
                    'The generate will always work with read-only mode of the project, so no changes are saved to project')
  
    gen_group.add_option("-o", "--output",\
                   dest="output",\
                   help="defines the target folder where the files are is generated or copied",\
                   metavar="FOLDER",\
                   default="output")

    gen_group.add_option("-l", "--layer",\
                   dest="layers",\
                   type="int",
                   action="append",
                   help="define layers of the configuration that are included to the output. "\
                        "The layer operation can be used several times in a single command."\
                        "Example -l -1 --layer=-2, which would append a layers -1 and -2 to the layers => layers = -1,-2",
                   metavar="LAYER",\
                   default=None)
    
    gen_group.add_option("--all-layers",
                   dest="all_layers",
                   action="store_true",
                   help="Include all layers in generation. This switch overrides all other layer "\
                        "configurations (iMaker API and using the --layer parameter)",
                   default=False)

    gen_group.add_option("-i", "--impl",\
                   dest="impls",\
                   action="append",
                   help=\
"""Define a Python regular expression filter for actual ImplML plugin(s) that needs to be executed. The whole path to ImplML filename is used in the regexp matching.
The impl operation can be used several times in a single command.
                                                                        
Example1 --impl crml => matches for any ImplML file that has a CrML string in the path.
Example2 --impl makeml$ => matches for ImplML file that has ends with MakeML string.
""",
                   metavar="IMPLS",\
                   default=None)

    gen_group.add_option("--impl-tag",\
                   dest="tags",\
                   type="string",
                   action="append",
                   help="define a tag for the implementations that are included to the output. "\
                        "A tag is name value pair and has the following format: name:value, e.g. target:rofs3."\
                        "Example --impl-tag=target:uda --impl-tag=target:content, which would include impls include both tags.",
                   metavar="TAG",\
                   default=None)

    gen_group.add_option("--impl-tag-policy",\
                   dest="tags_policy",\
                   type="string",
                   action="append",
                   help="Policy for implementation tags. May have one of the following values: --impl-tag-policy=AND, --impl-tag-policy=OR. "\
                   "Default is OR.",
                   metavar="TAGS_POLICY",\
                   default=None)
    
    gen_group.add_option("-s", "--set",\
                   dest="overrides",\
                   action="append",
                   type="string",
                   help="Override a ConfML reference in the execution."\
                        "The set operation can be used several times in a single command."\
                        "Example -s foo.bar=10 -s foo.fea='test'.",
                   metavar="SET",\
                   default=None)

    gen_group.add_option("--add",\
                   dest="added",\
                   action="append",
                   type="string",
                   help="Add a given configuration to the given configuration as last element."\
                        "The add operation can be used several times in a single command."\
                        "Example --add foo/root.confml --add bar/root-confml.",
                   metavar="CONF",\
                   default=None)

    gen_group.add_option("-r", "--report",\
                   dest="report",\
                   action="store",
                   type="string",
                   help="Generates a report about settings that are properly generated."\
                        "Example -r report.html.",
                   metavar="FILE",\
                   default=None)

    gen_group.add_option("-t", "--template",\
                   dest="template",\
                   action="store",
                   type="string",
                   help="Template used in report generation."\
                        "Example -t report_template.html.",
                   metavar="FILE",\
                   default=None)
    
    gen_group.add_option("--report-data-output",\
                   type="string",
                   help="Specifies a file where intermediary report data is generated.",
                   metavar="FILE",\
                   default=None)

    gen_group.add_option("-n", "--dryrun",\
                   dest="dryrun",\
                   action="store_true",
                   help="Executes generation without generation output.",
                   default=False)

    gen_group.add_option("--add-setting-file",\
                   dest="settings",\
                   action="append",
                   type="string",
                   help="Generate specific settings in ini format."\
                        "Example -o my_generate_settings.cfg.",
                   metavar="FILE",\
                   default=None)
    
    layers = None
    current = None
    remote = None
    
    start_time = time.time()
    
    parser.add_option_group(gen_group)
    (options, args) = parser.parse_args()

    settinglist = [os.path.join(ROOT_PATH,'conesub_generate.cfg')]
    if options.settings:
        for setting_file in options.settings:
            settinglist.append(os.path.normpath(os.path.join(ROOT_PATH, setting_file)))            
    gset = cone_common.get_settings(settinglist)
    
    cone_common.handle_common_options(options, settings=gset)
          
    current = api.Project(api.Storage.open(options.project,"r"))
    active_root = current.get_storage().get_active_configuration()
    if not options.configuration:
        if active_root == "":
            parser.error("configuration must be given")
        else:
            logging.getLogger('cone').info('No configuration given! Using active root configuration %s' % active_root)
            options.configuration = active_root
    try:
        config  = current.get_configuration(options.configuration)
    except exceptions.NotFound:
        parser.error("No such configuration: %s" % options.configuration)
    reffilters = None
    implfilters = None
    impltags = None
    
    # Include possible additional configurations
    if options.added:
        for configname in options.added:
            logging.getLogger('cone').info('Adding configuration %s' % configname) 
            config.include_configuration(utils.resourceref.norm(configname))
    
    # Get defs from configuration         
    try:
        layer_str_list = (config.get_default_view().get_feature('imakerapi.cone_layers').get_value() or '').split(',')
        # Make sure that empty layers definitions are ignored
        layer_str_list = utils.distinct_array(layer_str_list)
        if '' in layer_str_list:
            layer_str_list.remove('')
        # converting layrs identifiers from strings to int
        layerdefs = []
        for layerstr in layer_str_list:
            try:
                layerdefs.append(int(layerstr))
            except ValueError, e:
                logging.getLogger('cone').error('Invalid layer filter %s' % layerstr)
        implfilters = (config.get_default_view().get_feature('imakerapi.cone_impls').get_value() or '').split(',')
    except exceptions.NotFound:
        layerdefs = []
        implfilters = []
        pass

    # Get filters from command line if they exist => cmd overrides configuration
    if options.layers:
        layerdefs = options.layers
    if options.impls:
        implfilters = options.impls
    if options.tags and len(options.tags) > 0:
        impltags = {}
        for tag in options.tags:
            (name,value) = tag.split(':',2)
            existingvalue = impltags.get(name,[])
            existingvalue.append(value)
            impltags[name] = existingvalue
        logging.getLogger('cone').info('Tag filter %s' % impltags)
    else:
        impltags = None
    
    tags_policy = 'OR'
    if options.tags_policy:
        tags_policy = options.tags_policy[0]
    
    # Finally, --all-layers overrides all other layer settings
    if options.all_layers:
        layerdefs = []
    
    logging.getLogger('cone').info('Layer filter %s' % layerdefs)
    
    # Add reffilters only if the given layerids are somehow reasonable    
    if len(layerdefs) > 0:
        # get the data references from given layers
        logging.getLogger('cone').info('Getting layer specific data reference from %s' % layerdefs)
        reffilters = []
        for layerid in utils.distinct_array(layerdefs): 
            logging.getLogger('cone').info('Searching layer %s' % layerid)            
            layer = config.get_configuration_by_index(layerid)
            refs = _get_new_refs(reffilters, layer.list_leaf_datas())
            logging.getLogger('cone').info("Refs from layer '%s'\n%s" % (layer.get_path(), '\n'.join(refs)))
            reffilters += refs
    

    if options.overrides:
        config.add_configuration(api.Configuration('tempdata.confml'))
        for override in options.overrides:
            (ref,value) = override.split('=',1) 
            config.get_default_view().get_feature(ref).set_value(value)
            
    # Make sure that the output folder exists
    if not os.path.exists(options.output):
        os.makedirs(options.output)

    impls = plugin.filtered_impl_set(config,implfilters)
    impls.output = options.output
    
    logging.getLogger('cone').info("Supported implementation file extensions: %r" % plugin.get_supported_file_extensions())
    
#    logging.getLogger('cone').debug('Loaded implementations:')
#    for impl in impls:
#        msg = "File '%s', impl. type '%s', class '%s', phase '%s'" % \
#              (impl.ref, impl.IMPL_TYPE_ID, type(impl).__name__, impl.invocation_phase())
#        logging.getLogger('cone').debug(msg)
    
    
    # Create temporary variables
    temp_feature_refs = impls.create_temp_features(config)
    if reffilters is not None:
        reffilters.extend(temp_feature_refs)
        logging.getLogger('cone').info('Refs from temporary variables:\n%s' % '\n'.join(temp_feature_refs))
    
    
    
    # ---------------
    # Generate output
    # ---------------
    
    rule_exec_results = []
    
    # Create an implementation container with all the relevant implementations
    all_impls = impls.filter_implementations(tags=impltags, policy=tags_policy)
    
    # Implementations taking part in output generation
    gen_impls = plugin.ImplSet()
    context = plugin.GenerationContext()
    context.configuration = config
    log = logging.getLogger('cone')
    for phase in plugin.ImplSet.INVOCATION_PHASES:
        phase_impls = all_impls.filter_implementations(phase=phase)
        log.info("Generating phase '%s', %d implementation(s)" % (phase, len(phase_impls)))
        
        context.phase = phase
        # No use going any further if there are no implementations
        # for the phase at all
        if len(phase_impls) == 0:
            continue
        
        # Load and execute rules for this phase
        # -------------------------------------
#        relation_container = phase_impls.get_relation_container()
#        log.info("%d rule(s) for phase '%s'" % (relation_container.get_relation_count(), phase))
#        if relation_container.get_relation_count() > 0:
#            log.info("Executing rules...")
#            results = relation_container.execute()
#            log.info("Got %d execution result(s)" % len(results))
#            rule_exec_results.extend(results)
        
        
        # Create an implementation container for the phase with
        # the new reffilters and generate output with it
        # -----------------------------------------------------
        impls = phase_impls.filter_implementations(refs=reffilters)
        log.info("%d implementation(s) after filtering for phase '%s'" % (len(impls), phase))
        if len(impls) > 0:
            if impltags != None:
                context.tags = impltags
                context.tags_policy = tags_policy
            impls.output = options.output
            log.info("Generating output...")
            impls.generate(context)
            impls.post_generate(context)
            
            # Add new refs after generation
#            if reffilters != None and len(reffilters) > 0:
#                layer = config.get_configuration_by_index(-1)
#                new_refs = _get_new_refs(reffilters, layer.list_leaf_datas())
#                log.info('Added %d ref(s) after generation:\n%s' % (len(new_refs), '\n'.join(new_refs)))
#                reffilters += new_refs
            
        # Add new references after each phase execution
        # ---------------------------------------
        if reffilters != None and len(reffilters) > 0:
            layer = config.get_configuration_by_index(-1)
            new_refs = _get_new_refs(reffilters, layer.list_leaf_datas())
            log.info('Added %d ref(s) after phase %s execution:\n%s' % (len(new_refs), phase, '\n'.join(new_refs)))
            reffilters += new_refs
        
        # Add the implementations to the set of implementations participating
        # in output generation (used in the report)
        for impl in impls:
            for actual_impl in impl.get_all_implementations():
                logging.getLogger('cone').info('Adding impl %s' % impl)
                gen_impls.add(actual_impl)
    
    rule_exec_results = context.results
    print "Generated %s to %s!" % (options.configuration, impls.output)
    
    
    # ---------------
    # Generate report
    # ---------------
    
    # If reporting is enabled collect data for report
    if options.report != None or options.report_data_output != None:
        logging.getLogger('cone').info('Collecting data for report.')
        all_refs = reffilters or utils.distinct_array(config.get_configuration_by_index(-1).list_leaf_datas())
        logging.getLogger('cone').info('Collecting data found refs %s' % all_refs)
        logging.getLogger('cone').info('Collecting data found gen_impls %s' % gen_impls)
        rep_data = generation_report.collect_report_data(config, options, all_refs, gen_impls, rule_exec_results)
        logging.getLogger('cone').info('Collecting data found rep_data  %s' % rep_data)
        
        duration = str("%.3f" % (time.time() - start_time) )
        rep_data.set_duration( duration )
        
        # Save intermediary report data file if necessary
        if options.report_data_output != None:
            logging.getLogger('cone').info('Dumping report data to %s' % options.report_data_output)
            print "Dumping report data to '%s'" % options.report_data_output
            generation_report.save_report_data(rep_data, options.report_data_output)
        
        # Generate the report if necessary
        if options.report != None:
            generation_report.generate_report(rep_data, options.report, options.template)
            print_summary(rep_data)
    
    if current: current.close()

def _get_new_refs(old_refs, new_refs):
    """
    Return a distinct array of refs in ``new_refs`` that are not present in ``old_refs``.
    """
    result = []
    for ref in new_refs:
        if ref not in old_refs and ref not in result:
            result.append(ref)
    return result

def print_summary(rep_data):
    """ Prints generation summary to logger and console. """
    print "\nGENERATION SUMMARY:"
    print "--------------------"
    print "Refs in files: %s" % rep_data.nbr_of_refs
    print "Refs with no implementation: %s" % rep_data.nbr_of_refs_noimpl
    print "Generation duration: %s" % rep_data.duration
    print "\n\n"
    

if __name__ == "__main__":
    main()
