#
# 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:
#
## 
# @author Teemu Rytkonen
import re
import logging
import simplejson

""" cone specific imports """
from cone.public import persistence, exceptions, api, utils, container
from cone.carbon import model

MODEL                    = model

def dumps(obj, indent=True):
    """ Make sure that the object is mapped to an object in this model """
    mobj = obj._get_mapper('carbon').map_object(obj)
    writer = get_writer_for_class(mobj.__class__.__name__)
    dict = writer.dumps(mobj)
    # Return the data as dict, as it is urlencoded by client
    return dict

def loads(jsonstr):
    return CarbonReader().loads(jsonstr)


class CarbonResourceMapper(object):
    def __init__(self):
        self.CARBON_RESOURCE_TYPE_MAP = {'configurationroot' : self.map_carbon_configurationroot,
                             'configurationlayer' : self.map_carbon_configurationlayer,
                             'featurelist' : self.map_carbon_featurelist}
        self.CONFML_RESOURCE_TYPE_MAP = {'configurationroot' : self.map_confml_configurationroot,
                             'configurationlayer' : self.map_confml_configurationlayer,
                             'featurelist' : self.map_confml_featurelist}

    def map_carbon_resource(self, resourcepath):
        for resourceext in self.CARBON_RESOURCE_TYPE_MAP:
            if resourcepath.endswith(resourceext):
                return self.CARBON_RESOURCE_TYPE_MAP[resourceext](resourcepath)
        return resourcepath

    def map_confml_resource(self, resourcetype, resourcepath):
        return self.CONFML_RESOURCE_TYPE_MAP[resourcetype](resourcepath)

    def map_carbon_configurationroot(self, resourcepath):
        return resourcepath.replace('.configurationroot', '.confml')

    def map_carbon_configurationlayer(self, resourcepath):
        return resourcepath.replace('.configurationlayer', '/root.confml')

    def map_carbon_featurelist(self, resourcepath):
        return "featurelists/%s" % resourcepath.replace('.featurelist', '.confml')

    def map_confml_configurationroot(self, resourcepath):
        return resourcepath.replace('.confml', '.configurationroot')

    def map_confml_configurationlayer(self, resourcepath):
        return resourcepath.replace('/root.confml', '.configurationlayer')

    def map_confml_featurelist(self, resourcepath):
        path = resourcepath.replace('featurelists/','').replace('.confml', '')
        version_identifier = 'WORKING'
        m = re.match('^(.*) \((.*)\)', path)
        # if the resourcepath does not have version information 
        # use default WORKING
        if m:
            path = m.group(1)
            version_identifier = m.group(2)
        return '%s (%s).featurelist' % (path, version_identifier)

class ResourceListReader(persistence.ConeReader):
    """
    """ 
    def loads(self, jsonstr):
        """
        @param jsonstr: The json string to read. 
        """
        reslist = model.ResourceList()
        datadict = simplejson.loads(jsonstr)
        for configuration in datadict.get('configurations', []):
            reslist.add_resource(model.ConfigurationResource(**configuration))
        for featurelist in datadict.get('featurelists', []):
            reslist.add_resource(model.FeatureListResource(**featurelist))
        return reslist

class HasResourceReader(persistence.ConeReader):
    """
    """ 
    def loads(self, jsonstr):
        """
        @param jsonstr: The json string to read. 
        """
        try:
            datadict = simplejson.loads(jsonstr)
            return datadict.get('has_resource',False)
        except ValueError,e:
            logging.getLogger('cone').error("Failed to parser json from %s" % jsonstr)
            raise e


class CarbonWriter(persistence.ConeWriter):
    """
    """ 
    def dumps(self, obj):
        """
        @param obj: The object 
        """
        """ Make sure that the object is mapped to an object in this model """
        mobj = obj._get_mapper('carbon').map_object(obj)
        writer = get_writer_for_class(mobj.__class__.__name__)
        return writer.dumps(obj)


class CarbonReader(persistence.ConeReader):
    """
    """ 
    def loads(self, jsonstr):
        """
        @param xml: The xml which to read. reads only the first object. 
        """
        try:
            datadict = simplejson.loads(jsonstr)
            for key in datadict:
                reader = get_reader_for_elem(key)
                return reader.loads(datadict[key])
        except (SyntaxError, ValueError),e:
            utils.log_exception(logging.getLogger('cone'), "Json string parse raised exception: %s!" % (e))
            raise exceptions.ParseError("Json string %s parse raised exception: %s!" % (jsonstr,e))

