#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
acl - Interface with the ACL database and queue.

A simple command to determine access-list associations, also allows you to add or remove an ACL
from the load queue.
"""

__author__ = 'Jathan McCollum, Eileen Tschetter, Mark Ellzey Thomas, Michael Shields'
__maintainer__ = 'Jathan McCollum'
__email__ = 'jathan.mccollum@teamaol.com'
__copyright__ = 'Copyright 2003-2011, AOL Inc.'
__version__ = '1.6'

from textwrap import wrap
from collections import defaultdict
import optparse
import os
import sys

from trigger.utils.cli import get_terminal_width
from trigger.acl.queue import Queue
from trigger.acl.db import AclsDB, get_matching_acls
from trigger.conf import settings
from trigger import exceptions

# Setup
aclsdb = AclsDB()
term_width = get_terminal_width() # How wide is your term!
valid_modes = ['list', 'listmanual', 'liststaged'] # Valid listing modes
changers = False # Sentinel for add/remove flags

# Parse arguments.
optp = optparse.OptionParser()
optp.add_option('-s', '--staged', help='list currently staged ACLs',
                action='store_const', const='liststaged', dest='mode')
optp.add_option('-l', '--list', help='list ACLs currently in integrated (automated) queue',
                action='store_const', const='list', dest='mode')
optp.add_option('-m', '--listmanual', help='list entries currently in manual queue',
                action='store_const', const='listmanual', dest='mode')
optp.add_option('-i', '--inject', help='inject into load queue',
                action='store_const', const='inject', dest='mode')
optp.add_option('-c', '--clear', help='clear from load queue',
                action='store_const', const='clear', dest='mode')
optp.add_option('-x', '--exact', help='match entire name, not just start',
                action='store_true', dest='exact')
optp.add_option('-d', '--device-name-only', help="don't match on ACL",
                action='store_true', dest='dev_only')
optp.add_option('-a', '--add', type='string', action='append',
                help="add an acl to explicit ACL database, example: 'acl -a abc123 test1-abc test2-abc'")
optp.add_option('-r', '--remove', type='string', action='append',
                help="remove an acl from explicit ACL database, example: 'acl -r abc123 -r xyz246 test1-abc'")
optp.add_option('-q', '--quiet', help="be quiet! (For use with scripts/cron)",
                action='store_true')
(opts, args) = optp.parse_args()

def pretty_print_acls(name, acls, term_width=term_width, offset=41):
    output = wrap(' '.join(acls), term_width - offset)
    print '%-39s %s' % (name, output[0])
    for line in output[1:]:
        print ' '*39, line

def p_error(msg=None):
    optp.print_help()
    if msg:
        optp.error(msg)
    sys.exit(1)

if opts.add and opts.remove:
    p_error('cannot both add & remove: pick one.')

if opts.add or opts.remove:
    if len(args) == 0:
        p_error('must specify at least one device to modify')
    changers = True

elif ((len(args) == 0 and opts.mode not in valid_modes) or
    (len(args) != 0 and opts.mode in valid_modes)):
    p_error()
    sys.exit(1)

# Strip "acl." from acl names
args = [x.startswith('acl.') and x[4:] or x for x in args]
queue = Queue()

# Do the work.
if opts.mode == 'liststaged':
    tfdir = settings.TFTPROOT_DIR
    print 'Access-lists currently staged in %s (listed by date):\n' % tfdir
    os.chdir(tfdir)
    os.system('ls -ltr acl.*')

elif opts.mode == 'list':
    acl_data = defaultdict(list)
    [acl_data[acl].append(router) for router, acl in queue.list()]
    [pretty_print_acls(dev, acl_data[dev]) for dev in sorted(acl_data)]

elif opts.mode == 'listmanual':
    for item, user, ts, done in queue.list(queue='manual'):
        print item
        print '\tadded by %s on %s' % (user, ts)
        print

elif opts.mode == 'inject':
    for arg in args:
        devs = [dev[0] for dev in get_matching_acls([arg])]
        queue.insert(arg, devs)

elif opts.mode == 'clear':
    [queue.delete(arg) for arg in args]

elif changers:
    from trigger.netdevices import NetDevices
    nd = NetDevices()

    invalid_dev_count = 0

    for arg in args:
        try:
            dev = nd.find(arg.lower())
        except KeyError:
            print "skipping %s: invalid device" % arg
            invalid_dev_count += 1
            continue
            #the continue here leads that single error if its the only attempt

        if opts.add:
            for acl in opts.add:
                try:
                    print aclsdb.add_acl(dev, acl)
                except exceptions.ACLSetError as err:
                    print err

        elif opts.remove:
            for acl in opts.remove:
                try:
                    print aclsdb.remove_acl(dev, acl)
                except exceptions.ACLSetError, err:
                    print err
            #should also conside adding a comment about autoacls if it was for opts.remove

    if invalid_dev_count == len(args):
        print "\nPlease use --help to find the right syntax."

else:
    # Pretty-print the device/acls justified to the terminal
    acl_data = get_matching_acls(args, opts.exact, match_acl=(not opts.dev_only), match_device=True)
    if not acl_data:
        msg = 'No results for %s' % args if not opts.quiet else 1
        sys.exit(msg)

    [pretty_print_acls(name, acls) for name, acls in acl_data]
