# 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 copy import deepcopy

from rift.mano.yang_translator.common.exception import ValidationError
from rift.mano.yang_translator.common.utils import _
from rift.mano.yang_translator.rwmano.syntax.tosca_resource \
    import ToscaResource
from rift.mano.yang_translator.rwmano.yang.yang_vdu import YangVdu

TARGET_CLASS_NAME = 'YangVnfd'


class YangVnfd(ToscaResource):
    '''Class for RIFT.io YANG VNF descriptor translation to TOSCA type.'''

    yangtype = 'vnfd'

    CONFIG_TYPES = ['script', 'netconf', 'rest', 'juju']

    OTHER_KEYS = (MGMT_INTF, HTTP_EP, MON_PARAM) = \
                 ('mgmt_interface', 'http_endpoint', 'monitoring_param')


    def __init__(self,
                 log,
                 name,
                 type_,
                 yang):
        super(YangVnfd, self).__init__(log,
                                       name,
                                       type_,
                                       yang)
        self.props = {}
        self.vdus = []
        self.mgmt_intf = {}
        self.mon_param = []
        self.http_ep = []

    def handle_yang(self):
        self.log.debug(_("Process VNFD desc {0}: {1}").format(self.name,
                                                              self.yang))

        def process_vnf_config(conf):
            vnf_conf = {}
            if self.CONFIG_ATTR in conf:
                for key, value in conf.pop(self.CONFIG_ATTR).items():
                    vnf_conf[key] = value

            if self.CONFIG_TMPL in conf:
                vnf_conf[self.CONFIG_TMPL] = conf.pop(self.CONFIG_TMPL)

            def copy_config_details(conf_type, conf_details):
                vnf_conf[self.CONFIG_TYPE] = conf_type
                vnf_conf[self.CONFIG_DETAILS] = conf_details

            for key in self.CONFIG_TYPES:
                if key in conf:
                    copy_config_details(key, conf.pop(key))
                    break

            if len(conf):
                self.log.warn(_("{0}, Did not process all in VNF "
                                "configuration {1}").
                              format(self, conf))
            self.log.debug(_("{0}, vnf config: {1}").format(self, vnf_conf))
            self.props[self.VNF_CONFIG] = vnf_conf

        def process_mgmt_intf(intf):
            if len(self.mgmt_intf) > 0:
                err_msg(_("{0}, Already processed another mgmt intf {1}, "
                          "got another {2}").
                        format(self, self.msmg_intf, intf))
                self.log.error(err_msg)
                raise ValidationError(message=err_msg)

            self.mgmt_intf['protocol'] = 'tcp'

            if self.PORT in intf:
                self.mgmt_intf[self.PORT] = intf.pop(self.PORT)
                self.props[self.PORT] = self.mgmt_intf[self.PORT]

            if 'vdu_id' in intf:
                for vdu in self.vdus:
                    if intf['vdu_id'] == vdu.id:
                        self.mgmt_intf[self.VDU] = vdu.get_name(self.name)
                        intf.pop('vdu_id')
                        break

            if self.DASHBOARD_PARAMS in intf:
                self.mgmt_intf[self.DASHBOARD_PARAMS] = \
                                            intf.pop(self.DASHBOARD_PARAMS)

            if len(intf):
                self.log.warn(_("{0}, Did not process all in mgmt "
                                "interface {1}").
                              format(self, intf))
            self.log.debug(_("{0}, Management interface: {1}").
                           format(self, self.mgmt_intf))

        def process_http_ep(eps):
            self.log.debug("{}, HTTP EP: {}".format(self, eps))
            for ep in eps:
                http_ep = {'protocol': 'http'}  # Required for TOSCA
                http_ep[self.PATH] = ep.pop(self.PATH)
                http_ep[self.PORT] = ep.pop(self.PORT)
                http_ep[self.POLL_INTVL] = ep.pop(self.POLL_INTVL_SECS)
                if len(ep):
                    self.log.warn(_("{0}, Did not process the following for "
                                    "http ep {1}").format(self, ep))
                    self.log.debug(_("{0}, http endpoint: {1}").format(self, http_ep))
                self.http_ep.append(http_ep)

        def process_mon_param(params):
            for param in params:
                monp = {}
                fields = [self.NAME, self.ID, 'value_type', 'units', 'group_tag',
                          'json_query_method', 'http_endpoint_ref', 'widget_type',
                          self.DESC]
                for key in fields:
                    if key in param:
                        monp[key] = param.pop(key)

                if len(param):
                    self.log.warn(_("{0}, Did not process the following for "
                                    "monitporing-param {1}").
                                  format(self, param))
                    self.log.debug(_("{0}, Monitoring param: {1}").format(self, monp))
                self.mon_param.append(monp)

        def process_cp(cps):
            for cp_dic in cps:
                self.log.debug("{}, CP: {}".format(self, cp_dic))
                name = cp_dic.pop(self.NAME)
                for vdu in self.vdus:
                    if vdu.has_cp(name):
                        vdu.set_cp_type(name, cp_dic.pop(self.TYPE_Y))
                        break
                if len(cp_dic):
                    self.log.warn(_("{0}, Did not process the following for "
                                    "connection-point {1}: {2}").
                                  format(self, name, cp_dic))

        ENDPOINTS_MAP = {
            self.MGMT_INTF: process_mgmt_intf,
            self.HTTP_EP:  process_http_ep,
            self.MON_PARAM: process_mon_param,
            'connection_point': process_cp
        }

        dic = deepcopy(self.yang)
        try:
            for key in self.REQUIRED_FIELDS:
                self.props[key] = dic.pop(key)

            self.id = self.props[self.ID]

            # Process VDUs before CPs so as to update the CP struct in VDU
            # when we process CP later
            if self.VDU in dic:
                for vdu_dic in dic.pop(self.VDU):
                    vdu = YangVdu(self.log, vdu_dic.pop(self.NAME),
                                  self.VDU, vdu_dic)
                    vdu.process_vdu()
                    self.vdus.append(vdu)

            for key in ENDPOINTS_MAP.keys():
                if key in dic:
                    ENDPOINTS_MAP[key](dic.pop(key))

            if self.VNF_CONFIG in dic:
                process_vnf_config(dic.pop(self.VNF_CONFIG))

            self.remove_ignored_fields(dic)
            if len(dic):
                self.log.warn(_("{0}, Did not process the following for "
                                "VNFD: {1}").
                              format(self, dic))
            self.log.debug(_("{0}, VNFD: {1}").format(self, self.props))
        except Exception as e:
            err_msg = _("Exception processing VNFD {0} : {1}"). \
                      format(self.name, e)
            self.log.error(err_msg)
            raise ValidationError(message=err_msg)

    def update_cp_vld(self, cp_name, vld_name):
        for vdu in self.vdus:
            cp = vdu.get_cp(cp_name)
            if cp:
                vdu.set_vld(cp_name, vld_name)
                break

    def generate_tosca_type(self, tosca):
        self.log.debug(_("{0} Generate tosa types").
                       format(self))

        for vdu in self.vdus:
            tosca = vdu.generate_tosca_type(tosca)

        # Add data_types
        if self.T_VNF_CONFIG not in tosca[self.DATA_TYPES]:
            tosca[self.DATA_TYPES][self.T_VNF_CONFIG] = {
                self.PROPERTIES:
                {self.CONFIG_TYPE:
                 {self.TYPE: self.STRING},
                 'config_delay':
                 {self.TYPE: self.INTEGER,
                  self.DEFAULT: 0,
                  self.REQUIRED: self.NO,
                  self.CONSTRAINTS:
                  [{'greater_or_equal': 0}]},
                 'config_priority':
                 {self.TYPE: self.INTEGER,
                  self.CONSTRAINTS:
                  [{'greater_than': 0}]},
                 self.CONFIG_DETAILS:
                 {self.TYPE: self.MAP},
                 self.CONFIG_TMPL:
                 {self.TYPE: self.STRING,
                  self.REQUIRED: self.NO},
                }
            }

        # Add capability types
        if self.CAPABILITY_TYPES not in tosca:
            tosca[self.CAPABILITY_TYPES] = {}
        if self.T_HTTP_EP not in tosca[self.CAPABILITY_TYPES]:
            tosca[self.CAPABILITY_TYPES][self.T_HTTP_EP] = {
                self.DERIVED_FROM: 'tosca.capabilities.Endpoint',
                self.PROPERTIES: {
                    'polling_interval':
                    {self.TYPE: self.INTEGER},
                    'path':
                    {self.TYPE: self.STRING},
                },
            }

        if self.T_MGMT_INTF not in tosca[self.CAPABILITY_TYPES]:
            tosca[self.CAPABILITY_TYPES][self.T_MGMT_INTF] = {
                self.DERIVED_FROM: 'tosca.capabilities.Endpoint',
                self.PROPERTIES: {
                    self.DASHBOARD_PARAMS:
                    {self.TYPE: self.MAP},
                    self.VDU:
                    {self.TYPE: self.STRING},
                },
            }

        if self.T_MON_PARAM not in tosca[self.CAPABILITY_TYPES]:
            tosca[self.CAPABILITY_TYPES][self.T_MON_PARAM] = {
                self.DERIVED_FROM: 'tosca.capabilities.nfv.Metric',
                self.PROPERTIES: {
                    'id':
                    {self.TYPE: self.INTEGER},
                    'name':
                    {self.TYPE: self.STRING},
                    'value_type':
                    {self.TYPE: self.STRING,
                     self.DEFAULT: 'INT'},
                    'group_tag':
                    {self.TYPE: self.STRING,
                     self.DEFAULT: 'Group1'},
                    'units':
                    {self.TYPE: self.STRING},
                    'description':
                    {self.TYPE: self.STRING},
                    'json_query_method':
                    {self.TYPE: self.STRING,
                     self.DEFAULT: 'NAMEKEY'},
                    'http_endpoint_ref':
                    {self.TYPE: self.STRING},
                    'widget_type':
                    {self.TYPE: self.STRING,
                     self.DEFAULT: 'COUNTER'},
                }
            }

        # Define the VNF type
        if self.T_VNF1 not in tosca[self.NODE_TYPES]:
            tosca[self.NODE_TYPES][self.T_VNF1] = {
                self.DERIVED_FROM: 'tosca.nodes.nfv.VNF',
                self.PROPERTIES: {
                    'vnf_configuration':
                    {self.TYPE: self.T_VNF_CONFIG},
                    'port':
                    {self.TYPE: self.INTEGER,
                     self.CONSTRAINTS:
                     [{'in_range': '[1, 65535]'}]},
                    self.START_BY_DFLT:
                    {self.TYPE: self.BOOL,
                     self.DEFAULT: self.TRUE},
                },
                self.CAPABILITIES: {
                    'mgmt_interface':
                    {self.TYPE: self.T_MGMT_INTF},
                    'http_endpoint':
                    {self.TYPE: self.T_HTTP_EP},
                    'monitoring_param_0':
                    {self.TYPE: self.T_MON_PARAM},
                    'monitoring_param_1':
                    {self.TYPE: self.T_MON_PARAM},
                },
                self.REQUIREMENTS: [
                    {'vdus':
                     {self.TYPE: 'tosca.capabilities.nfv.VirtualLinkable',
                      self.RELATIONSHIP:
                      'tosca.relationships.nfv.VirtualLinksTo',
                      self.NODE: self.T_VDU1,
                      self.OCCURENCES: '[1, UNBOUND]'}}
                ],
            }

        return tosca

    def generate_vnf_template(self, tosca, index):
        self.log.debug(_("{0}, Generate tosca template for VNF {1}").
                       format(self, index, tosca))

        for vdu in self.vdus:
            tosca = vdu.generate_vdu_template(tosca, self.name)

        node = {}
        node[self.TYPE] = self.T_VNF1

        # Remove fields not required in TOSCA
        self.props.pop(self.DESC)

        # Update index to the member-vnf-index
        self.props[self.ID] = index
        node[self.PROPERTIES] = self.props

        caps = {}
        if len(self.mgmt_intf):
            caps[self.MGMT_INTF] = {
                self.PROPERTIES: self.mgmt_intf
            }

        if len(self.http_ep):
            caps[self.HTTP_EP] = {
                self.PROPERTIES: self.http_ep[0]
            }
            if len(self.http_ep) > 1:
                self.log.warn(_("{0}: Currently only one HTTP endpoint "
                                "supported: {1}").
                              format(self, self.http_ep))

        if len(self.mon_param):
            count = 0
            for monp in self.mon_param:
                name = "{}_{}".format(self.MON_PARAM, count)
                caps[name] = {self.PROPERTIES: monp}
                count += 1

        node[self.CAPABILITIES] = caps

        if len(self.vdus):
            reqs = []
            for vdu in self.vdus:
                reqs.append({'vdus': {self.NODE: vdu.get_name(self.name)}})

            node[self.REQUIREMENTS] = reqs
        else:
            self.log.warn(_("{0}, Did not find any VDUS with this VNF").
                          format(self))

        self.log.debug(_("{0}, VNF node: {1}").format(self, node))

        tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][self.name] = node

        return tosca

    def get_supporting_files(self):
        files = []

        for vdu in self.vdus:
            f = vdu.get_supporting_files()
            if f and len(f):
                files.extend(f)

        return files
