#!/usr/bin/env python

################################################################################
#
# Copyright (c) 2012 ObjectLabs Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
################################################################################

from dargparse import dargparse
import sys
import traceback
from dex import dex

PARSER_DEFINITION ={
    "prog": "dex",
    "usage": "usage: dex [uri] (-f <logfile_path> | -p) [<options>]",
    "description": "Scans a provided MongoDB log file or profile collection "
                    "and uses the provided URI to compare queries found in "
                    "the logfile or profile collection to the indexes "
                    "available in the database, recommending indexes for those "
                    "queries which are not indexed. Recommended for MongoDB "
                    "version 2.2.0 or later.",
    "args": [
        {
            "name": "uri",
            "type": "positional",
            "help": "database connection URI, including database path, and "
                    "authentication if required. If authentication is is "
                    "enabled and multiple databases need to be dexed, "
                    "this URI should point to the admin database. Required "
                    "for profile (-p) mode. Optional for logfile (-f) mode. "
                    "If absent, Dex does not verify its logfile analysis "
                    "against the database and recommends indexes for ALL "
                    "queries.",
            "nargs": "?"
        },
        {
            "name": "logfile_path",
            "type" : "optional",
            "help": "path to a MongoDB log file. If provided, "
                    "the file will be searched for queries.",
            "cmd_arg": [
                "-f",
                "--file"
            ],
            "nargs": 1,
            "default": None
        },
        {
            "name": "use_profile",
            "type" : "optional",
            "help": "flag to examine the MongoDB system.profile collection. "
                    "If set, the profile collection will be searched for "
                    "queries. URI is required for profile mode.",
            "cmd_arg": [
                "-p",
                "--profile"
            ],
            "action": "store_true",
            "nargs": 0,
            "default": False
        },
        {
            "name": "watch",
            "type" : "optional",
            "help": "instructs Dex to watch the system.profile or log "
                    "(depending on which -p/-f is specified) for entries, "
                    "rather than processing existing content. Upon keyboard "
                    "interrupt (Ctrl+C) watch terminates and the accumulated "
                    "output is provided. When using watch mode and profile "
                    "mode together, you must target a specific database using "
                    "-n \"dbname.*\"",
            "cmd_arg": [
                "-w",
                "--watch"
            ],
            "nargs": 1,
            "action": "store_true",
            "default": False
        },
        {
            "name": "namespaces",
            "type" : "optional",
            "help": "a MongoDB namespace (db.collection). Can be provided "
                    "multiple times. This option creates a filter, and "
                    "queries not in the provided namespace(s) will not be "
                    "analyzed. Format: -n ('db.collection' | '*' | 'db.*' | "
                    "'collection'). '*.*' and '*.collection' are redundant "
                    "but also supported. An asterisk is shorthand for "
                    "'all'--actual regexes are not supported. Note that "
                    "-n '*' is equivalent to not providing a -n argument.",
            "cmd_arg": [
                "-n",
                "--namespace"
            ],
            "nargs": 1,
            "action": "append",
            "default": []
        },
        {
            "name": "slowms",
            "type" : "optional",
            "help": "minimum query execution time for analysis, in milliseconds. "
                    "Analogous to MongoDB's SLOW_MS value. Queries that "
                    "complete in fewer milliseconds than this value will "
                    "will not be analyzed. Default is 0.",
            "cmd_arg": [
                "-s",
                "--slowms"
            ],
            "nargs": 1,
            "valueType": int,
            "default": 0
        },
        {
            "name": "timeout",
            "type" : "optional",
            "help": "Maximum Dex time in minutes. Default is 0 (no timeout)."
                    "Applies to logfile (-f) mode only.",
            "cmd_arg": [
                "-t",
                "--timeout"
            ],
            "nargs": 1,
            "valueType": float,
            "default": 0
        },
        {
            "name": "nocheck",
            "type" : "optional",
            "help": "if provided, Dex will recommend indexes without checking"
                    "the specified database to see if they exist. This means"
                    "Dex may recommend an index that's already been created",
            "cmd_arg": [
                "--nocheck"
            ],
            "action": "store_true",
            "nargs": 0,
            "default": False
        },
        {
            "name": "verbose",
            "type" : "optional",
            "help": "enables provision of additional output information, "
                    "including Dex's query and index analysis structures.",
            "cmd_arg": [
                "-v",
                "--verbose"
            ],
            "action": "store_true",
            "nargs": 0,
            "default": False
        }

    ]


}


###############################################################################
# MAIN
###############################################################################
def main(args):
    # build the parse object
    darg_parser = dargparse.build_parser(PARSER_DEFINITION)

    # parse the command line
    options = darg_parser.parse_args(args)
    namespaces = options.namespaces
    slowms = options.slowms
    timeout = options.timeout

    check = True
    if options.nocheck:
        check = False
        sys.stderr.write("--nocheck flag provided. Existing indexes will not be checked, "
                         "therefore you may receive recommendations for indexes "
                         "that already exist. (see -h/--help for more information)\n")

    md = dex.Dex(options.uri, options.verbose, namespaces, slowms, check, timeout)

    if options.use_profile:
        if options.uri is None:
            sys.stderr.write("URI is required for profile mode. (use -h/--help for help)\n")
            return 1
        if options.watch is False:
            return md.analyze_profile()
        else:
            return md.watch_profile()
    elif options.logfile_path is None:
        sys.stderr.write("Either logfile (-f <filename>) or profile (-p) mode is required (use -h/--help for help)\n")
    elif options.logfile_path is not None:
        if options.uri is None:
            if check:
                sys.stderr.write("No URI provided, so using --nocheck mode. Existing indexes will not be checked, "
                                 "therefore you may receive recommendations for indexes "
                                 "that already exist. (see -h/--help for more information)\n")
        if options.watch is False:
            return md.analyze_logfile(options.logfile_path)
        else:
            return md.watch_logfile(options.logfile_path)


###############################################################################
########################                   ####################################
########################     BOOTSTRAP     ####################################
########################                   ####################################
###############################################################################

if __name__ == '__main__':
    try:
        # call main with a sub-list starting skipping the
        # command itself (which is the first arg)
        main(sys.argv[1:])
    except (SystemExit, KeyboardInterrupt) , e:
        if e.code == 0:
            pass
        else:
            raise
    except:
        traceback.print_exc()
