#!/usr/bin/python

import os
import sys

home = os.environ.get("CUMIN_HOME", os.path.normpath("/usr/share/cumin"))
sys.path.append(os.path.join(home, "python"))

from cumin.config import *
from cumin.util import *
from mint import *
from parsley.loggingex import PipeLogThread
from psycopg2 import OperationalError
from cumin.admin import SchemaVersion, SchemaMissing
from cumin.errors import CuminErrors

def restore_IO():
    sys.stderr = sys.__stderr__
    sys.stdout = sys.__stdout__

def process_classes(mint, values, section_name, on_empty=None):

    return_code = CuminErrors.NO_ERROR
    pkgs = set()    
    if values and len(values) > 0:

        for cls_str in values.split(","):
            pair = cls_str.strip().split(":")
            if len(pair) == 2:
                pname = pair[0]
                cname = pair[1]
            else:
                log.error("Configuration section '%s',"\
                          " class name '%s' is badly formed"\
                              % (section_name, cls_str.strip()))
                return_code = CuminErrors.PARSE_ERROR
                break

            try:
                pkg = mint.model._packages_by_name[pname]
                if cname == "*":
                    pkgs = pkgs.union(set(pkg._classes))
                else:
                    try:
                        cls = pkg._classes_by_name[cname]
                        pkgs.add(cls)
                    except KeyError:
                        log.warning("Configuration section '%s',"\
                                    " class '%s' is not contained in package"\
                                    " '%s'" % (section_name, cname, pname))
            except KeyError:
                log.warning("Configuration section '%s',"\
                            " package '%s' not found" % (section_name, pname))
    elif on_empty == "all":
        for pkg in mint.model._packages:
            pkgs = pkgs.union(set(pkg._classes))

    return return_code, pkgs

def process_agents(values, section_name):

    return_code = CuminErrors.NO_ERROR
    agents = set()    
    if values and len(values) > 0:

        for agent_str in values.split(","):
            agent_str = agent_str.strip()
            if agent_str[0] == ":":
                # Leading : indicates V1 agent label value
                agents.add((agent_str[1:],))
            else:
                # V2, Looking for vendor, product, instance
                vendor = product = instance = "*"
                agent = agent_str.split(":")
                if len(agent) > 3:
                    log.error("Configuration section '%s',"\
                              " agent name '%s' is badly formed"\
                              % (section_name, agent_str))
                    return_code = CuminErrors.PARSE_ERROR
                    break
                if len(agent) >= 1:
                    vendor = agent[0]
                if len(agent) >= 2:
                    product = agent[1]
                if len(agent) == 3:
                    instance = agent[2]                
                agents.add((vendor, product, instance))

    return return_code, agents

def adjust_return(passed_init, ret):
    # Shift non-zer0 return codes left 1 bit
    # and OR in whether or not init passed
    if ret != 0:
        ret = ret << 1 | passed_init
    return ret

