#!/usr/bin/env python
"""Query reporting system for client status."""
__revision__ = '$Revision$'

import os
import sys

try:
    import Bcfg2.Server.Reports.settings
except ConfigParser.NoSectionError:
    print("Your bcfg2.conf is currently missing the statistics section which "
          "is necessary for the reporting interface. Please see bcfg2.conf(5) "
          "for more details.")
    sys.exit(1)

project_directory = os.path.dirname(Bcfg2.Server.Reports.settings.__file__)
project_name = os.path.basename(project_directory)
sys.path.append(os.path.join(project_directory, '..'))
project_module = __import__(project_name, '', '', [''])
sys.path.pop()
# Set DJANGO_SETTINGS_MODULE appropriately.
os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name

from Bcfg2.Server.Reports.reports.models import Client
import getopt
import datetime
import fileinput

usage = """Usage: bcfg2-reports [option] ...

Options and arguments (and corresponding environment variables):
-a                        : shows all hosts, including expired hosts
-b NAME                   : single-host mode - shows bad entries from the
                            current interaction of NAME
-c                        : shows only clean hosts
-d                        : shows only dirty hosts
-e NAME                   : single-host mode - shows extra entries from the
                            current interaction of NAME
-h                        : shows help and usage info about bcfg2-reports
-m NAME                   : single-host mode - shows modified entries from the
                            current interaction of NAME
-s NAME                   : single-host mode - shows bad, modified, and extra
                            entries from the current interaction of NAME
-t NAME                   : single-host mode - shows total number of managed and
                            good entries from the current interaction of NAME
-x NAME                   : toggles expired/unexpired state of NAME
--badentry=KIND,NAME      : shows only hosts whose current interaction has bad
                            entries in of KIND kind and NAME name; if a single
                            argument ARG1 is given, then KIND,NAME pairs will be
                            read from a file of name ARG1
--modifiedentry=KIND,NAME : shows only hosts whose current interaction has
                            modified entries in of KIND kind and NAME name; if a
                            single argument ARG1 is given, then KIND,NAME pairs
                            will be read from a file of name ARG1
--extraentry=KIND,NAME    : shows only hosts whose current interaction has extra
                            entries in of KIND kind and NAME name; if a single
                            argument ARG1 is given, then KIND,NAME pairs will be
                            read from a file of name ARG1
--fields=ARG1,ARG2,...    : only displays the fields ARG1,ARG2,...
                            (name,time,state)
--sort=ARG1,ARG2,...      : sorts output on ARG1,ARG2,... (name,time,state)
--stale                   : shows hosts which haven't run in the last 24 hours
"""

def timecompare(client1, client2):
    """Compares two clients by their timestamps."""
    return cmp(client1.current_interaction.timestamp, \
               client2.current_interaction.timestamp)

def namecompare(client1, client2):
    """Compares two clients by their names."""
    return cmp(client1.name, client2.name)

def statecompare(client1, client2):
    """Compares two clients by their states."""
    clean1 = client1.current_interaction.isclean()
    clean2 = client2.current_interaction.isclean()

    if clean1 and not clean2:
        return -1
    elif clean2 and not clean1:
        return 1
    else:
        return 0

def totalcompare(client1, client2):
    """Compares two clients by their total entry counts."""
    return cmp(client2.current_interaction.totalcount, \
               client1.current_interaction.totalcount)

def goodcompare(client1, client2):
    """Compares two clients by their good entry counts."""
    return cmp(client2.current_interaction.goodcount, \
               client1.current_interaction.goodcount)

def badcompare(client1, client2):
    """Compares two clients by their bad entry counts."""
    return cmp(client2.current_interaction.totalcount - \
               client2.current_interaction.goodcount,   \
               client1.current_interaction.totalcount - \
               client1.current_interaction.goodcount)

def crit_compare(criterion, client1, client2):
    """Compares two clients by the criteria provided in criterion."""
    for crit in criterion:
        comp = 0
        if crit == 'name':
            comp = namecompare(client1, client2)
        elif crit == 'state':
            comp = statecompare(client1, client2)
        elif crit == 'time':
            comp = timecompare(client1, client2)
        elif crit == 'total':
            comp = totalcompare(client1, client2)
        elif crit == 'good':
            comp = goodcompare(client1, client2)
        elif crit == 'bad':
            comp = badcompare(client1, client2)
        
        if comp != 0:
            return comp
    
    return 0

