#
# 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 itertools import chain
import logging

from translator.hot.syntax.hot_resource import HotResource
log = logging.getLogger('heat-translator')

# Name used to dynamically load appropriate map class.
TARGET_CLASS_NAME = 'ToscaNfvScalingAspect'
HEAT_NON_SCALING_RESOURCES = ['OS::Heat::ScalingPolicy',
                              'OS::Heat::AutoScalingGroup']
HOT_FLAVOR = 'OS::Nova::Flavor'
SCALING_ASPECT_DELTA = 'tosca.policies.nfv.VduScalingAspectDeltas'
VDU_CP = 'tosca.nodes.nfv.VduCp'
VDU_INITIAL_DELTA = 'tosca.policies.nfv.VduInitialDelta'


class ToscaNfvScalingAspect(HotResource):
    """Translate TOSCA policy type tosca.policies.nfv.ScalingAspects."""

    toscatype = 'tosca.policies.nfv.ScalingAspects'
    DESIRED_CAPACITY = 'DESIRED_CAPACITY'

    def __init__(self, policy, aspect_name, csar_dir=None,
                 hot_template_parameters=None):
        hot_type = "OS::Heat::ScalingPolicy"
        self.aspect_name = aspect_name
        super(ToscaNfvScalingAspect, self).__init__(
            policy,
            name=self.aspect_name,
            type=hot_type,
            csar_dir=csar_dir)
        self.policy = policy
        self.hot_template_parameters = hot_template_parameters

        # default
        self.scaling_adjustment = None
        self.vdu_name = None
        self.delta_name = None

    def handle_properties(self, resources):
        # Resources of non HotResource
        # (Exclude derived)
        non_hot_rsrcs = [r for r in resources if type(r) is not HotResource]

        # Extract resource name from VduScalingAspectDeltas
        vsad_rsrcs = [
            r for r in non_hot_rsrcs if r.toscatype == SCALING_ASPECT_DELTA]
        vsad_rsrcs = [
            r for r in vsad_rsrcs if r.aspect == self.aspect_name]
        vsad_rsrc = vsad_rsrcs[0]

        # The names of the resource associated with the VDU.
        # Supporting resources below.
        #  - tosca.nodes.nfv.Vdu.Compute
        #  - tosca.nodes.nfv.VduCp
        #  - tosca.nodes.nfv.Vdu.VirtualBlockStorage
        vdu_infos = [
            r for r in resources
            if vsad_rsrc.targets is not None and r.name in vsad_rsrc.targets]
        if vdu_infos == []:
            log.warning('Can not create %s node '
                        'because target vdu does not defined.'
                        % self.aspect_name)

        related_rsrc_names = []
        related_vl_names = []
        for vdu_info in vdu_infos:
            vdu_name = vdu_info.name
            port_rsrc_names = [p.name for p in vdu_info.assoc_port_resources]
            strg_rsrc_names = [s for s in vdu_info.virtual_storages]
            related_rsrc_names.append(vdu_name)
            related_rsrc_names.extend(port_rsrc_names)
            related_rsrc_names.extend(strg_rsrc_names)

            # Extract virtual_link mapping to vdu_name
            cp_rsrcs = [
                r for r in non_hot_rsrcs
                if r.toscatype == VDU_CP and r.virtual_binding == vdu_name]
            related_vl_names.extend([
                cp.virtual_link for cp in cp_rsrcs
                if cp.virtual_link is not None])

        # Parameters defined in referenced YAML
        parameters = {}

        # Resources of scaling/non-scaling
        scl_rsrcs = []
        non_scl_rsrcs = []

        # Properties of :AutoScalingGroup
        asg_props = {}

        # Resources which are contain related_rsrc_names are not
        # call handle_expansion(), so call here and add resources.
        related_rsrcs = [
            r for r in resources if r.name in related_rsrc_names]
        exp_rsrcs = list(
            chain.from_iterable([
                r.handle_expansion()
                for r in related_rsrcs
                if r.handle_expansion() is not None]))

        # Allocate resources generated by handle_expansion()
        # to scaling or non-scaling resource.
        # Flavor is non-scaling resource.
        scl_rsrcs.extend([
            r for r in exp_rsrcs
            if r.type != HOT_FLAVOR])
        non_scl_rsrcs.extend([
            r for r in exp_rsrcs
            if r.type == HOT_FLAVOR])

        for resource in resources:
            # Allocate resources to scaling or non-scaling resource.
            if resource.type not in HEAT_NON_SCALING_RESOURCES and \
                    resource.name in related_rsrc_names:
                scl_rsrcs.append(resource)
            else:
                non_scl_rsrcs.append(resource)

            # Processing for VDU
            if resource.name in related_rsrc_names and \
                resource.type == 'OS::Nova::Server':
                self.vdu_name = resource.name

                # Target aspect
                target_aspect = \
                    self.policy.properties['aspects'][self.aspect_name]

                # Extract scaling_adjustment from VduScalingAspectDeltas.
                # VduScalingAspectDeltas can specify a delta for each scaling
                # step but only the first one is used and others are ignored.
                delta_names = target_aspect['step_deltas']
                self.delta_name = delta_names[0]
                self.scaling_adjustment = vsad_rsrc.deltas.get(self.delta_name)

                # Extract min_size from VduInitialDelta
                vid_rsrcs = [
                    r for r in non_hot_rsrcs
                    if r.toscatype == VDU_INITIAL_DELTA]
                initial_deltas = [
                    r for r in vid_rsrcs
                    if r.targets is not None and self.vdu_name in r.targets]
                min_size = None \
                    if initial_deltas == [] else initial_deltas[0].num

                res = {}
                if min_size is not None and \
                        self.scaling_adjustment is not None:
                    # Calculate max_size
                    max_scale_level = target_aspect['max_scale_level']
                    max_size = \
                        min_size + max_scale_level * self.scaling_adjustment
                    res["min_size"] = min_size
                    res["max_size"] = max_size
                else:
                    log.warning('No min_size or(and) max_size is found for '
                                'aspect_name:%s, VDU:%s' % (
                                    self.aspect_name, self.vdu_name))

                # desired_capacity needs to be replaced by users because it is
                # calculated using scaling aspect and instantiation level given
                # in instantiation VNF request.
                res["desired_capacity"] = self.DESIRED_CAPACITY
                res['resource'] = {'type': self.aspect_name + '.hot.yaml'}

                props = {}
                props['type'] = resource.type
                props['properties'] = resource.properties

                for vl_name in related_vl_names:
                    vl_id = '%s_id' % (vl_name.lower())
                    asg_props.update({
                        vl_id: '{ get_resource: %s }' % (vl_name)})

                # Replace flavor id
                flvr_name = resource.flavor_resource_name
                flvr_id = '%s_id' % (flvr_name.lower())
                asg_props.update({
                    flvr_id: '{ get_resource: %s }' % (flvr_name)})
                resource.properties['flavor'] = \
                    '{ get_param: %s }' % (flvr_id)
                parameters[flvr_id] = {'type': 'string'}

                res['resource'].update({
                    'properties': asg_props
                })
                non_scl_rsrcs.append(
                    HotResource(resource,
                                type='OS::Heat::AutoScalingGroup',
                                name=self.aspect_name,
                                properties=res))

            # Processing for CP related to target VDU
            elif resource.name in related_rsrc_names and \
                resource.type == 'OS::Neutron::Port':
                for vl_name in related_vl_names:
                    if vl_name == resource.virtual_link:
                        # Replace network id
                        vl_id = '%s_id' % (vl_name.lower())
                        resource.properties['network'] = \
                            '{ get_param: %s }' % (vl_id)
                        parameters[vl_id] = {'type': 'string'}

        # Create referenced YAML which is defined scaling resources
        yaml_name = self.aspect_name + '.hot.yaml'
        parameters = None if parameters == {} else parameters
        nested_template = self._handle_nested_template(
            scl_rsrcs,
            yaml_name,
            self.hot_template_parameters,
            parameters=parameters)

        return non_scl_rsrcs, nested_template
