#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright 2016 RIFT.io Inc


import uuid

from collections import OrderedDict

import six

from rift.mano.tosca_translator.common.utils import _

from toscaparser.common.exception import ValidationError
from toscaparser.elements.interfaces import InterfacesDef
from toscaparser.functions import GetInput


SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
            DELETION_POLICY) = \
           ('type', 'properties', 'metadata',
            'depends_on', 'update_policy', 'deletion_policy')


class ManoResource(object):
    '''Base class for TOSCA node type translation to RIFT.io MANO type.'''

    def __init__(self,
                 log,
                 nodetemplate,
                 name=None,
                 type_=None,
                 properties=None,
                 metadata=None,
                 artifacts=None,
                 depends_on=None,
                 update_policy=None,
                 deletion_policy=None):
        self.log = log
        self.nodetemplate = nodetemplate
        if name:
            self.name = name
        else:
            self.name = nodetemplate.name
        self.type_ = type_
        self._id = None
        self._version = None
        self.properties = properties or {}
        self.metadata = metadata
        self._artifacts = artifacts

        # The difference between depends_on and depends_on_nodes is
        # that depends_on defines dependency in the context of the
        # HOT template and it is used during the template output.
        # Depends_on_nodes defines the direct dependency between the
        # tosca nodes and is not used during the output of the
        # HOT template but for internal processing only. When a tosca
        # node depends on another node it will be always added to
        # depends_on_nodes but not always to depends_on. For example
        # if the source of dependency is a server, the dependency will
        # be added as properties.get_resource and not depends_on
        if depends_on:
            self.depends_on = depends_on
            self.depends_on_nodes = depends_on
        else:
            self.depends_on = []
            self.depends_on_nodes = []
        self.update_policy = update_policy
        self.deletion_policy = deletion_policy
        self.group_dependencies = {}
        self.operations = {}
        # if hide_resource is set to true, then this resource will not be
        # generated in the output yaml.
        self.hide_resource = False
        log.debug(_('Translating TOSCA node %(name)s of type %(type)s') %
                  {'name': self.name,
                   'type': self.type_})

    # Added the below property menthods to support methods that
    # works on both toscaparser.NodeType and translator.ManoResource
    @property
    def type(self):
        return self.type_

    @type.setter
    def type(self, value):
        self.type_ = value

    def get_type(self):
        return self.type_

    @property
    def id(self):
        if self._id is None:
            self._id = str(uuid.uuid1())
        return self._id

    @property
    def description(self):
        return _("Translated from TOSCA")

    @property
    def vendor(self):
        if self._vendor is None:
            if self.metadata and 'vendor' in self.metadata:
                self._vendor = self.metadata['vendor']
            else:
                self._vendor = "RIFT.io"
        return self._vendor

    @property
    def version(self):
        if self._version is None:
            if self.metadata and 'version' in self.metadata:
                self._version = str(self.metadata['version'])
            else:
                self._version = '1.0'
        return self._version

    @property
    def artifacts(self):
        return self._artifacts

    @artifacts.setter
    def artifacts(self, value):
        self._artifacts = value

    def __str__(self):
        return "%s(%s)"%(self.name, self.type)

    def map_tosca_name_to_mano(self, name):
        new_name = name.replace("_", "-")
        return new_name

    def map_keys_to_mano(self, d):
        if isinstance(d, dict):
            for key in d.keys():
                d[self.map_tosca_name_to_mano(key)] = \
                                    self.map_keys_to_mano(d.pop(key))
            return d
        elif isinstance(d, list):
            arr = []
            for memb in d:
                arr.append(self.map_keys_to_mano(memb))
            return arr
        else:
            return d

    def validate_properties(self, properties, required=None, optional=None):
        if not isinstance(properties, dict):
            err_msg = _("Properties for {0}({1}) is not right type"). \
                      format(self.name, self.type_)
            self.log.error(err_msg)
            raise ValidationError(message=err_msg)

        if required:
            # Check if the required properties are present
            if not set(required).issubset(properties.keys()):
                for key in required:
                    if key not in properties:
                        err_msg = _("Property {0} is not defined "
                                    "for {1}({2})"). \
                                  format(key, self.name, self.type_)
                        self.log.error(err_msg)
                        raise ValidationError(message=err_msg)

            # Check for unknown properties
            for key in properties.keys():
                if (key not in required or
                    key not in optional):
                    self.log.warn(_("Property {0} not supported for {1}({2}), "
                                    "will be ignored.").
                                  format(key, self.name, self.type_))

    def handle_properties(self):
        pass

    def handle_artifacts(self):
        pass

    def handle_capabilities(self):
        pass

    def handle_requirements(self, nodes):
        pass

    def handle_interfaces(self):
        pass

    def update_image_checksum(self, in_file):
        pass

    def generate_yang_model(self, nsd, vnfds, use_gi=False):
        """Generate yang model for the node"""
        self.log.debug(_("{0}: Not doing anything for YANG model generation").
                       format(self))

    def top_of_chain(self):
        dependent = self.group_dependencies.get(self)
        if dependent is None:
            return self
        else:
            return dependent.top_of_chain()

    def get_dict_output(self):
        resource_sections = OrderedDict()
        resource_sections[TYPE] = self.type
        if self.properties:
            resource_sections[PROPERTIES] = self.properties
        if self.metadata:
            resource_sections[MEDADATA] = self.metadata
        if self.depends_on:
            resource_sections[DEPENDS_ON] = []
            for depend in self.depends_on:
                resource_sections[DEPENDS_ON].append(depend.name)
        if self.update_policy:
            resource_sections[UPDATE_POLICY] = self.update_policy
        if self.deletion_policy:
            resource_sections[DELETION_POLICY] = self.deletion_policy

        return {self.name: resource_sections}

    def get_tosca_props(self):
        tosca_props = {}
        for prop in self.nodetemplate.get_properties_objects():
            if isinstance(prop.value, GetInput):
                tosca_props[prop.name] = {'get_param': prop.value.input_name}
            else:
                tosca_props[prop.name] = prop.value
        return tosca_props

    def get_tosca_caps(self):
        tosca_caps = {}
        for cap in self.nodetemplate.get_capabilities_objects():
            properties = cap.get_properties()
            if len(properties):
                tosca_caps[cap.name] = {}
                for name in properties:
                    tosca_caps[cap.name][name] = properties[name].value
        return tosca_caps

    def get_tosca_reqs(self):
        tosca_reqs = []
        for requirement in self.nodetemplate.requirements:
            for endpoint, details in six.iteritems(requirement):
                req = {}
                relation = None
                interfaces = None
                if isinstance(details, dict):
                    target = details.get('node')
                    relation = details.get('relationship')
                else:
                    target = details
                if (target and relation and
                    not isinstance(relation, six.string_types)):
                    interfaces = relation.get('interfaces')
                req[endpoint] = {'target': target}
                if relation:
                    req[endpoint] = {'relation': relation}
                if interfaces:
                    req[endpoint] = {'interfaces': interfaces}
            tosca_reqs.append(req)
        return tosca_reqs

    def get_property(self, args):
        # TODO(Philip): Should figure out how to get this resolved
        # by tosca-parser using GetProperty
        if isinstance(args, list):
            if len(args) == 2 and \
               args[0] == 'SELF':
                if args[1] in self.properties:
                    return self.properties[args[1]]
                else:
                    self.log.error(_("{0}, property {} not defined").
                                   format(self.name, args[1]))
                    return
        self.log.error(_("Get property for {0} of type {1} not supported").
                       format(self.name, args))

    def get_node_with_name(self, name, nodes):
        """Get the node instance with specified name"""
        for node in nodes:
            if node.name == name:
                return node

    def get_nodes_related(self, target, type_, nodes):
        """Get list of nodes related to target node"""
        dep_nodes = []
        for node in nodes:
            if (node.name == target.name or
                type_ != node.type):
                continue
            for rel in node.nodetemplate.related_nodes:
                if rel.name == target.name:
                    dep_nodes.append(node)
                    break
        return dep_nodes

    def get_mano_attribute(self, attribute, args):
        # this is a place holder and should be implemented by the subclass
        # if translation is needed for the particular attribute
        raise Exception(_("No translation in TOSCA type {0} for attribute "
                          "{1}").format(self.nodetemplate.type, attribute))

    @staticmethod
    def _get_all_operations(node):
        operations = {}
        for operation in node.interfaces:
            operations[operation.name] = operation

        node_type = node.type_definition
        if (isinstance(node_type, str) or
            node_type.type == "tosca.policies.Placement"):
            return operations

        while True:
            type_operations = ManoResource._get_interface_operations_from_type(
                node_type, node, 'Standard')
            type_operations.update(operations)
            operations = type_operations

            if node_type.parent_type is not None:
                node_type = node_type.parent_type
            else:
                return operations

    @staticmethod
    def _get_interface_operations_from_type(node_type, node, lifecycle_name):
        operations = {}
        if (isinstance(node_type, str) or
            node_type.type == "tosca.policies.Placement"):
            return operations
        if node_type.interfaces and lifecycle_name in node_type.interfaces:
            for name, elems in node_type.interfaces[lifecycle_name].items():
                # ignore empty operations (only type)
                # ignore global interface inputs,
                # concrete inputs are on the operations themselves
                if name != 'type' and name != 'inputs':
                    operations[name] = InterfacesDef(node_type,
                                                     lifecycle_name,
                                                     node, name, elems)
        return operations

    @staticmethod
    def get_parent_type(node_type):
        if node_type.parent_type is not None:
            return node_type.parent_type
        else:
            return None

    @staticmethod
    def get_base_type(node_type):
        parent_type = ManoResource.get_parent_type(node_type)
        if parent_type is not None:
            if parent_type.type.endswith('.Root'):
                return node_type
            else:
                return ManoResource.get_base_type(parent_type)
        else:
            return node_type