def print_fields(fields, cli, max_name, entrydict):
    """
    Prints the fields specified in fields of cli, max_name
    specifies the column width of the name column.
    """
    fmt = ''
    for field in fields:
        if field == 'name':
            fmt += ("%%-%ds   " % (max_name))
        else:
            fmt += "%s   "
    fdata = []
    for field in fields:
        if field == 'time':
            fdata.append(str(cli.current_interaction.timestamp))
        elif field == 'state':
            if cli.current_interaction.isclean():
                fdata.append("clean")
            else:
                fdata.append("dirty")
        elif field == 'total':
            fdata.append("%5d" % cli.current_interaction.totalcount)
        elif field == 'good':
            fdata.append("%5d" % cli.current_interaction.goodcount)
        elif field == 'bad':
            fdata.append("%5d" % cli.current_interaction.totalcount \
                               - cli.current_interaction.goodcount)
        else:
            try:
                fdata.append(getattr(cli, field))
            except:
                fdata.append("N/A")

    display = fmt % tuple(fdata)
    if len(entrydict) > 0:
        display += "   "
        display += str(entrydict[cli])
    print(display)

def print_entry(item, max_name):
    fmt = ("%%-%ds   " % (max_name))
    fdata = item.entry.kind + ":" + item.entry.name
    display = fmt % (fdata)
    print(display)
        
fields = ""
sort = ""
badentry = ""
modifiedentry = ""
extraentry = ""
expire = ""
singlehost = ""

c_list = Client.objects.all()

result = list()
entrydict = dict()

args = sys.argv[1:]
try:
    opts, pargs = getopt.getopt(args, 'ab:cde:hm:s:t:x:',
                                ['stale',
                                 'sort=',
                                 'fields=',
                                 'badentry=',
                                 'modifiedentry=',
                                 'extraentry='])
except getopt.GetoptError:
    msg = sys.exc_info()[1]
    print(msg)
    print(usage)
    sys.exit(2)

for option in opts:
    if len(option) > 0:
        if option[0] == '--fields':
            fields = option[1]
        if option[0] == '--sort':
            sort = option[1]
        if option[0] == '--badentry':
            badentry = option[1]
        if option[0] == '--modifiedentry':
            modifiedentry = option[1]
        if option[0] == '--extraentry':
            extraentry = option[1]
        if option[0] == '-x':
            expire = option[1]
        if option[0] == '-s' or \
           option[0] == '-t' or \
           option[0] == '-b' or \
           option[0] == '-m' or \
           option[0] == '-e':
            singlehost = option[1]

if expire != "":
    for c_inst in c_list:
        if expire == c_inst.name:
            if c_inst.expiration == None:
                c_inst.expiration = datetime.datetime.now()
                print("Host expired.")
            else:
                c_inst.expiration = None
                print("Host un-expired.")
            c_inst.save()

elif '-h' in args:
    print(usage)
elif singlehost != "":
    for c_inst in c_list:
        if singlehost == c_inst.name:
            if '-t' in args:
                managed = c_inst.current_interaction.totalcount
                good = c_inst.current_interaction.goodcount
                print("Total managed entries: %d (good: %d)" % (managed, good))
            baditems = c_inst.current_interaction.bad()
            if len(baditems) > 0 and ('-b' in args or '-s' in args):
                print("Bad Entries:")
                max_name = -1
                for item in baditems:
                    if len(item.entry.name) > max_name:
                        max_name = len(item.entry.name)
                for item in baditems:
                    print_entry(item, max_name)
            modifieditems = c_inst.current_interaction.modified()
            if len(modifieditems) > 0 and ('-m' in args or '-s' in args):
                print "Modified Entries:"
                max_name = -1
                for item in modifieditems:
                    if len(item.entry.name) > max_name:
                        max_name = len(item.entry.name)
                for item in modifieditems:
                    print_entry(item, max_name)
            extraitems = c_inst.current_interaction.extra()
            if len(extraitems) > 0 and ('-e' in args or '-s' in args):
                print("Extra Entries:")
                max_name = -1
                for item in extraitems:
                    if len(item.entry.name) > max_name:
                        max_name = len(item.entry.name)
                for item in extraitems:
                    print_entry(item, max_name)
                

