import gevent
from gevent import monkey
monkey.patch_all()

from CloudProviderFramework import *
from libcloud.compute.types import Provider
from libcloud.compute.types import NodeState
from libcloud.compute.providers import get_driver
from libcloud.compute.base import NodeAuthPassword
from cloud_provider_plugin_base import CloudProviderPluginBase
from concurrent.futures import ThreadPoolExecutor
import threading
import sys
import re
import uuid
import requests
import json
import os

from gevent import getcurrent
from gevent.pool import Pool
from gevent.pool import Group

POOL_SIZE = 16

class CloudProviderPlugin(CloudProviderPluginBase):
    def __init__(self, pluginHolder):
        CloudProviderPluginBase.__init__(self, pluginHolder)
        self.cls = get_driver(Provider.EC2)
        self.drivers = []
        self.greenlets = []
        self.driversDict = dict()
        self.pool = Pool(POOL_SIZE)
        self.user_id = None
        self.project_id = None
        self.key = None
        self.loc = None
        self.zone = None
        #self.provider_name = "dummy_provider"
        self.resource_group_name = "dummy_group"
        self.location_name = "dummy_location"
        self.sku_name = "dummy_sku"
        self.vm_name = "dummy_vm"
        self.vm_ip_address = ""
        self.multithreaded = False

    ### tools
    def get_exc_message(self):
        infos = sys.exc_info()
        message = '' 
        for info in infos:
            message = message + str(info) + ' '
        return message

    def spawn(self, func, *args, **kwargs):
            if self.multithreaded:
                parent = gevent.getcurrent()
                
                def on_error(subtask):
                    parent.throw(subtask.exception)
                
                g = self.pool.spawn(func, *args, **kwargs)
                g.link_exception(on_error)
            else:
               func(*args, **kwargs) 

    def handle_exception(self, greenlet):
        try:
            self.set_error("Error")
        except Exception as exc:
            pass
    
    ###
    def initialize(self):
        self.user_id = self.subscriber_id()
        self.project_id = self.client_id()
        self.key = self.password()
        self.zone = self.location()
        self.provider_name = self.name()
    
    def update(self):
        self.pool.join(timeout=0.001)  
    ###

    # list locations
    def list_locations(self):
        self.spawn(self.process_list_locations)

    def process_list_locations(self):
        response = ListLocationsResponse()
        location_names = [self.location_name]             
        response.locations = location_names  
        self.list_locations_finished(response)

    # list resource groups (instance group in GCE)
    def list_resource_groups(self):
        self.spawn(self.process_list_resource_groups)
    
    def process_list_resource_groups(self):
        response = ListResourceGroupsResponse()
        group_names = [self.resource_group_name]
        response.resource_group_names = group_names
        self.list_resource_groups_finished(response)

    # create a resource group (instance group in GCE)
    def create_resource_group(self, resourceGroupName, location, vmConfig):
        self.spawn(self.process_create_resource_group, resourceGroupName, location, vmConfig)
    
    def process_create_resource_group(self, resourceGroupName, location, vmConfig):
        response = CreateResourceGroupResponse()
        self.resource_group_name = resourceGroupName
        self.create_resource_group_finished(response)
    
    # delete a resource group (placement group in GCE)
    def delete_resource_group(self, resourceGroupName):
        self.spawn(self.process_create_resource_group, resourceGroupName)
    
    def process_delete_resource_group(self, resourceGroupName):
        pass

    # list all available VM sizes (number of cores, RAM, HDD, etc.) in a location
    def list_skus(self, location):
        self.spawn(self.process_list_skus, location)

    def process_list_skus(self, location):
        skuResponse = ListSkusResponse()
        skuResponse.provider_name = self.provider_name
        priceResponse = ListVmPricesResponse()
        priceResponse.provider_name = self.provider_name

        priceDict = dict()
        sku_dict = dict()
        prices = []

        sku_info = SkuInfo()
        price_info = VmPriceInfo()
        sku_info.name = self.sku_name
        sku_info.memory_size = 24
        sku_info.os_disk_size = 30
        price_info.sku_name = self.sku_name 
        price_info.unit_of_measure = ""
        price_info.unit_price = -1
        sku_dict[self.sku_name] = sku_info

        skuResponse.sku_info_map = sku_dict
        priceResponse.vm_prices = prices

        self.list_skus_finished(skuResponse)
        self.list_vm_prices_finished(priceResponse)
        
    # list all available VM sizes (number of cores, RAM, HDD, etc.) in a location
    def list_vm_sizes(self, resourceGroupName, location):
        self.spawn(self.process_list_vm_sizes, resourceGroupName, location)
    
    def process_list_vm_sizes(self, resourceGroupName, location):
        response = VmSizesResponse()
        size_names = [self.sku_name]

        response.vm_sizes = size_names
        self.list_vm_sizes_finished(response)
        
    # list all VMs in a resource group (instance group for GCE)
    def list_vms(self, resourceGroupName):
        self.spawn(self.process_list_vms, resourceGroupName)
        
    def process_list_vms(self, resourceGroupName):
        response = VirtualMachinesResponse()
        response.resource_group_name = self.resource_group_name
        nic_response = NetworkInterfacesResponse()
        
        vm_dict = dict()
        nic_dict = dict()
        
        if self.vm_ip_address != "":
            vm_name = self.resource_group_name.removeprefix("rrc-")
            vm_info = VmInfo()
            vm_info.instance_name = vm_name
            vm_info.resource_group_name = self.resource_group_name
            vm_dict[vm_name] = vm_info
            

            nic_info = NicInfo()
            nic_info.name = vm_name
            nic_info.ip_address = self.vm_ip_address
            nic_dict[vm_name] = nic_info
            
        response.vm_info_map = vm_dict
        nic_response.nic_info_map = nic_dict
        
        self.list_vms_finished(response)
        self.list_nics_finished(nic_response)
    
    # list nics (private/public IPs) of VMs in a given resource group (instance group in GCE)
    def list_nics(self, resourceGroupName):
        self.spawn(self.process_list_nics, resourceGroupName)
    
    def process_list_nics(self, resourceGroupName):
        nic_response = NetworkInterfacesResponse()
        nic_dict = dict()

        vm_name = self.resource_group_name.removeprefix("rrc-")
        nic_info = NicInfo()
        nic_info.name = vm_name
        nic_info.ip_address = self.vm_ip_address
        nic_dict[vm_name] = nic_info
        
        nic_response.nic_info_map = nic_dict
        self.list_nics_finished(nic_response)
        
    # get the state of a given VM (running, starting, stopping, etc.)
    def get_vm_info(self, vmName, resourceGroupName):
        self.spawn(self.process_get_vm_info, vmName, resourceGroupName)

    def process_get_vm_info(self, vmName, resourceGroupName):
        response = InstanceResponse()

        vm_name = self.resource_group_name.removeprefix("rrc-")
        response.vm_name = vm_name
        
        response.power_state = RestPowerState.PS_ReadyRole
        response.power_state_string = 'running'
            
        self.get_vm_info_finished(response)
    
    # add new VMs to a given resource group (instance group in GCE)
    def add_vms(self, vmNames, vmConfig, resourceGroupName):
        self.spawn(self.process_add_vms, vmNames, vmConfig, resourceGroupName)
        
    def process_add_vms(self, vmNames, vmConfig, resourceGroupName):
        response = CreateVMsResponse()
        self.vm_ip_address = vmConfig.cloud_connector_config.vm_image_name
        self.add_vms_finished(response)
        
    # delete a VM from a given resource group (instance group in GCE)
    def delete_vm(self, vmName, resourceGroupName):
        self.pool.spawn(self.process_delete_vm, vmName, resourceGroupName)
        
    def process_delete_vm(self, vmName, resourceGroupName):
        pass
    
    # starts VMs in a given resource group (instance group in GCE)
    def start_vms(self, vmNames, resourceGroupName):
        self.spawn(self.process_start_vms, vmNames, resourceGroupName)
        
    def process_start_vms(self, vmNames, resourceGroupName):
        response = VirtualMachineNameResponse()
        self.start_vms_finished(response)
    
    # shutdown VMs in a resource group (instance group in GCE)
    def shutdown_vms(self, vmNames, resourceGroupName):
        self.spawn(self.process_shutdown_vms, vmNames, resourceGroupName)
        
    def process_shutdown_vms(self, vmNames, resourceGroupName):
        response = VirtualMachineNameResponse()
        self.shutdown_vms_finished(response)

    # shutdown VMs in a resource group
    def simulate_vm_eviction(self, vmName, resourceGroupName):
        self.spawn(self.process_simulate_vm_eviction, vmName, resourceGroupName)
        
    def process_simulate_vm_eviction(self, vmName, resourceGroupName):
        self.process_shutdown_vms(vmName, resourceGroupName)
    
    def generalize_vm(self, vmName, resourceGroupName):
        self.process_generalize_vm(vmName, resourceGroupName)
            
    def process_generalize_vm(self, vmName, resourceGroupName):
        pass
     
    def capture_image_from_vm(self, imageName, vmName, resourceGroupName):
        self.process_capture_image_from_vm(imageName, vmName, resourceGroupName)
        
    def process_capture_image_from_vm(self, imageName, vmName, resourceGroupName):
        pass
    
    # create a new virtual network with a given config
    # this function creates the following resources:
    # 1. VPN with render subnet and VPN subnet
    # 2. Firewall entries allowing ssh and vpn traffic
    # 3. (optional) a new VM which hosts the OpenVPN server with static address
    # 4. (optional) additional routes
    def create_virtual_network(self, resourceGroupName, virtualNetworkConfig):
        self.spawn(self.process_create_virtual_network, resourceGroupName, virtualNetworkConfig)
        
    def process_create_virtual_network(self, resourceGroupName, virtualNetworkConfig):
        response = RestResponse()
        deployment_response = DeploymentInfoResponse()
            
        self.create_virtual_network_finished(response)
        self.get_deployment_info_finished(deployment_response)
        
    def create_uuid(context):
        random_uuid = context + '_' + str(uuid.uuid4())
        return random_uuid
        
