#!/usr/bin/env python
"""
Receive XSP monitoring messages and
store them in a database.
"""
__rcsid__ = "$Id$"
__author__ = "Dan Gunter <dkgunter@lbl.gov>"

# Std library imports
import logging
import optparse
import os
import pprint
import signal
import socket
import sys
import time

# Third-party imports
import pymongo
import bson
bson_decode = bson.decode_all

# Local imports
from netlogger import nlmi
from netlogger import util
from netlogger.nllog import OptionParser, get_logger, get_root_logger
from netlogger import xsplib

#
# Constants
#

XSP_TYPE = 0x20 # xsp msg type

#
# Exceptions
#

class DBConnectionException(Exception):
    pass


#
# Signal handlers
#

# Stop things that are in a loop
g_stop = False

def on_kill(signo, frame):
    """Signal handler for a graceful exit.
    """
    global g_stop
    log = get_logger(__file__)
    log.warn("killed", signo=signo)
    g_stop = True

def on_hup(signo, frame):    
    return

#
# Classes and functions
#

class MongoWrapper:
    def __init__(self, host=None, port=None, collection=None, database=None):
       try:
           self.conn = pymongo.Connection(host=host, port=port)
       except pymongo.errors.ConnectionFailure:
           raise DBConnectionException("Couldn't connect to Mongo at {0}:{1:d}"
                                       .format(host, port))
       self.db = self.conn[database]
       self.coll = self.db[collection]

    def store(self, data):
        obj = bson_decode(data)
        # XXX: break into metadata / data?
        if isinstance(obj, list):
            for item in obj:
                if not isinstance(item, dict):
                    raise ValueError("list items must be dicts")
                self.coll.insert(item)
        elif isinstance(obj, dict):
            self.coll.insert(obj)
        else:
            raise ValueError("don't know how to store " + str(type(obj)))

class StdoutWrapper:
    def __init__(self):
        return

    def store(self, data):
        print("DATA\n----")
        ddict = bson_decode(data)
        pprint.pprint(ddict)

def run(host=None, port=None, db=None):
    """Accept and handle connections until killed.
    """
    log = get_logger(__file__)
    log.info("run.start")
    server = xsplib.XSPServer(host, port, db.store)
    while not g_stop:
        log.debug("loop.start")
        server.loop(timeout=3, count=1)
        log.debug("loop.end", status=int(g_stop))
    log.info("run.stop")
    
def main(argv=None):
    """Program entry point.
    """
    global g_stop
    g_stop = False
    status = 0

    usage = "%prog [options] [<dbtype> (mongo*, stdout)]"
    desc = ' '.join(__doc__.split())
    parser = OptionParser(usage=usage, description=desc, can_be_daemon=True)
    parser.add_option("--host", dest="host", action="store", default="localhost",
                      help="XSP server host (default=%default)")
    parser.add_option("--port", dest="port", action="store", type="int", default=5006,
                      help="XSP server listen port (default=%default)")
    group = optparse.OptionGroup(parser, "Options for <db-type> = 'mongo'")
    group.add_option("--dbhost", dest="dbhost", action="store", default="localhost",
                     help="MongoDB server host (default=%default)")
    group.add_option("--dbport", dest="dbport", action="store", type="int", default=27017,
                      help="MongoDB server listen port (default=%default)")
    group.add_option("--collection", dest="coll", action="store", default="xsp",
                     help="MongoDB collection name (default=%default)")
    group.add_option("--db", dest="db", action="store", default="xsp",
                     help="MongoDB database (default=%default)")
    parser.add_option_group(group)
    
    if argv is None:
        argv = sys.argv[1:]
    options, args = parser.parse_args(argv)

    # Parse args
    log = get_logger(__file__)  # Must come after parsing args
    log.debug("parse.args.start")
    db = None
    if len(args) == 0:
        dbtype = 'mongo'
    elif len(args) > 1:
        parser.error("only one argument, <dbtype>, allowed")
        return(-1)
    else:
        dbtype = args[0].lower()
    if dbtype.startswith('mongo'):
        try:
            db = MongoWrapper(host=options.dbhost, port=options.dbport,
                              collection=options.coll, database=options.db)
        except DBConnectionException, err:
            log.critical("db.connect.error", msg=err)
            return(-3)
    elif dbtype == 'stdout':
        db = StdoutWrapper()
    else:
        parser.error("unknown <dbtype>. must be in: mongo, stdout")
        return(-2)
    log.debug("parse.args.end", status=0)

    # Set up signal handlers
    log.debug("init.signals.start")
    util.handleSignals(
        (on_kill, ('SIGTERM', 'SIGINT', 'SIGUSR2')),
        (on_hup, ('SIGHUP',)) )
    log.debug("init.signals.end", status=0)

    status = run(host=options.host, port=options.port, db=db)
    return status

if __name__ == '__main__':
    sys.exit(main())