class ConfigurationCreateWriter(CarbonWriter):
    @classmethod
    def supported_class(cls, classname):
        """
        Class method to determine if this CarbonWriter supports writing
        of the given class name
        """
        return False

    def dumps(self, obj):
        """
        @param obj: The Configuration object 
        """
        featurelists = []
        included = []
        # Remove the featurelists and configurations from the creation phase
#        resmapper = CarbonResourceMapper()
#        for confpath in obj.list_configurations():
#            config = obj.get_configuration(confpath)
#            if config.meta and config.meta.get('type') == 'featurelist':
#                featurelists.append(resmapper.map_confml_resource('featurelist',confpath))
#            elif config.meta and config.meta.get('type'):
#                included.append(resmapper.map_confml_resource(config.meta.get('type'),confpath))
#            else:
#                # ignore configs that are not carbon configs
#                pass

        configuration_dict = {'name' : obj.name,
                              'parent_path' : '',
                              'included' : included,
                              'description' : obj.desc or 'Needs description',
                              'configuration_type' : 'carbon',
                              'resource_type' : 'configuration',
                              'feature_lists' : featurelists, 
                               }
        
        return configuration_dict

class ConfigurationWriter(CarbonWriter):
    @classmethod
    def supported_class(cls, classname):
        """
        Class method to determine if this CarbonWriter supports writing
        of the given class name
        """
        if classname=="CarbonConfiguration":
            return True
        else:
            return False

    def dumps(self, obj):
        if obj.meta:
            if obj.meta.get('type') == 'configurationroot':
                return self.dumps_root(obj)
            elif obj.meta.get('type') == 'configurationlayer':
                return self.dumps_layer(obj)
        raise Exception("Not supported CarbonConfigruration, %s" % obj)

    def dumps_root(self, obj):
        """
        @param obj: The Configuration object 
        """
        featurelists = []
        included = []
        resmapper = CarbonResourceMapper()
        for confpath in obj.list_configurations():
            config = obj.get_configuration(confpath)
            if config.meta:
                if config.meta.get('type') == 'featurelist':
                    featurelists.append(resmapper.map_confml_resource('featurelist',confpath))
                else:
                    included.append(resmapper.map_confml_resource(config.meta.get('type'),confpath))
            else:
                # This default case could also be identified as error
                included.append(confpath)

        configuration_dict = {'feature_lists': featurelists,
                             'parent_config': None, 
                             'configuration_name': obj.name, 
                             'version_identifier': obj.version_identifier, 
                             'included': included, 
                             'ref': obj.ref}
        
        return configuration_dict

    def dumps_layer(self, obj):
        """
        @param obj: The Configuration object 
        """
        configuration_dict = {'version_identifier': obj.version_identifier}

        datawriter = DataWriter()
        data = datawriter.dumps(obj)
        configuration_dict['data'] =  data
        
        return configuration_dict

class ConfigurationRootReader(CarbonReader):
    """
    """ 
    @classmethod
    def supported_elem(cls, elemname, parent=None):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given elem name
        """
        if elemname=="configurationroot":
            return True
        else:
            return False

    def __init__(self):
        pass

    def loads(self, dict):
        """
        @param obj: The Configuration object 
        """
        name = dict.get('configuration_name')
        path = name + ".confml"
        conf = model.CarbonConfiguration(dict.get('ref'), path=path, type='configurationroot')
        conf.name = name
        conf.version = dict.get('version_identifier')
        resmapper = CarbonResourceMapper()
        
        """ Read the featurelists as included configurations """
        for fealist in dict.get('feature_lists',[]):
            conf.include_configuration(resmapper.map_carbon_resource(fealist))
        """ Read the included configurations """
        for includedconfig in dict.get('included',[]):
            conf.include_configuration(resmapper.map_carbon_resource(includedconfig))
        return conf

class ConfigurationLayerReader(CarbonReader):
    """
    """ 
    @classmethod
    def supported_elem(cls, elemname, parent=None):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given elem name
        """
        if elemname=="configurationlayer":
            return True
        else:
            return False

    def __init__(self):
        pass

    def loads(self, dict):
        """
        @param obj: The Configuration object 
        """
        name = dict.get('configuration_name')
        path = name + ".confml"
        conf = model.CarbonConfiguration(dict.get('ref'), path=path, type='configurationlayer')
        conf.name = name
        
        conf.version = dict.get('version_identifier')
        
        """ Last read the data of this configuration and add it as a configuration """
        data_reader = DataReader()
        datacont = data_reader.loads(dict.get('data', {}))
        proxy = api.ConfigurationProxy(datacont.path)
        conf.add_configuration(proxy)
        proxy._set_obj(datacont)
        
        return conf

