#!/usr/bin/python2

# -*- coding: utf-8 -*-

# TODO:
# Ability to set CPU/Memory reservations

try:
    import json
except ImportError:
    import simplejson as json

DOCUMENTATION = '''
---
module: vsphere_client
short_description: Creates a virtual guest on vsphere.
description:
     - Communicates with vsphere, creating a new virtual guest OS based on
       the specifications you specify to the module.
version_added: "1.1"
options:
  vcenter_hostname:
    description:
      - The hostname of the vcenter server the module will connect to, to create the guest.
    required: true
    default: null
    aliases: []
  user:
    description:
      - username of the user to connect to vcenter as.
    required: true
    default: null
  password:
    description:
      - password of the user to connect to vcenter as.
    required: true
    default: null
  resource_pool:
    description:
      - The name of the resource_pool to create the VM in.
    required: false
    default: None
  cluster:
    description:
      - The name of the cluster to create the VM in. By default this is derived from the host you tell the module to build the guest on.
    required: false
    default: None
  datacenter:
    description:
      - The name of the datacenter to create the VM in.
    required: true
    default: null
  datastore:
    description:
      - The datastore to store the VMs config files in. (Hard-disk locations are specified separately.)
    required: true
    default: null
  esxi_hostname:
    description:
      - The hostname of the esxi host you want the VM to be created on.
    required: true
    default: null
  power_on:
    description:
      - Whether or not to power on the VM after creation.
    required: false
    default: no
    choices: [yes, no]
  vm_name:
    description:
      - The name you want to call the VM.
    required: true
    default: null
  vm_memory_mb:
    description:
      - How much memory in MB to give the VM.
    required: false
    default: 1024
  vm_num_cpus:
    description:
      - How many vCPUs to give the VM.
    required: false
    default: 1
  vm_scsi:
    description:
      - The type of scsi controller to add to the VM.
    required: false
    default: "paravirtual"
    choices: [paravirtual, lsi, lsi_sas, bus_logic]
  vm_disk:
    description:
      - A key, value list of disks and their sizes and which datastore to keep it in.
    required: false
    default: null
  vm_nic:
    description:
      - A key, value list of nics, their types and what network to put them on.
    required: false
    default: null
    choices: [vmxnet3, vmxnet2, vmxnet, e1000, e1000e, pcnet32]
  vm_notes:
    description:
      - Any notes that you want to show up in the VMs Annotations field.
    required: false
    default: null
  vm_cdrom:
    description:
      - A path, including datastore, to an ISO you want the CDROM device on the VM to have.
    required: false
    default: null
  vm_extra_config:
    description:
      - A key, value pair of any extra values you want set or changed in the vmx file of the VM. Useful to set advanced options on the VM.
    required: false
    default: null
  guestosid:
    description:
      - "A vmware guest needs to have a specific OS identifier set on it
        during creation. You can find your os guestosid at the following URL:
      http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html"
    required: true
    default: null
# informational: requirements for nodes
requirements: [ pysphere ]
author: Romeo Theriault
'''


def power_state(vm, state, force):

    power_status = vm.get_status()

    check_status = ' '.join(state.split("_")).upper()

    # Need Force
    if not force and power_status in [
        'SUSPENDED', 'POWERING ON',
        'RESETTING', 'BLOCKED ON MSG'
    ]:

        return "VM is in %s power state. Force is required!" % power_status

    # State is already true
    if power_status == check_status:
        return False

    else:
        try:
            if state == 'powered_off':
                vm.power_off(sync_run=True)

            elif state == 'powered_on':
                vm.power_on(sync_run=True)

            elif state == 'restarted':
                if power_status in ('POWERED ON', 'POWERING ON', 'RESETTING'):
                    vm.reset(sync_run=False)
                else:
                    return "Cannot restart VM in the current state %s" \
                        % power_status
            return True

        except Exception, e:
            return e

    return False


def gather_facts(vm):
    """
    Gather facts for VM directly from vsphere.
    """
    vm.get_properties()
    facts = {
        'module_hw': True,
        'hw_name': vm.properties.name,
        'hw_guest_full_name':  vm.properties.config.guestFullName,
        'hw_guest_id': vm.properties.config.guestId,
        'hw_product_uuid': vm.properties.config.uuid,
        'hw_processor_count': vm.properties.config.hardware.numCPU,
        'hw_memtotal_mb': vm.properties.config.hardware.memoryMB,
    }

    ifidx = 0
    for entry in vm.properties.config.hardware.device:

        if not hasattr(entry, 'macAddress'):
            continue

        factname = 'hw_eth' + str(ifidx)
        facts[factname] = {
            'addresstype': entry.addressType,
            'label': entry.deviceInfo.label,
            'macaddress': entry.macAddress,
            'macaddress_dash': entry.macAddress.replace(':', '-'),
            'summary': entry.deviceInfo.summary,
        }

        ifidx += 1

    return facts


class DefaultVMConfig(object):

    def __init__(self, check_dict, interface_dict):
        self.check_dict, self.interface_dict = check_dict, interface_dict
        self.set_current, self.set_past = set(
            check_dict.keys()), set(interface_dict.keys())
        self.intersect = self.set_current.intersection(self.set_past)
        self.recursive_missing = None

    def shallow_diff(self):
        return self.set_past - self.intersect

    def recursive_diff(self):

        if not self.recursive_missing:
            self.recursive_missing = []
            for key, value in self.interface_dict.items():
                if isinstance(value, dict):
                    for k, v in value.items():
                        if k in self.check_dict[key]:
                            if not isinstance(self.check_dict[key][k], v):
                                self.recursive_missing.append((k, v))
                        else:
                            self.recursive_missing.append((k, v))

        return self.recursive_missing


