#
# 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 sys
import logging
import tempfile
import os, re
import shutil
                          
from optparse import OptionParser, OptionGroup
import cone_common

from cone.public import api, utils

from conesub_merge import merge_config_root_to_config_root,\
                          get_active_root_if_necessary,\
                          MergePolicy, MergeFailedException
                          
from conesub_export import run_export

VERSION = '1.0'

logger    = logging.getLogger('cone')

class  MetaNotFoundException(Exception):
    pass    

def find_variant_layers_to_merge(source_config, target_config, find_pattern):
    """
    Find all layers in the configuration that contain custvariant* in
    their path name and return a list containing source->target mappings.
    @param source_config: The source configuration object.
    @param target_config: The target configuration object.
    @param new_name: The new name to replace custvariant* in the
        target path name with.
    @return: A list of (source_layer, target_layer) tuples.
    """
    pattern = re.compile(find_pattern)
    
    result = []
    for src in source_config.list_configurations():
        m = pattern.match(src)
        if m:
            result.append(src)
            
    print "Found layers %r" % result
    return result        

def main(argv=sys.argv):
    """ Initialize a variant from a cpf. """
    parser = OptionParser(version="%%prog %s" % VERSION)
    
    parser.add_options(cone_common.COMMON_OPTIONS)

    parser.add_option("-p", "--project",
                       dest="project",
                       help="Defines the location of current project. Default is the current working directory.",
                       default=".",
                       metavar="STORAGE")
    
    group = OptionGroup(parser, 'Initvariant options',
                        'The initvariant action is intended for merging a variant CPF back into the '
                        'configuration project, or creating a new empty variant based on an existing '
                        'configuration. It merges all customer variant layers (layers with '
                        'custvariant* in their path name) and renames them based on the variant ID '
                        'and variant name ("custvariant_<id>_<name>").')
    
    group.add_option("-c", "--configuration",
                        dest="configuration",
                        help="Defines the name of the target configuration. By default the "
                             "configuration file name is composed of product name, variant ID "
                             "and variant name like this: <product>_custvariant_<id>_<name>_root.confml",
                        metavar="CONFIG")
    
    group.add_option("-r", "--remote",
                   dest="remote",
                   help="Defines the location of remote storage (CPF)",
                   metavar="STORAGE")
    
    group.add_option("-s", "--sourceconfiguration",
                        dest="sourceconfiguration",
                        help="Defines the name of the remote configuration inside the remote storage. "
                             "Default is the active root of the remote project.",
                        metavar="CONFIG")
    
    group.add_option("--variant-id", help="Variant ID, mandatory.")
    group.add_option("--variant-name", help="Variant name, optional.")
    group.add_option("--product-name",
                     help="Product name, taken from the configuration data by default "
                          "(i.e. defaults to '${imakerapi.productname}')",
                     default="${imakerapi.productname}")
    
    group.add_option("--set-active-root",
                     action="store_true",
                     help="Set the newly created (or update) configuration root as the "
                          "project's active root after the merge is done.")
    
    group.add_option("-b","--based-on-configuration",
                     dest="boconfig",
                   help="Defines the configuration root which is used as a base "
                        "configuration for the new empty variant.")
    
    group.add_option("--find-layer-regexp",
                     dest="find_pattern",
                     default='.*/manual/.*|.*/configurator/.*',
                     help="Defines the pattern which is used to find the layers "
                          "from source configuration that will be merged"
                          "Default: '.*/manual/.*|.*/configurator/.*' " )
        
    parser.add_option_group(group)
    (options, _) = parser.parse_args(argv)
    
    cone_common.handle_common_options(options)
    
    # Check the passed options
    if not options.remote and not options.boconfig:
        parser.error("Remote project or based-on-configuration must be given")
    if options.remote and options.boconfig:
        parser.error("Only either remote project or based-on-configuration can be given, but not both")
    if not options.variant_id:  parser.error("Variant ID must be given")
    
    temp_cpf_folder = None
    
    if options.boconfig:   
        class ExportOptions(object):
            pass
            
        path = ''
        coreplat_name = ''
        product = ''
        
        project = api.Project(api.Storage.open(options.project,"a", username=options.username, password=options.password))
        config = project.get_configuration(options.boconfig)
        meta = config.meta
        
        if meta:
            for prop in meta.array:
                if 'name' in prop.attrs and 'value' in prop.attrs:
                    name = prop.attrs['name']
                    if name == 'coreplat_name':
                        coreplat_name =  prop.attrs['value']
                    if name == 'product_name':
                        product = prop.attrs['value']
        
        if not coreplat_name or not product:
            print >>sys.stderr, "Could not find coreplat_name or product_name from meta data."
            print >>sys.stderr, "Are you sure the given based-on-configuration is valid?"
            sys.exit(2)
        
        path = coreplat_name + '/' + product        
                    
        temp_cpf_folder = tempfile.mkdtemp()
        
        export_options = ExportOptions()
        export_options.project = options.project
        export_options.remote = os.path.join(temp_cpf_folder, 'temp.cpf')
        export_options.configs = [options.boconfig]
        export_options.username = options.username
        export_options.password = options.password
        export_options.config_wildcards = None
        export_options.config_regexes   = None
        export_options.export_dir = None
        export_options.exclude_content_filter = None
        export_options.added = [path + '/customer/custvariant/manual/root.confml',
                                path + '/customer/custvariant/configurator/root.confml']
        export_options.exclude_empty_folders = False
        export_options.action = None
                
        options.remote = export_options.remote
        
        print "Exporting variant CPF to a temporary directory"
        run_export(export_options)

    target_project = api.Project(api.Storage.open(options.project,"a", username=options.username, password=options.password))
    source_project = api.Project(api.Storage.open(options.remote,"r", username=options.username, password=options.password))

    print "Target project: %s" % options.project
    print "Source project: %s" % options.remote
    replace_dict = {"VAR_ID": options.variant_id, "VAR_NAME": options.variant_name}    
    
    try:
        # Open the source configuration
        source_config = get_active_root_if_necessary(source_project, options.sourceconfiguration, 'source')
        source_config = source_project.get_configuration(source_config)
        
        # Determine the new name of the layer part (replaces 'custvariant[^/]*')
        if options.variant_name:
            new_name = "custvariant_%s_%s" % (options.variant_id, options.variant_name)
        else:
            new_name = "custvariant_%s" % options.variant_id
        
        # Determine the target configuration
        if options.configuration:
            target_config = options.configuration
        else:
            # Target configuration not given explicitly, automatically
            # determine the name based on the product name and the new
            # layer name
            try:
                product_name = utils.expand_refs_by_default_view(
                    options.product_name,
                    source_config.get_default_view(),
                    catch_not_found = False)
            except Exception, e:
                print "Could not determine product name: %s" % e
                sys.exit(2)
            print "Product name:   %s" % product_name
            target_config = "%s_%s_root.confml" % (product_name, new_name)
        
        
        def find_layers(source_config, target_config):
            ret_list = []
            
            layers = find_variant_layers_to_merge(source_config,
                                                target_config,
                                                options.find_pattern)            
            p = re.compile(r'custvariant[^/]*')
            for layer in layers:
                tgt_layer = p.sub(new_name, layer) 
                ret_list.append((layer, tgt_layer))
            
            return ret_list
        
        # Perform the merge
        merge_config_root_to_config_root(
            source_project      = source_project,
            target_project      = target_project,
            source_config       = options.sourceconfiguration,
            target_config       = target_config,
            layer_finder_func   = find_layers,
            merge_policy        = MergePolicy.OVERWRITE_LAYER)
        
        if options.set_active_root:
            target_project.get_storage().set_active_configuration(target_config)
    except MergeFailedException, e:
        print "Could not initialize variant: %s" % e
        sys.exit(2)
    else:
        # Merge successful, so save the target project
        # to persist the changes
        target_project.save()
    
    target_project.close()
    source_project.close()
    
    if temp_cpf_folder:
        logger.debug("Removing temporary CPF directory '%s'" % temp_cpf_folder)
        shutil.rmtree(temp_cpf_folder)
    

if __name__ == "__main__":
    main()
