#
# 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 re
import fnmatch
import logging
from optparse import OptionParser, OptionGroup

import cone_common
from cone.public import api, plugin, utils, exceptions, container


VERSION     = '1.0'
DEFAULT_EXT = '.cpf'
CPF_NAMESPACE  = "http://www.nokia.com/xml/cpf-id/1"
CPF_META_TAG = "configuration-property"

logger    = logging.getLogger('cone')

def main():
    """ Update/set values to features in configuration(s). """
    parser = OptionParser(version="%%prog %s" % VERSION)
    
    parser.add_options(cone_common.COMMON_OPTIONS)
    
    parser.add_option("-c", "--configuration",
                        dest="configs",
                        action="append",
                        help="Defines the name of the configuration for the action, can be "\
                             "specified multiple times to include multiple configurations.",
                        metavar="CONFIG",
                        default=[])
    
    parser.add_option("--config-wildcard",
                      action="append",
                      dest="config_wildcards",
                      help="Wildcard pattern for including configurations, e.g. "\
                           "product_langpack_*_root.confml",
                      metavar="WILDCARD",
                      default=[])
    
    parser.add_option("--config-regex",
                      action="append",
                      dest="config_regexes",
                      help="Regular expression for including configurations, e.g. "\
                           "product_langpack_\\d{2}_root.confml",
                      metavar="REGEX",
                      default=[])
  
    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, 'Update options',
                        'The update functionality is meant for ConfML manipulation '
                        'in current project (defined with -p). '
                        'Default value for the current project is the currently working directory. '
                        'A project can be either a folder or a cpf/zip file.')
    
    
    group.add_option("-m","--add-meta",\
                   dest="meta",\
                   action="append",
                   type="string",
                   help="Add given metadata to defined configuration."\
                        "Example --add-meta \"owner=John Cone\" -m product=E75",
                   default=None)    

    group.add_option("--add-cpf-meta",\
                   dest="cpfmeta",\
                   action="append",
                   type="string",
                   help="Add given CPF identification metadata to defined configuration."\
                        "Example --add-cpf-meta \"coreplat_name=Platform1\"",
                   default=None)
    
    group.add_option("-d","--add-desc",\
                   dest="desc",\
                   type="string",\
                   help="Add given description to defined configuration."\
                        "Example --add-desc \"Customer one CPF\" -d Description1",
                   default=None)
    
    group.add_option("--remove-meta",\
                   dest="remove_meta",\
                   action="append",
                   type="string",
                   help="Removes given metadata from defined configuration."\
                        "Example --remove-meta owner --remove-meta coreplat_name",
                   metavar="META",\
                   default=None)

    group.add_option("--remove-desc",\
                   dest="remove_desc",\
                   action="store_true",\
                   help="Removes description from defined configuration."\
                        "Example --remove-desc",
                   default=False)    

    group.add_option("--add-data",\
                   dest="data",\
                   action="append",
                   type="string",
                   help="Add given ConfML data to defined configuration."\
                        "Example --add-data \"KCRUidAvkon.KAknDefaultAppOrientation=1\"",
                   default=None) 

    group.add_option("--add-configuration",\
                   dest="configurations",\
                   action="append",
                   type="string",
                   help="Include given configuration inside the updated configuration."\
                        "Example --add-configuration \"test_configuration.confml\"",
                   default=None) 

    group.add_option("--add-policy",\
                   dest="policy",\
                   type="string",
                   help="Define the data update policy, which can be append|prepend|replace."\
                        "Example --add-configuration \"test_configuration.confml\" --add-policy=prepend"\
                        "This would add the configuration as the first include to the target configuration.",
                   default='append') 

    parser.add_option_group(group)
    (options, args) = parser.parse_args()
    
    cone_common.handle_common_options(options)
    
    # Open the project and find out the active configuration
    project = api.Project(api.Storage.open(options.project, "a", username=options.username, password=options.password))
    try:
        active_root = project.get_storage().get_active_configuration()
    except AttributeError:
        active_root = None
    
    # Collect the list of configurations specified from the command line
    config_list = []
    if options.configs or options.config_wildcards or options.config_regexes:
        try:
            config_list = cone_common.get_config_list_from_project(
                project          = project,
                configs          = options.configs,
                config_wildcards = options.config_wildcards,
                config_regexes   = options.config_regexes)
        except cone_common.ConfigurationNotFoundError, e:
            parser.error(str(e))
    
    # Use the active configuration if no configurations are specifically given
    if len(config_list) == 0:
        if active_root is None:
            parser.error("No configurations given and the project does not have an active root")
        else:
            logger.info('No configurations given! Using active root configuration %s' % active_root)
            config_list = [active_root]
    
    
    
    # Parse added meta and data definitions
    added_meta = _parse_name_value_pairs(options.meta, 'metadata')
    added_cpf_meta = _parse_name_value_pairs(options.cpfmeta, 'CPF metadata')
    added_data = _parse_name_value_pairs(options.data, 'data')
    
    for config_name in config_list:
        print "Updating %s" % config_name
        config = project.get_configuration(config_name)
        
        # Handle metadata and data additions
        if added_meta:      _add_meta(config, added_meta)
        if added_cpf_meta:  _add_cpf_meta(config, added_cpf_meta)
        if added_data:      _add_data(config, added_data)
        
        # Handle configuration additions
        for configinc in options.configurations or []:
            config.include_configuration(configinc, parse_policy(options.policy))
          
        # Handle description  
        if options.desc:        
            logger.info("Setting description to %s" % options.desc)
            config.desc = options.desc
        if options.remove_desc:
            if config.desc:
                logger.info("Removing description")
                del config.desc
        
        # Handle metadata removals
        if options.remove_meta:
            for remove_meta in options.remove_meta:
                if config.meta:
                    index = config.meta.find_by_tag(remove_meta)
                    if index != -1:
                        del config.meta[index]
                        logger.info("Removed %s" % remove_meta)
                    else:
                        index = config.meta.find_by_attribute("name", remove_meta)
                        if index != -1:
                            del config.meta[index]
                            logger.info("Removed %s" % remove_meta)
                else:
                    logger.info("Could not remove metadata entry %s: not found." % remove_meta)
    
    project.save()
    project.close()