def config_check(name, passed, default, module):

    diff = DefaultVMConfig(passed, default)
    if len(diff.shallow_diff()):
        module.fail_json(
            msg="Missing required key/pair [%s]. %s must contain %s" %
                (', '.join(diff.shallow_diff()), name, default))

    if diff.recursive_diff():
        module.fail_json(
            msg="Config mismatch for %s on %s" %
                (name, diff.recursive_diff()))

    return True


def main():

    vm = None

    proto_vm_hardware = {
        'memory_mb': int,
        'num_cpus': int
    }

    proto_vm_disk = {
        'disk1': {
            'size_gb': int,
            'type': basestring
        }
    }

    proto_vm_nic = {
        'nic1': {
            'type': basestring,
            'network': basestring,
            'network_type': basestring
        }
    }

    proto_esxi = {
        'datastore': basestring,
        'datacenter': basestring,
        'hostname': basestring
    }

    module = AnsibleModule(
        argument_spec=dict(
            vcenter_hostname=dict(required=True, type='str'),
            username=dict(required=True, type='str'),
            password=dict(required=True, type='str'),
            state=dict(
                required=False,
                choices=[
                    'powered_on',
                    'powered_off',
                    'present',
                    'absent',
                    'restarted',
                    'reconfigured'
                ],
                default='present'),
            vmware_guest_facts=dict(required=False, choices=BOOLEANS),
            guest=dict(required=True, type='str'),
            vm_disk=dict(required=False, type='dict', default={}),
            vm_boot_state=dict(
                required=False,
                choices=[
                    'powered_on',
                    'powered_off',
                ],
                default='powered_on'),
            vm_nic=dict(required=False, type='dict', default={}),
            vm_hardware=dict(required=False, type='dict', default={}),
            vm_extra_config=dict(required=False, type='dict', default={}),
            force=dict(required=False, choices=BOOLEANS, default=False),
            esxi=dict(required=False, type='dict', default={}),


        ),
        supports_check_mode=False,
        mutually_exclusive=[['state', 'vmware_guest_facts']],
        required_together=[
            ['state', 'force'],
            [
                'state',
                'vm_disk',
                'vm_boot_state',
                'vm_nic',
                'vm_hardware',
                'esxi'
            ]
        ],
    )

    try:
        from pysphere import VIServer, VIProperty, MORTypes
        from pysphere.resources import VimService_services as VI
        from pysphere.vi_task import VITask
        from pysphere import VIException, VIApiException, FaultTypes
    except ImportError, e:
        module.fail_json(msg='pysphere module required')

    vcenter_hostname = module.params['vcenter_hostname']
    username = module.params['username']
    password = module.params['password']
    vmware_guest_facts = module.params['vmware_guest_facts']
    state = module.params['state']
    guest = module.params['guest']
    force = module.params['force']
    vm_disk = module.params['vm_disk']
    vm_boot_state = module.params['vm_boot_state']
    vm_nic = module.params['vm_nic']
    vm_hardware = module.params['vm_hardware']
    vm_extra_config = module.params['vm_extra_config']
    esxi = module.params['esxi']

    # CONNECT TO THE SERVER
    viserver = VIServer()
    try:
        viserver.connect(vcenter_hostname, username, password)
    except VIApiException, err:
        module.fail_json(msg="Cannot connect to %s: %s" %
                         (vcenter_hostname, err))

    # Check if the VM exists before continuing
    try:
        vm = viserver.get_vm_by_name(guest)

        # Run for facts only
        if vmware_guest_facts:
            try:
                module.exit_json(ansible_facts=gather_facts(vm))
            except Exception, e:
                module.fail_json(
                    msg="Fact gather failed with exception %s" % e)

        # Power Changes
        elif state in ['powered_on', 'powered_off', 'restarted']:
            state_result = power_state(vm, state, force)

            # Failure
            if isinstance(state_result, basestring):
                module.fail_json(msg=state_result)
            else:
                module.exit_json(changed=state_result)

        # Just check if there
        elif state == 'present':
            module.exit_json(changed=False)

        # Fail on reconfig without params
        elif state == 'reconfigured':
            pass

    # VM doesn't exist
    except Exception:

        # Fail for fact gather task
        if vmware_guest_facts:
            module.fail_json(
                msg="No such VM %s. Fact gathering requires an existing vm"
                    % guest)
        if state not in ['absent', 'present']:
            module.fail_json(
                msg="No such VM %s. States [powered_on, powered_off, "
                "restarted, reconfigured] required an existing VM" % guest)
        elif state == 'absent':
            module.exit_json(changed=False, msg="vm %s not present" % guest)

        # Create the VM
        elif state == 'present':

            # Check the guest_config
            config_check("vm_disk", vm_disk, proto_vm_disk, module)
            config_check("vm_nic", vm_nic, proto_vm_nic, module)
            config_check("vm_hardware", vm_hardware, proto_vm_hardware, module)
            config_check("esxi", esxi, proto_esxi, module)

    if vm:
        # If the vm already exists, lets get some info from it, pass back the
        # vm's vmware_guest_facts and then exit.
        viserver.disconnect()
    module.exit_json(
        changed=False,
        vcenter=vcenter_hostname)


# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