class FeatureListCreateWriter(CarbonWriter):
    @classmethod
    def supported_class(cls, classname):
        """
        Feature list create writer is supported only explicitly
        """
        return False

    def dumps(self, obj):
        """
        @param obj: The FeatureList object 
        """
        """ Make sure that the object is mapped to an object in this model """
        mobj = obj._get_mapper('carbon').map_object(obj)
        featurelist_dict = {'type' : 'featurelist',
                            'flv_description' : mobj.desc or 'Needs description',
                            'version_identifier' : mobj.version_identifier
                            }
        if hasattr(mobj, 'responsible'):
            featurelist_dict['responsible'] = mobj.responsible
        return featurelist_dict

class FeatureListWriter(CarbonWriter):
    @classmethod
    def supported_class(cls, classname):
        """
        Feature list create writer is supported only explicitly
        """
        if classname == 'FeatureList':
            return True
        else:
            return False

    def dumps(self, obj):
        """
        @param obj: The FeatureList object 
        """

        featurelist_dict = {
                            'type' : 'featurelist',
                            'name' : obj.name,
                            'flv_description' : obj.desc or 'Needs description',
                            'path' : obj.path,
                            'features' : []
                            }
        if obj.meta.get('version_identifier'):
            featurelist_dict['version_identifier'] = obj.meta.get('version_identifier')
        # add all features of the featurelist
        for fearef in obj.list_features():
            feature = obj.get_feature(fearef)
            writer = FeatureWriter()
            feadict = writer.dumps(feature)
            featurelist_dict['features'].append(feadict)
        
        return featurelist_dict

class FeatureListReader(CarbonReader):
    """
    """ 
    @classmethod
    def supported_elem(cls, elemname, parent=None):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given elem name
        """
        if elemname=="featurelist":
            return True
        else:
            return False

    def __init__(self):
        pass

    def loads(self, dict):
        """
        @param obj: The Configuration object 
        """
        fealist_expanded            = dict.get('expanded')
        fealist_version             = dict.get('version_identifier')
        fealist_is_latest_version   = dict.get('is_latest_version')
        fealist_list_id             = dict.get('list_id')
        fealist_path                = dict.get('path')
        fealist_version_title       = dict.get('version_title')
        fealist_can_be_released     = dict.get('can_be_released')
        fealist_type                = dict.get('type')
        fealist_has_external_relations = dict.get('is_latest_version')
        
        # Create a configuration object from the featurelist
        conf = model.FeatureList(path='featurelists/'+fealist_version_title+'.confml')
        conf.meta.add('version_identifier', fealist_version)
        
        for feature in dict.get('features'):
            reader = FeatureReader()
            fea = reader.loads(feature)
            if fea != None:
                conf.add_feature(fea)
        
        for feafqr in conf.list_all_features():
            # Add empty data object to featurelist configuration
            conf.add_data(api.Data(fqr=feafqr))
            
        return conf


class FeatureWriter(CarbonWriter):
    CONFML_TO_CARBON_TYPE = {
                             'boolean'   : 'BOOLEAN',
                             'int'       : 'INTEGER',
                             'selection' : 'SELECTION',
                             'string'    : 'STRING',
                             None : None,
                             '' : ''
                             }
    @classmethod
    def supported_class(cls, classname):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given class name
        """
        if classname=="Feature" or\
            classname=="CarbonBooleanSetting" or\
            classname=="CarbonIntSetting" or\
            classname=="CarbonStringSetting" or\
            classname=="CarbonSelectSetting"or\
            classname=="CarbonSetting":
            return True
        else:
            return False

    def dumps(self, obj):
        """
        @param obj: The Feature object 
        """
        """ Make sure that the object is mapped to an object in this model """
        mobj = obj._get_mapper('carbon').map_object(obj)
        
        featuredict = {'type' : 'feature',
                       'status' : 'APPROVED',
                       'title' : mobj.name,
                       'ref' : mobj.ref,
                       'description' : mobj.desc or 'Needs description',
                       'responsible' : None,
                       'value_type' : self.CONFML_TO_CARBON_TYPE[mobj.type],
                       'children' : []}
        if featuredict['value_type'] != None:
            featuredict['type_object'] = 'carbon_feature_type_normal'
        if mobj.type == 'selection':
            featuredict['options'] = mobj.options.keys() 

        writer = FeatureWriter()
        for fearef in mobj.list_features():
            feaobj = obj.get_feature(fearef)
            childdict = writer.dumps(feaobj)
            featuredict['children'].append(childdict)
        return featuredict