def _parse_name_value_pairs(entries, entry_type_name):
    """
    Parse a list of 'name=value' pairs into a dictionary.
    @param entries: The list of entries to parse.
    @param entry_type_name: Entry type name shown in the error message if an entry
        could not be parsed into a name-value pair.
    """
    if not entries:
        return {}
    
    result = {}
    pattern = re.compile("(.+)=(.+)")
    for entry in entries:
        mo = pattern.search(entry)
        if mo:
            name = mo.group(1)
            value = mo.group(2)
            result[name] = value
        else:
            logger.error("Illegal %s definition: %s" % (entry_type_name, entry))
    return result

def _add_meta(config, added_meta):
    if not config.meta:
        config.meta = []
    
    for tag, value in added_meta.iteritems():
        index = config.meta.find_by_tag(tag)
        if index != -1:
            logger.info("Replacing %s's value %s with %s" % \
                        (tag, config.meta[index].value, value))
            config.meta.replace(index, tag, value)
        else:
            logger.info("Adding value %s for %s." % (value, tag))                
            config.meta.add(tag, value)

def _add_cpf_meta(config, added_cpf_meta):
    if not config.meta:
        config.meta = []
    for attrName, attrValue in added_cpf_meta.iteritems():
        index = config.meta.find_by_attribute("name", attrName)
        if index != -1:
            logger.info("Replacing %s's value %s with %s" % \
                        (attrName, config.meta[index].attrs["value"], attrValue))
            config.meta.replace(index, CPF_META_TAG, None, CPF_NAMESPACE, {"name": attrName, "value": attrValue})
        else:
            logger.info("Adding value %s for %s." % \
                        (attrName, attrValue))                
            config.meta.add(CPF_META_TAG, None, CPF_NAMESPACE, {"name": attrName, "value": attrValue})

def _add_data(config, added_data):
    for ref, value in added_data.iteritems():
        if value.startswith("["):
            value = eval(value)
            for value_item in value:
                config.get_default_view().get_feature(ref).add_sequence(value_item, 0)
                logger.info("Set %s=%s" % (ref, repr(value)))
        else:
            config.get_default_view().get_feature(ref).set_value(value)
            logger.info("Set %s=%s" % (ref, value))

def parse_policy(policy_str):
    """
    >>> parse_policy('replace')
    0
    >>> parse_policy('append')
    1
    >>> parse_policy('prepend')
    2
    """
    if policy_str == 'append':
        return container.APPEND
    elif policy_str == 'replace':
        return container.REPLACE
    elif policy_str == 'prepend':
        return container.PREPEND
    else:
        raise Exception('Could not parse policy string! %s' % policy_str)

if __name__ == "__main__":
    main()
    
    



