#!/usr/bin/python
#
# Copyright (c) 2014, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
#   Neither the name of Arista Networks nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
DOCUMENTATION = """
module: eos_vlan
short_description: Manage VLAN resources in EOS
description:
  - Provides active state management of VLAN configuration on Arista EOS
    nodes. All configuration of resources is idempotent unless
    otherwise specified.
author: Arista EOS+
requirements:
  - Arista EOS 4.12.0 or later
  - EOS command API enabled
  - pyeapi 0.1.0 or later
options:
  vlanid:
    description:
      - The unique VLAN identifier associated with this resource.  The value
        for this identiifer must be in the range of 1 to 4094.
    required: true
  name:
    description:
      - An ASCII string identifer for this VLAN
  enable:
    description:
      - Configures the administrative state for the vlan, true if the vlan
        should be enabled otherwise false
    default: true
    choices: BOOLEANS
  config:
    description:
      - Configures the path to the pyeapi config file
  connection:
    description:
      - Configures the name of the connection to use from the eapi.conf file
    default: 'localhost'
  state:
    description:
      - The state of the VLAN related to the current running configuration
    default: 'present'
    choices: ['present', 'absent']
  null_as_default:
    description:
      - Instructs the module how to handle null values.  If this flag is set
        then null values trigger default configuration otherwise do nothing
    default: false
    choices: BOOLEANS
"""
import re
import syslog

from ansible.module_utils.basic import *

import pyeapi

from pyeapi.api.vlans import isvlan


def log(entry):
    """ Writes an entry to the local syslog

    Args:
        entry (str): The text string to write to syslog

    """
    syslog.openlog('ansible-eos')
    syslog.syslog(syslog.LOG_NOTICE, str(entry))

def get_instance(node, vlanid):
    """ Returns an instance of Vlan based on vlanid

    Response

    .. code-block:: json

        {
            "vlanid": <string>,
            "state": [absent, present],
            "name:": <string>,
            "enable": [true, false]
        }

    Args:
        node (Node): An instance of pyeapi.client.Node

        vlanid (string): The Vlan ID to retreive an instance for

    Returns:
        A Python dictionary object that represenses the Vlan resource

    """
    result = node.api('vlans').get(vlanid)
    instance = dict(vlanid=vlanid, state='absent')
    if result:
        instance['state'] = 'present'
        instance['name'] = result['name']
        instance['enable'] = result['state'] == 'active'
        instance['trunk_groups'] = ','.join(result['trunk_groups'])
    return instance

def create_instance(node, vlanid):
    """ Creates a new instance of a Vlan on the node

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        vlanid (string): The VLAN ID to create an instance for.  This value
            must be a string in the range of 1 to 4094

    Returns:
        A Python dictionary object that represents the Vlan resource from
            the nodes running configuration

    """
    log("Invoked create for vlanid %s" % vlanid)
    node.api('vlans').create(vlanid)
    return get_instance(node, vlanid)

def set_attribute(node, method_name, vlanid, value, default):
    log('Invoked %s for vlan id %s with value %s and default %s'\
        % (method_name, vlanid, value, default))
    method = getattr(node.api('vlans'), method_name)
    return method(vlanid, value, default)

def set_name(node, vlanid, **kwargs):
    """ Configures the name attribute for the vlan id

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        vlanid (str): The Vlan ID to configure the name value for
       kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['name']
    return set_attribute(node, 'set_name', vlanid, value, default)

def set_enable(node, vlanid, **kwargs):
    """ Configures the vlan as adminstratively enabled or disabled

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        vlanid (str): The Vlan ID to configure the name value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = 'active' if kwargs['enable'] else 'suspend'
    return set_attribute(node, 'set_state', vlanid, value, default)

def set_trunk_groups(node, vlanid, **kwargs):
    """ Configures the list of trunk groups assigned to this Vlan

    Args:
        node (Node): An instance of pyeapi.client.Node used for sending and
            receiveing eAPI messages
        vlanid (str): The Vlan ID to configure the name value for
        kwargs: Optional keyword arguments

    Returns:
        True if the command was completed successfully otherwise False is
            returned

    """
    default = kwargs.get('default', False)
    value = kwargs['trunk_groups']
    return set_attribute(node, 'set_trunk_groups', vlanid, value, default)

def main():
    """ The main module routine called when the module is run by Ansible
    """

    argument_spec = dict(
        vlanid=dict(required=True),
        enable=dict(type='bool', default=True),
        name=dict(),
        trunk_groups=dict(),
        config=dict(),
        connection=dict(default='localhost'),
        null_as_default=dict(type='bool', default=False),
        state=dict(default='present', choices=['present', 'absent'])
    )

    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=True)

    if module.params['config']:
        pyeapi.load_config(module.params['config'])
    node = pyeapi.connect_to(module.params['connection'])

    result = dict(changed=False, created=False, removed=False, changes=dict())
    state = module.params['state']
    null_as_default = module.params['null_as_default']

    vlanid = module.params['vlanid']
    if not isvlan(vlanid):
        module.fail_json(msg='vlanid is not a valid value')


    trunk_groups = module.params['trunk_groups']
    if trunk_groups is not None:
        trunk_groups = re.sub(r',\s+', ',', module.params['trunk_groups'])

    attributes = {
        'vlanid': vlanid,
        'name': module.params['name'],
        'enable': module.params['enable'],
        'trunk_groups': trunk_groups
    }

    instance = get_instance(node, vlanid)

    if state == 'present':
        if instance['state'] == 'absent':
            if not module.check_mode:
                instance = create_instance(node, vlanid)
            result['created'] = True

        changeset = attributes.viewitems() - instance.viewitems()
        for key, value in changeset:
            func = 'set_%s' % key
            if func in globals():
                command = globals().get(func)
                default = value is None and null_as_default
                if value is not None or default:
                    if not result['created']:
                        result['changes'][key] = value
                        result['changed'] = True
                    if not module.check_mode:
                        command(node, **attributes)

    elif instance['state'] == 'present' and state == 'absent':
        result['removed'] = True
        if not module.check_mode:
            node.api('vlans').delete(vlanid)

    result['instance'] = get_instance(node, vlanid)
    module.exit_json(**result)

main()
