# -*- coding: utf-8 -*-
from threading import Lock
from pyVmomi import vim


class DvPortGroupCreator(object):
    def __init__(self, pyvmomi_service, synchronous_task_waiter):
        """

        :param pyvmomi_service:
        :param synchronous_task_waiter:
        :type synchronous_task_waiter: cloudshell.cp.vcenter.common.vcenter.task_waiter.SynchronousTaskWaiter
        :return:
        """
        self.pyvmomi_service = pyvmomi_service
        self.synchronous_task_waiter = synchronous_task_waiter
        self._lock = Lock()

    def get_or_create_network(self,
                              si,
                              vm,
                              dv_port_name,
                              dv_switch_name,
                              dv_switch_path,
                              vlan_id,
                              vlan_spec,
                              logger,
                              promiscuous_mode):
        network = None
        error = None
        self._lock.acquire()
        try:
            # check if the network is attached to the vm and gets it, the function doesn't goes to the vcenter
            network = self.pyvmomi_service.get_network_by_name_from_vm(vm, dv_port_name)

            # if we didn't found the network on the vm
            if network is None:
                # try to get it from the vcenter
                try:
                    network = self.pyvmomi_service.find_portgroup(si,
                                                                  '{0}/{1}'.format(dv_switch_path, dv_switch_name),
                                                                  dv_port_name)
                except KeyError:
                    logger.debug("Failed to find port group for {}".format(dv_port_name), exc_info=True)
                    network = None

            # if we still couldn't get the network ---> create it(can't find it, play god!)
            if network is None:
                self._create_dv_port_group(dv_port_name,
                                           dv_switch_name,
                                           dv_switch_path,
                                           si,
                                           vlan_spec,
                                           vlan_id,
                                           logger,
                                           promiscuous_mode)
                network = self.pyvmomi_service.find_network_by_name(si, dv_switch_path, dv_port_name)

            if not network:
                raise ValueError('Could not get or create vlan named: {0}'.format(dv_port_name))
        except ValueError as e:
            logger.debug("Failed to find network", exc_info=True)
            error = e
        finally:
            self._lock.release()
            if error:
                raise error
            return network

    def _create_dv_port_group(self, dv_port_name, dv_switch_name, dv_switch_path, si, spec, vlan_id,
                              logger, promiscuous_mode):
        dv_switch = self.pyvmomi_service.get_folder(si, '{0}/{1}'.format(dv_switch_path, dv_switch_name))
        if not dv_switch:
            raise ValueError('DV Switch {0} not found in path {1}'.format(dv_switch_name, dv_switch_path))

        task = DvPortGroupCreator.dv_port_group_create_task(dv_port_name, dv_switch, spec, vlan_id,
                                                            logger, promiscuous_mode)
        self.synchronous_task_waiter.wait_for_task(task=task,
                                                   logger=logger,
                                                   action_name='Create dv port group',
                                                   hide_result=False)

    @staticmethod
    def dv_port_group_create_task(dv_port_name, dv_switch, spec, vlan_id, logger, promiscuous_mode, num_ports=32):
        """
        Create ' Distributed Virtual Portgroup' Task
        :param dv_port_name: <str>  Distributed Virtual Portgroup Name
        :param dv_switch: <vim.dvs.VmwareDistributedVirtualSwitch> Switch this Portgroup will be belong to
        :param spec:
        :param vlan_id: <int>
        :param logger:
        :param num_ports: <int> number of ports in this Group
        :param promiscuous_mode <str> 'True' or 'False' turn on/off promiscuous mode for the port group
        :return: <vim.Task> Task which really provides update
        """

        dv_pg_spec = vim.dvs.DistributedVirtualPortgroup.ConfigSpec()
        dv_pg_spec.name = dv_port_name
        dv_pg_spec.numPorts = num_ports
        dv_pg_spec.type = vim.dvs.DistributedVirtualPortgroup.PortgroupType.earlyBinding

        dv_pg_spec.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy()
        dv_pg_spec.defaultPortConfig.securityPolicy = vim.dvs.VmwareDistributedVirtualSwitch.SecurityPolicy()

        dv_pg_spec.defaultPortConfig.vlan = spec
        dv_pg_spec.defaultPortConfig.vlan.vlanId = vlan_id

        promiscuous_mode = promiscuous_mode.lower() == 'true'
        dv_pg_spec.defaultPortConfig.securityPolicy.allowPromiscuous = vim.BoolPolicy(value=promiscuous_mode)
        dv_pg_spec.defaultPortConfig.securityPolicy.forgedTransmits = vim.BoolPolicy(value=True)

        dv_pg_spec.defaultPortConfig.vlan.inherited = False
        dv_pg_spec.defaultPortConfig.securityPolicy.macChanges = vim.BoolPolicy(value=False)
        dv_pg_spec.defaultPortConfig.securityPolicy.inherited = False

        task = dv_switch.AddDVPortgroup_Task([dv_pg_spec])

        logger.info(u"DV Port Group '{}' CREATE Task ...".format(dv_port_name))
        return task

    @staticmethod
    def dv_port_group_destroy_task(port_group):
        """
        Creates 'Destroy Distributed Virtual Portgroup' Task
        The current Portgoriup should be 'Unused' if you wanted this task will be successfully performed
        :param port_group: <vim.dvs.DistributedVirtualPortgroup>
        :return: <vim.Task> Task which really provides update
        """
        return port_group.Destroy()
