# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import sys
from types import NoneType

from vagrantplaybook.compat import compat_integer_types
from vagrantplaybook.ansible import ansible_unwrap

from vagrantplaybook.errors import ValueGeneratorError, ValueGeneratorTypeError
from vagrantplaybook.compose.node import Node

class NodeGroup:
    '''
    This class defines a group of nodes, representig a set of vagrant machines with similar characteristics.
    Nodes will be composed by NodeGroup.compose method, according with the configuration of values/value_generator
    of the group of node itself.
    '''

    def __init__(self, index, name, instances, box, boxname, hostname, fqdn, aliases, ip, cpus, memory, ansible_groups, attributes):
        '''Creates a new NodeGroup.

        Keyword arguments:
        index           -- A number identifying the group of nodes withing the cluster.
        name            -- The name of the group of nodes.
        instances       -- The number of nodes/instances to be created in the group of nodes.
        box             -- The value/value generator to be used for assigning to each node in this group a base box to be used for creating vagrant machines implementing nodes in this group.
        boxname         -- The value/value generator to be used for assigning to each node in this group a box name a.k.a. the name for the machine in VirtualBox/VMware console.
        hostname        -- The value/value generator to be used for assigning to each node in this group a unique hostname.
        fqdn            -- The value/value generator to be used for assigning to each node in this group a unique fqdn.
        aliases         -- The value/value generator to be used for assigning to each node in this group a unique list of aliases a.k.a. (comma separated list of alias) alternative host names.
        ip              -- The value/value generator to be used for assigning to each node in this groupa unique ip.
        cpus            -- The value/value generator to be used for assigning to each node in this group cpus.
        memory          -- The value/value generator to be used for assigning to each node in this group memory.
        ansible_groups  -- The value/value generator to be used for assigning each node in this group to a list of ansible groups.
        attributes      -- The value/value generator to be used for assigning a dictionary with custom attributes - Hash(String, obj) - to each node in this group.
        '''

        self.index          = index
        self.name           = name
        self.instances      = instances
        self.box            = box
        self.boxname        = boxname
        self.hostname       = hostname
        self.fqdn           = fqdn
        self.aliases        = aliases
        self.ip             = ip
        self.cpus           = cpus
        self.memory         = memory
        self.ansible_groups = ansible_groups
        self.attributes     = attributes

    def compose(self, templar, cluster_name, cluster_node_prefix, cluster_domain, cluster_offset):
        ''' Composes the group of nodes, by creating the required number of nodes in accordance with values/value generators.

        Additionally:
        * some "embedded" trasformation will be applied to attributes:
          * boxname (prefied by cluster_name, if defined)
          * hostname (prefied by cluster_name, if defined)
        * some "autogenerated" node properties will be computed:
          * fqdn (hostname + cluster_domain, if defined)

        Keyword arguments:
        cluster_name        -- The name of the cluster
        cluster_node_prefix -- A prefix to be added before each node name / box name
        cluster_domain      -- The domain to which the cluster belongs
        cluster_offset      -- The offset - the initial group_index - to be used for nodes in the nodegroup
        '''

        node_index = 0
        while node_index < self.instances:

          available_variables = dict(
            cluster_name = cluster_name,
            cluster_node_prefix = cluster_node_prefix,
            cluster_domain = cluster_domain,
            group_index = self.index,
            group_name = self.name,
            node_index = node_index 
          )

          box, available_variables            = self._generate(templar, 'box', self.box, available_variables)
          boxname, available_variables        = self._generate(templar, 'boxname', self.boxname, available_variables)
          hostname, available_variables       = self._generate(templar, 'hostname', self.hostname, available_variables)
          aliases, available_variables        = self._generate(templar, 'aliases', self.aliases, available_variables)
          fqdn, available_variables           = self._generate(templar, 'fqdn', self.fqdn, available_variables) 
          ip, available_variables             = self._generate(templar, 'ip', self.ip, available_variables)
          cpus, available_variables           = self._generate(templar, 'cpus', self.cpus, available_variables, type = compat_integer_types)
          memory, available_variables         = self._generate(templar, 'memory', self.memory, available_variables, type = compat_integer_types)
          ansible_groups, available_variables = self._generate(templar, 'ansible_groups', self.ansible_groups, available_variables)
          attributes, available_variables     = self._generate(templar, 'attributes', self.attributes, available_variables)

          yield Node(box, boxname, hostname, fqdn, aliases, ip, cpus, memory, ansible_groups, attributes, cluster_offset + node_index, node_index)

          node_index += 1

    def __setattr__(self, name, value):
        #TODO: attribute type validation
        self.__dict__[name] = value

    def _generate(self, ansible_templar, var, generator, available_variables, type = NoneType):
        ''' utility function for resolving value/value generators

        Keyword arguments:
        ansible_templar      --  The ansible templar engine
        var                  --  The name of the var to be generated
        generator            --  The value generator expression (a ninja template managed by ansible; can be also a literal)
        available_variables  --  Variables available within the execution context of the generator expression
        type                 --  The expected type for the generated value
        '''

        # set the variables available within the ninja context for value generation
        ansible_templar.set_available_variables(available_variables)

        # generates the values (or simple keeps the given value)
        try:
            value = ansible_templar.template(generator)
        except Exception, e:
            raise ValueGeneratorError(self.name, available_variables['node_index'], var, e.message), None, sys.exc_info()[2]

        # checks the generated value matches the expected type
        try:
            if type == compat_integer_types:
                value = int(value)
        except Exception, e:
            raise ValueGeneratorTypeError(self.name, available_variables['node_index'], var, e.message)

        value = ansible_unwrap(value)
        available_variables[var] = value

        return value, available_variables
