#
# Copyright 2016 RIFT.io Inc
#
# 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.
#


from rift.mano.tosca_translator.common.utils import _
from rift.mano.tosca_translator.common.utils import convert_keys_to_python
from rift.mano.tosca_translator.rwmano.syntax.mano_resource import ManoResource
from toscaparser.functions import GetInput

from toscaparser.common.exception import ValidationError

try:
    import gi
    gi.require_version('RwVnfdYang', '1.0')

    from gi.repository import RwVnfdYang
except ImportError:
    pass
except ValueError:
    pass


# Name used to dynamically load appropriate map class.
TARGET_CLASS_NAME = 'ToscaNfvVnf'


class ToscaNfvVnf(ManoResource):
    '''Translate TOSCA node type tosca.nodes.nfv.vnf.'''

    toscatype = 'tosca.nodes.nfv.VNF'

    REQUIRED_PROPS = ['name', 'short-name', 'id', 'short-name', 'description',
                      'mgmt-interface']
    OPTIONAL_PROPS = ['version', 'vendor', 'http-endpoint', 'monitoring-param',
                      'connection-point']
    IGNORE_PROPS = ['port', 'monitoring_param']
    TOSCA_CAPS = ['mgmt_interface', 'http_endpoint', 'monitoring_param_0',
                  'monitoring_param_1', 'connection_point']

    def __init__(self, log, nodetemplate, metadata=None):
        super(ToscaNfvVnf, self).__init__(log,
                                          nodetemplate,
                                          type_="vnfd",
                                          metadata=metadata)
        self._const_vnfd = {}
        self._vnf_config = {}
        self._vdus = []
        self._policies = []
        self._cps = []
        self.vnf_type = nodetemplate.type
        self._reqs = {}

    def map_tosca_name_to_mano(self, name):
        new_name = super().map_tosca_name_to_mano(name)
        if new_name.startswith('monitoring-param'):
            new_name = 'monitoring-param'
        if new_name == 'polling-interval':
            new_name = 'polling_interval_secs'
        return new_name

    def handle_properties(self):
        tosca_props = self.get_tosca_props()
        self.log.debug(_("VNF {0} with tosca properties: {1}").
                       format(self.name, tosca_props))

        def get_vnf_config(config):
            vnf_config = {}
            for key, value in config.items():
                new_key = self.map_tosca_name_to_mano(key)
                if isinstance(value, dict):
                    sub_config = {}
                    for subkey, subvalue in value.items():
                        sub_config[self.map_tosca_name_to_mano(subkey)] = \
                                        subvalue
                    vnf_config[new_key] = sub_config
                else:
                    vnf_config[new_key] = value

            if vnf_config['config-type'] != 'script':
                err_msg = _("{}, Only script config supported "
                             "for now: {}"). \
                           format(self, vnf_config['config-type'])
                self.log.error(err_msg)
                raise ValidationError(message=err_msg)

            # Replace config-details with actual name (config-type)
            if ('config-type' in vnf_config and
                'config-details' in vnf_config):
                vnf_config[vnf_config['config-type']] = \
                                    vnf_config.pop('config-details')
                vnf_config.pop('config-type')

            # Update config-delay and confgig-priortiy to correct struct
            vnf_config['config-attributes'] = {}
            if 'config-delay' in vnf_config:
                vnf_config['config-attributes']['config-delay'] = \
                            vnf_config.pop('config-delay')
            else:
                vnf_config['config-attributes']['config-delay'] = 0
            if 'config-priority' in vnf_config:
                vnf_config['config-attributes']['config-priority'] = \
                            vnf_config.pop('config-priority')
            return vnf_config

        vnf_props = {}
        for key, value in tosca_props.items():
            if key == 'id':
                self._const_vnfd['member-vnf-index'] = int(value)
                self._const_vnfd['vnfd-id-ref'] = self.id
            elif key == 'vnf_configuration':
                self._vnf_config = get_vnf_config(value)
            else:
                vnf_props[key] = value

        if 'name' not in vnf_props:
            vnf_props['name'] = self.name

        if 'short-name' not in vnf_props:
            vnf_props['short-name'] = self.name

        if 'id' not in vnf_props:
            vnf_props['id'] = self.id

        if 'vendor' not in vnf_props:
            vnf_props['vendor'] = self.vendor

        if 'description' not in vnf_props:
            vnf_props['description'] = self.description

        if 'start_by_default' in vnf_props:
            self._const_vnfd['start-by-default'] = \
                                        vnf_props.pop('start_by_default')
        if 'logo' in self.metadata:
            vnf_props['logo'] = self.metadata['logo']

        self.log.debug(_("VNF {0} with constituent vnf: {1}").
                       format(self.name, self._const_vnfd))
        self.log.debug(_("VNF {0} with properties: {1}").
                       format(self.name, vnf_props))
        self.properties = vnf_props

    def handle_capabilities(self):
        tosca_caps = self.get_tosca_caps()
        self.log.debug(_("VDU {0} tosca capabilites: {1}").
                       format(self.name, tosca_caps))

        def get_props(props):
            properties = {}
            for key in props.keys():
                value = props[key]
                if isinstance(value, dict):
                    if 'get_property' in value:
                        val = self.get_property(value['get_property'])
                        value = val
                properties[self.map_tosca_name_to_mano(key)] = value
            return properties

        for key, value in tosca_caps.items():
            if key in ToscaNfvVnf.TOSCA_CAPS:
                new_key = self.map_tosca_name_to_mano(key)
                props = get_props(value)
                if 'id' in props:
                    props['id'] = str(props['id'])
                if 'protocol' in props:
                    props.pop('protocol')

                # There is only one instance of mgmt interface, but others
                # are a list
                if key == 'mgmt_interface':
                    self.properties[new_key] = props
                elif key == 'http_endpoint':
                    if new_key not in self.properties:
                        self.properties[new_key] = []
                    self.properties[new_key].append(props)
                else:
                    if new_key not in self.properties:
                        self.properties[new_key] = []
                    self.properties[new_key].append(props)

        self.log.debug(_("VDU {0} properties: {1}").
                       format(self.name, self.properties))

    def handle_requirements(self, nodes, policies, vnf_type_to_vdus_map):
        tosca_reqs = self.get_tosca_reqs()
        for req in tosca_reqs:
            for key, value in req.items():
                if 'target' in value:
                    self._reqs[key] = value['target']

        for policy in policies:
            if hasattr(policy, '_vnf_name') and policy._vnf_name == self.name:
                self._policies.append(policy)


        if self.vnf_type in vnf_type_to_vdus_map:
            for vdu_node_name in vnf_type_to_vdus_map[self.vnf_type]:
                node = self.get_node_with_name(vdu_node_name, nodes)
                if node:
                    self._vdus.append(node)
                    node._vnf = self
                    # Add the VDU id to mgmt-intf
                    if 'mgmt-interface' in self.properties:
                        self.properties['mgmt-interface']['vdu-id'] = \
                                        node.id
                        if 'vdu' in self.properties['mgmt-interface']:
                            # Older yang
                            self.properties['mgmt-interface'].pop('vdu')
                else:
                    err_msg = _("VNF {0}, VDU {1} specified not found"). \
                              format(self.name, target)
                    self.log.error(err_msg)
                    raise ValidationError(message=err_msg)

    def generate_yang_model_gi(self, nsd, vnfds):
        vnfd_cat = RwVnfdYang.YangData_Vnfd_VnfdCatalog()
        vnfd = vnfd_cat.vnfd.add()
        props = convert_keys_to_python(self.properties)
        for key in ToscaNfvVnf.IGNORE_PROPS:
            if key in props:
                props.pop(key)
        try:
            vnfd.from_dict(props)
        except Exception as e:
            err_msg = _("{0} Exception updating vnfd from dict {1}: {2}"). \
                      format(self, props, e)
            self.log.error(err_msg)
            raise e
        vnfds.append(vnfd_cat)

        # Update the VDU properties
        for vdu in self._vdus:
            vdu.generate_yang_submodel_gi(vnfd)
        for policy in self._policies:
            policy.generate_yang_submodel_gi(vnfd)

        # Update constituent vnfd in nsd
        try:
            props = convert_keys_to_python(self._const_vnfd)
            nsd.constituent_vnfd.add().from_dict(props)
        except Exception as e:
            err_msg = _("{0} Exception constituent vnfd from dict {1}: {2}"). \
                      format(self, props, e)
            self.log.error(err_msg)
            raise e

        # Update the vnf configuration info in mgmt_interface
        props = convert_keys_to_python(self._vnf_config)
        try:
            vnfd.vnf_configuration.from_dict(props)
        except Exception as e:
            err_msg = _("{0} Exception vnfd mgmt intf from dict {1}: {2}"). \
                      format(self, props, e)
            self.log.error(err_msg)
            raise e

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

        for key in ToscaNfvVnf.IGNORE_PROPS:
            if key in self.properties:
                self.properties.pop(key)

        if use_gi:
            return self.generate_yang_model_gi(nsd, vnfds)

        vnfd = {}
        vnfd.update(self.properties)
        # Update vnf configuration on mgmt interface
        vnfd['mgmt-interface']['vnf-configuration'] = self._vnf_config

        # Update the VDU properties
        vnfd['vdu'] = []
        for vdu in self._vdus:
            vnfd['vdu'].append(vdu.generate_yang_submodel())

        vnfds.append(vnfd)

        # Update constituent vnfd in nsd
        if 'constituent-vnfd' not in nsd:
            nsd['constituent-vnfd'] = []
        nsd['constituent-vnfd'].append(self._const_vnfd)

    def get_member_vnf_index(self):
        return self._const_vnfd['member-vnf-index']

    def get_supporting_files(self, files, desc_id=None):
        files[self.id] = []
        for vdu in self._vdus:
            if vdu.image:
                files[self.id].append({
                    'type': 'image',
                    'name': vdu.image,
                },)
            if vdu.cloud_init:
                files[self.id].append({
                    'type': 'cloud_init',
                    'name': vdu.cloud_init,
                },)