def main():
    passed_init = 1
    return_code = CuminErrors.NO_ERROR

    # Do our own simple option check so we can redirect IO early
    # without worrying about other options or the behavior of optParse
    opts = check_for_options(["--section", "--daemon"], sys.argv[1:])
    if type(opts["--section"]) is str:
        section_name = opts["--section"]
    else:
        section_name = "data"

    # If the --daemon option has been set, redirect stderr and stdio through
    # pipes.  Launch a thread to read those pipes and direct the content to
    # files with rollover control. 
    pipeThread = None
    if opts["--daemon"]:
        try:
            cumin_home = os.environ.get("CUMIN_HOME", os.path.normpath("/usr/share/cumin"))
            err_file = os.path.join(cumin_home, "log", section_name+".stderr")
            out_file = os.path.join(cumin_home, "log", section_name+".stdout")
 
           # Open pipes and redirect
            err_r, err_w = os.pipe()
            out_r, out_w = os.pipe()
            pipeThread = PipeLogThread(descriptors=[err_r, out_r],
                                       paths=[err_file, out_file],
                                       call_on_fail=restore_IO)
            pipeThread.start()
            sys.stderr = os.fdopen(err_w, "w", 0)
            sys.stdout = os.fdopen(out_w, "w", 0)
        except:
            print_exc()
            pipeThread = None
    
    class ArgError(Exception):
        pass

    try:
        mint = None
        setup_initial_logging()

        parser = CuminOptionParser()

        # Add additional parameters for data
        parser.add_option("--print-stats", action="store_true", default=False)
        parser.add_option("--print-events", type="int", default=0, metavar="LEVEL")
        parser.add_option("--section", default="data")
        parser.add_option("--daemon", action="store_true", default=False)
        parser.add_option("--no-vacuum", dest="vacuum_enabled", action="store_false")
        parser.add_option("--no-expire", dest="expire_enabled", action="store_false")

        # Get options
        opts, args = parser.parse_args()

        # --section controls which section is read from the config file
        # If a section other than "data" is specified, require it to exist
        config = CuminDataConfig(opts.section, strict_section = opts.section != "data")

        # There might be other sections returned from config.parse() but
        # currently we don't care about them...
        values = getattr(config.parse(), opts.section)

        # Use the config values as defaults for unspecified options
        apply_defaults(values, opts)

        setup_operational_logging(opts, 
                                  values.log_max_mb,
                                  values.log_max_archives)

        if len(args) != 0:
            log.error("Extra arguments:" + "".join([" "+arg for arg in args]))
            raise ArgError

        model_dir = os.path.join(config.home, "model")

        broker_uris = [x.strip() for x in opts.brokers.split(",")]

        mint = Mint(model_dir, broker_uris, opts.database)

        if type(values.sasl_mech_list) == str:
            mint.sasl_mech_list = values.sasl_mech_list.upper()

        mint.print_event_level = opts.print_events

        mint.expire_enabled = opts.expire_enabled
        mint.expire_thread.interval = values.expire_interval
        mint.expire_thread.threshold = values.expire_threshold

        mint.vacuum_enabled = opts.vacuum_enabled
        mint.vacuum_thread.interval = values.vacuum_interval

        mint.check()
        mint.init()

        # Handle QMF class binding options
        if values.include_classes or values.exclude_classes:
            return_code, includes = process_classes(mint, 
                                                    values.include_classes,
                                                    opts.section, on_empty="all")

            return_code, excludes = process_classes(mint, 
                                                    values.exclude_classes,
                                                    opts.section)                

            mint.qmf_classes = includes.difference(excludes)

        # Handle agents binding option
        if values.agents:
            return_code, agents = process_agents(values.agents, opts.section)
            mint.qmf_agents = agents

        # If init_only was set or we failed init, don't proceed...
        if not opts.init_only and not return_code:
            passed_init = 0

            mint.start()
            stats = mint.update_thread.stats
            count = 0

            if opts.print_stats:
                print "[Starred columns are the number of events per second]"

                while True:
                    if count % 20 == 0:
                        stats.print_headings()

                    count += 1

                    stats.print_values()

                    sleep(5)
            else:
                while True:
                    sleep(86400)

    except KeyboardInterrupt:
        log.info("Received SIGINT")

    except SystemExit:
        if "--help" not in sys.argv:
            log.error("Error in options")
            return_code = CuminErrors.PARSE_ERROR

    except ArgError:
        return_code = CuminErrors.PARSE_ERROR

    except OperationalError:
        # Failed to talk to the database on check()
        log.info("Run 'cumin-database check' as root for more information.")
        return_code = CuminErrors.DATABASE_ERROR

    except SchemaMissing:
        log.info("Run 'cumin-admin create-schema' as root")
        return_code = CuminErrors.SCHEMA_ERROR

    except SchemaVersion:
        log.info("Run 'cumin-admin upgrade-schema' as root")
        return_code = CuminErrors.SCHEMA_VER_ERROR

    except:
        print_exc()
        return_code = CuminErrors.UNHANDLED_ERROR

    if mint:
        mint.stop()
    if pipeThread:
        pipeThread.stop()
    logging.shutdown()
    return adjust_return(passed_init, return_code)

if __name__ == "__main__":
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        sys.exit(0)
