from .api import API
from .statemonitor import StateMonitorProcess
from expyrimenter import Executor, SSH
from time import sleep
import threading
import logging


class CloudStack:
    _id_cache = None

    def __init__(s, executor=None, api=None):
        if executor is None:
            executor = Executor()
        if api is None:
            api = API()

        s.executor = executor
        s._api = api
        s._logger = logging.getLogger('cloudstack')

        s._sm_lock = threading.Lock()
        s._sm_users = 0

    def get_states(s):
        vms = s._api.listVirtualMachines()['virtualmachine']
        return {vm['name']: vm['state'] for vm in vms}

    def get_id(s, name):
        cache = CloudStack._id_cache
        if cache is None or name not in cache:
            s.load_id_cache()

        return CloudStack._id_cache[name]

    def get_state(s, name):
        vm_id = s.get_id(name)
        vms = s._api.listVirtualMachines(id=vm_id)['virtualmachine']
        return vms[0]['state']

    def start(s, *names):
        names = s._ensure_lists(names)
        for name in names:
            s._logger.info('Starting %s' % name)
            vm_id = s.get_id(name)
            s._submit_sm_user(start_vm, 'start VM', name, vm_id)

    def stop(s, *names):
        names = s._ensure_lists(names)
        for name in names:
            vm_id = s.get_id(name)
            s.executor.run_fn(stop_vm, 'stop VM', name, vm_id)

    def get_deploy_params(s, name):
        vm_id = s.get_id(name)
        vm = s._api.listVirtualMachines(id=vm_id)['virtualmachine'][0]
        keys = ['serviceofferingid', 'templateid', 'zoneid']

        params = {}
        for key in keys:
            params[key] = vm[key]

        return params

    def deploy(s, params, **kwargs):
        if kwargs:
            params.update(kwargs)
        s._submit_sm_user(deploy_vm, 'deploy VM', params)

    def deploy_like(s, existent, new, **kwargs):
        params = s.get_deploy_params(existent)
        params['name'] = new
        s.deploy(params, **kwargs)

    def load_id_cache(s):
        vms = s._api.listVirtualMachines()['virtualmachine']
        CloudStack._id_cache = {vm['name']: vm['id'] for vm in vms}
        return CloudStack._id_cache

    def _submit_sm_user(s, fn, title, *args, **kwargs):
        with s._sm_lock:
            s._sm_users += 1
            StateMonitorProcess.start()

        states = StateMonitorProcess.get_states()
        args += (states,)
        future = s.executor.run_fn(fn, title, *args, **kwargs)
        future.add_done_callback(s._sm_user_done)

        return future

    def _sm_user_done(s, future):
        with s._sm_lock:
            s._sm_users -= 1
            if s._sm_users == 0:
                StateMonitorProcess.stop()

    def _ensure_lists(s, args):
        lists = []
        for arg in args:
            if isinstance(arg, list):
                lists += arg
            else:
                lists.append(arg)
        return lists


def stop_vm(name, vm_id):
    api = API()
    api.stopVirtualMachine(id=vm_id)
    msg = 'Sent %s stop request.' % name
    logging.getLogger('cloudstack').debug(msg)


def start_vm(name, vm_id, states):
    api = API()
    api.startVirtualMachine(id=vm_id)
    wait_ssh(name, states)
    msg = '%s is up.' % name
    logging.getLogger('cloudstack').info(msg)


def deploy_vm(params, states):
    api = API()
    api.deployVirtualMachine(**params)
    log = logging.getLogger('cloudstack')
    if 'startvm' in params and params['startvm'] is False:
        msg = '%s is deployed.' % params['name']
        log.debug(msg)
    else:
        wait_ssh(params['name'], states)
        msg = '%s is ready for SSH.' % params['name']
        log.info(msg)


def wait_ssh(name, states):
    wait_state(name, 'Running', states)
    SSH.await_availability(name)


def wait_state(name, state, states, interval=5):
    while True:
        if state == states.get(name):
            break
        sleep(interval)