class FeatureReader(CarbonReader):
    """
    """ 
    @classmethod
    def supported_elem(cls, elemname, parent=None):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given elem name
        """
        if elemname=="features":
            return True
        else:
            return False

    def __init__(self):
        pass

    def loads(self, dict):
        """
        @param obj: The Configuration object 
        """
        id = dict.get('id')
        name = dict.get('title')
        ref = dict.get('ref')
        ref = utils.resourceref.to_objref(ref)
        status = dict.get('status')
        value_type = dict.get('value_type')
        description = dict.get('description')
        
        if value_type == 'boolean':
            fea = model.CarbonBooleanSetting(ref, type=value_type)
        elif value_type == 'integer':
            fea = model.CarbonIntSetting(ref, type=value_type)
        elif value_type == 'string':
            fea = model.CarbonStringSetting(ref, type=value_type)
        elif value_type == 'selection':
            fea = model.CarbonSelectionSetting(ref, type=value_type)
            for option in dict.get('options'):
                fea.add_option(option,option)
        elif value_type == '':
            fea = model.CarbonFeature(ref, type=value_type)
        else:
            fea = model.CarbonFeature(ref)

        
        fea.name = name
        fea.status = status
        fea.desc = description
         
        for childdict in dict.get('children',[]):
            reader = FeatureReader()
            subfea = reader.loads(childdict)
            if subfea != None:
                fea.add_feature(subfea)
        return fea

class DataWriter(CarbonWriter):
    @classmethod
    def supported_class(cls, classname):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given class name
        """
        if  classname=="Data" or \
            classname=="DataContainer":
            return True
        else:
            return False

    def dumps(self, obj):
        """
        @param obj: The DataContainer object 
        """
        datadict = {}
        for dataelem in obj._traverse(type=api.Data):
            if dataelem.get_value() != None:
                datadict[dataelem.get_fearef()] = map_confml2carbon_value(dataelem.get_value())
        return datadict


class DataReader(CarbonReader):
    """
    """ 
    @classmethod
    def supported_elem(cls, elemname, parent=None):
        """
        Class method to determine if this ConfmlWriter supports writing
        of the given elem name
        """
        if elemname=="data":
            return True
        else:
            return False

    def __init__(self):
        pass

    def loads(self, dict):
        """
        @param obj: The Configuration object 
        """
        datacont  = api.Configuration('confml/data.confml')
        for dataref in dict.keys():
            # Ignore null values
            if dict[dataref]:
                refs = []
                for elem in dataref.split('.'):
                    refs.append(utils.resourceref.to_objref(elem))
                newref = '.'.join(refs)
                dataelem = api.Data(fqr=newref, value=map_carbon2confml_value(dict[dataref]))
                datacont.add_data(dataelem)
        return datacont

def map_carbon2confml_value(value):
    if value == 'DEFINED':
        return 'true'
    elif value == 'UNDEFINED':
        return 'false'
    else:
        return value

def map_confml2carbon_value(value):
    if value == 'true':
        return 'DEFINED'
    elif value == 'false':
        return 'UNDEFINED'
    else:
        return value

def get_reader_for_elem(elemname, parent=None):
    for reader in CarbonReader.__subclasses__():
        if reader.supported_elem(elemname,parent):
            return reader()
    raise exceptions.ConePersistenceError("No reader for given elem %s under %s found!" % (elemname, parent))

def get_writer_for_class(classname):
    for writer in CarbonWriter.__subclasses__():
        if writer.supported_class(classname):
            return writer ()
    raise exceptions.ConePersistenceError("No writer for given class found! %s" % classname)
