#!/usr/bin/python

import getafsgroups
import re
import string
from sipb_xen_database import Machine, NIC
from webcommon import InvalidInput, g

MAX_MEMORY_TOTAL = 512
MAX_MEMORY_SINGLE = 256
MIN_MEMORY_SINGLE = 16
MAX_DISK_TOTAL = 50
MAX_DISK_SINGLE = 50
MIN_DISK_SINGLE = 0.1
MAX_VMS_TOTAL = 10
MAX_VMS_ACTIVE = 4

def getMachinesByOwner(user, machine=None):
    """Return the machines owned by the same as a machine.
    
    If the machine is None, return the machines owned by the same
    user.
    """
    if machine:
        owner = machine.owner
    else:
        owner = user
    return Machine.select_by(owner=owner)

def maxMemory(user, machine=None, on=True):
    """Return the maximum memory for a machine or a user.

    If machine is None, return the memory available for a new 
    machine.  Else, return the maximum that machine can have.

    on is whether the machine should be turned on.  If false, the max
    memory for the machine to change to, if it is left off, is
    returned.
    """
    if not on:
        return MAX_MEMORY_SINGLE
    machines = getMachinesByOwner(user, machine)
    active_machines = [x for x in machines if g.uptimes[x]]
    mem_usage = sum([x.memory for x in active_machines if x != machine])
    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)

def maxDisk(user, machine=None):
    machines = getMachinesByOwner(user, machine)
    disk_usage = sum([sum([y.size for y in x.disks])
                      for x in machines if x != machine])
    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)

def cantAddVm(user):
    machines = getMachinesByOwner(user)
    active_machines = [x for x in machines if g.uptimes[x]]
    if len(machines) >= MAX_VMS_TOTAL:
        return 'You have too many VMs to create a new one.'
    if len(active_machines) >= MAX_VMS_ACTIVE:
        return ('You already have the maximum number of VMs turned on.  '
                'To create more, turn one off.')
    return False

def validAddVm(user):
    reason = cantAddVm(user)
    if reason:
        raise InvalidInput('create', True, reason)
    return True

def haveAccess(user, machine):
    """Return whether a user has adminstrative access to a machine"""
    if user == 'moo':
        return True
    if user in (machine.administrator, machine.owner):
        return True
    if getafsgroups.checkAfsGroup(user, machine.administrator, 
                                  'athena.mit.edu'): #XXX Cell?
        return True
    if getafsgroups.checkLockerOwner(user, machine.owner):
        return True
    return owns(user, machine)

def owns(user, machine):
    """Return whether a user owns a machine"""
    if user == 'moo':
        return True
    return getafsgroups.checkLockerOwner(user, machine.owner)

def validMachineName(name):
    """Check that name is valid for a machine name"""
    if not name:
        return False
    charset = string.ascii_letters + string.digits + '-_'
    if name[0] in '-_' or len(name) > 22:
        return False
    for x in name:
        if x not in charset:
            return False
    return True

def validMemory(user, memory, machine=None, on=True):
    """Parse and validate limits for memory for a given user and machine.

    on is whether the memory must be valid after the machine is
    switched on.
    """
    try:
        memory = int(memory)
        if memory < MIN_MEMORY_SINGLE:
            raise ValueError
    except ValueError:
        raise InvalidInput('memory', memory, 
                           "Minimum %s MiB" % MIN_MEMORY_SINGLE)
    if memory > maxMemory(user, machine, on):
        raise InvalidInput('memory', memory,
                           'Maximum %s MiB' % maxMemory(user, machine))
    return memory

def validDisk(user, disk, machine=None):
    """Parse and validate limits for disk for a given user and machine."""
    try:
        disk = float(disk)
        if disk > maxDisk(user, machine):
            raise InvalidInput('disk', disk,
                               "Maximum %s G" % maxDisk(user, machine))
        disk = int(disk * 1024)
        if disk < MIN_DISK_SINGLE * 1024:
            raise ValueError
    except ValueError:
        raise InvalidInput('disk', disk,
                           "Minimum %s GiB" % MIN_DISK_SINGLE)
    return disk
            
def testMachineId(user, machine_id, exists=True):
    """Parse, validate and check authorization for a given user and machine.

    If exists is False, don't check that it exists.
    """
    if machine_id is None:
        raise InvalidInput('machine_id', machine_id, 
                           "Must specify a machine ID.")
    try:
        machine_id = int(machine_id)
    except ValueError:
        raise InvalidInput('machine_id', machine_id, "Must be an integer.")
    machine = Machine.get(machine_id)
    if exists and machine is None:
        raise InvalidInput('machine_id', machine_id, "Does not exist.")
    if machine is not None and not haveAccess(user, machine):
        raise InvalidInput('machine_id', machine_id,
                           "You do not have access to this machine.")
    return machine

def testAdmin(user, admin, machine):
    if admin in (None, machine.administrator):
        return None
    if admin == user:
        return admin
    if getafsgroups.checkAfsGroup(user, admin, 'athena.mit.edu'):
        return admin
    if getafsgroups.checkAfsGroup(user, 'system:'+admin,
                                  'athena.mit.edu'):
        return 'system:'+admin
    return admin
    
def testOwner(user, owner, machine=None):
    if owner == user or machine is not None and owner == machine.owner:
        return owner
    if owner is None:
        raise InvalidInput('owner', owner, "Owner must be specified")
    value = getafsgroups.checkLockerOwner(user, owner, verbose=True)
    if not value:
        return owner
    raise InvalidInput('owner', owner, value)

def testContact(user, contact, machine=None):
    if contact in (None, machine.contact):
        return None
    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
        raise InvalidInput('contact', contact, "Not a valid email.")
    return contact

def testDisk(user, disksize, machine=None):
    return disksize

def testName(user, name, machine=None):
    if name in (None, machine.name):
        return None
    if not Machine.select_by(name=name):
        return name
    raise InvalidInput('name', name, "Name is already taken.")

def testHostname(user, hostname, machine):
    for nic in machine.nics:
        if hostname == nic.hostname:
            return hostname
    # check if doesn't already exist
    if NIC.select_by(hostname=hostname):
        raise InvalidInput('hostname', hostname,
                           "Already exists")
    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
                           "must only use number, letters, and dashes.")
    return hostname