else:
    if fields == "":
        fields = ['name', 'time', 'state']
    else:
        fields = fields.split(',')

    if sort != "":
        sort = sort.split(',')

    if badentry != "":
        badentry = badentry.split(',')

    if modifiedentry != "":
        modifiedentry = modifiedentry.split(',')

    if extraentry != "":
        extraentry = extraentry.split(',')
    
    # stale hosts
    if '--stale' in args:
        for c_inst in c_list:
            if c_inst.current_interaction.isstale():
                result.append(c_inst)
    # clean hosts
    elif '-c' in args:    
        for c_inst in c_list:
            if c_inst.current_interaction.isclean():
                result.append(c_inst)
    # dirty hosts
    elif '-d' in args:    
        for c_inst in c_list:
            if not c_inst.current_interaction.isclean():
                result.append(c_inst)

    elif badentry != "":
        if len(badentry) == 1:
            fileread = fileinput.input(badentry[0])
            try:
                for line in fileread:
                    badentry = line.strip().split(',')
                    for c_inst in c_list:
                        baditems = c_inst.current_interaction.bad()
                        for item in baditems:
                            if item.entry.name == badentry[1] and item.entry.kind == badentry[0]:
                                result.append(c_inst)
                                if c_inst in entrydict:
                                    entrydict.get(c_inst).append(badentry[1])
                                else:
                                    entrydict[c_inst] = [badentry[1]]
                                break
            except IOError:
                e = sys.exc_info()[1]
                print("Cannot read %s: %s" % (e.filename, e.strerror))
        else:
            for c_inst in c_list:
                baditems = c_inst.current_interaction.bad()
                for item in baditems:
                    if item.entry.name == badentry[1] and item.entry.kind == badentry[0]:
                        result.append(c_inst)
                        break
    elif modifiedentry != "":
        if len(modifiedentry) == 1:
            fileread = fileinput.input(modifiedentry[0])
            try:
                for line in fileread:
                    modifiedentry = line.strip().split(',')
                    for c_inst in c_list:
                        modifieditems = c_inst.current_interaction.modified()
                        for item in modifieditems:
                            if item.entry.name == modifiedentry[1] and item.entry.kind == modifiedentry[0]:
                                result.append(c_inst)
                                if c_inst in entrydict:
                                    entrydict.get(c_inst).append(modifiedentry[1])
                                else:
                                    entrydict[c_inst] = [modifiedentry[1]]
                                break
            except IOError:
                e = sys.exc_info()[1]
                print("Cannot read %s: %s" % (e.filename, e.strerror))
        else:
            for c_inst in c_list:
                modifieditems = c_inst.current_interaction.modified()
                for item in modifieditems:
                    if item.entry.name == modifiedentry[1] and item.entry.kind == modifiedentry[0]:
                        result.append(c_inst)
                        break
    elif extraentry != "":
        if len(extraentry) == 1:
            fileread = fileinput.input(extraentry[0])
            try:
                for line in fileread:
                    extraentry = line.strip().split(',')
                    for c_inst in c_list:
                        extraitems = c_inst.current_interaction.extra()
                        for item in extraitems:
                            if item.entry.name == extraentry[1] and item.entry.kind == extraentry[0]:
                                result.append(c_inst)
                                if c_inst in entrydict:
                                    entrydict.get(c_inst).append(extraentry[1])
                                else:
                                    entrydict[c_inst] = [extraentry[1]]
                                break
            except IOError:
                e = sys.exc_info()[1]
                print("Cannot read %s: %s" % (e.filename, e.strerror))
        else:
            for c_inst in c_list:
                extraitems = c_inst.current_interaction.extra()
                for item in extraitems:
                    if item.entry.name == extraentry[1] and item.entry.kind == extraentry[0]:
                        result.append(c_inst)
                        break

    else:
        for c_inst in c_list:
            result.append(c_inst)
    max_name = -1
    if 'name' in fields:
        for c_inst in result:
            if len(c_inst.name) > max_name:
                max_name = len(c_inst.name)

    if sort != "":
        result.sort(lambda x, y: crit_compare(sort, x, y))
    
    if fields != "":
        for c_inst in result:
            if '-a' in args or c_inst.expiration == None:
                print_fields(fields, c_inst, max_name, entrydict)
