#!/usr/bin/env python
# -*- coding: utf-8 -*-
#PYTHON_ARGCOMPLETE_OK
"""sw - a dynamic firewall
"""

import argparse
import pickle
import json
from os import listdir
from sys import argv
from servicewall import parser_helpers
from servicewall.network_helpers import get_realm_id
from servicewall.service_helpers import ServiceDef, PortDef
try:
    import argcomplete
    ARGCOMPLETE = True
except ImportError:
    ARGCOMPLETE = False


CONF_DIR = "/etc/servicewall/"
LIB_DIR = "/usr/lib/servicewall/"
REALM_DEFS_DICT = CONF_DIR + "realms.json"
SERVICE_DEFS_PICKLE = LIB_DIR + "services.p"
SERVICE_DEFS_DIR = "/etc/servicewall/services/"

with open(REALM_DEFS_DICT, "rb") as fd:
    realm_defs = json.load(fd)
with open(SERVICE_DEFS_PICKLE, "rb") as fd:
    service_defs = pickle.load(fd)
for service_file in listdir(SERVICE_DEFS_DIR):
    if service_file.find('.') == 0:
        # It's a dotfile, keep going
        continue
    try:
        with open(SERVICE_DEFS_DIR + service_file) as fd:
            service_def = json.load(fd)
            service_def["ports"] = PortDef(**service_def["ports"])
            sdef = ServiceDef(**service_def)
            service_defs[sdef.title] = sdef
    except TypeError:
        pass

# Find allowed services to help autocompletion know what to delete - TODO
# doesn't work when the realm is unknown.
try:
    REALM_ID = get_realm_id()
    IS_ONLINE = True
except KeyError:
    # No network (but servicewall might still be enabled).
    REALM_ID = None
    IS_ONLINE = False
try:
    allowed_services = realm_defs[REALM_ID].keys()
except KeyError:
    allowed_services = []
try:
    default_allowed_services = realm_defs["ServiceWall:default"].keys()
except KeyError:
    default_allowed_services = []
all_allowed_services = *allowed_services, *default_allowed_services


def parser_helper(function_name):
    """This function will return a pointer to a function named "function_name"
    in parser_helpers. This pointer will only be evaluated when it is called,
    ie after parser is done.
    """
    def wrapper(arguments):
        eval("parser_helpers." + function_name)(arguments)
    return wrapper


parser = argparse.ArgumentParser(
    description="ServiceWall CLI frontend"
)

# parser fails badly when invoked without argument ; let's invoke basic help
# instead (see def no_arg_provided(args) in parser_helpers.py)
#parser.set_defaults(func=parser_helper("no_arg_provided"))
#parser.set_defaults(func=parser.print_help())
#defarg = parser.add_argument("-s", action='store_true')
#defarg.set_defaults(func=parser.print_help())

subparser = parser.add_subparsers()


#
# SUBPARSERS : enable | disable | status | reload
#

parser_enable = subparser.add_parser(
    "enable",
    help="enable the firewall",
    description="enable the firewall",
)
parser_enable.set_defaults(func=parser_helper("enable"))

parser_disable = subparser.add_parser(
    "disable",
    help="disable the firewall",
    description="disable the firewall",
)
parser_disable.set_defaults(func=parser_helper("disable"))
parser_enable = subparser.add_parser(
    "start",
    help="start the firewall",
    description="start the firewall",
)
parser_enable.set_defaults(func=parser_helper("start"))

parser_disable = subparser.add_parser(
    "stop",
    help="stop the firewall",
    description="stop the firewall",
)
parser_disable.set_defaults(func=parser_helper("stop"))

parser_show_status = subparser.add_parser(
    "status",
    help="tell if the firewall is enabled and what is the current realm",
    description="tell if the firewall is enabled and what is the current realm"
)
parser_show_status.set_defaults(func=parser_helper("status"))

parser_reload = subparser.add_parser(
    "reload",
    help="reload rules",
    description="reload rules",
)
parser_reload.set_defaults(func=parser_helper("reload"))


#
# SUBPARSERS : show { logs | realm | realms | service | services | port }
#

parser_show = subparser.add_parser(
    "show",
    help="show different useful things",
    description="show different useful things",
)
parser_show.set_defaults(func=parser_helper("show"))
show_subparser = parser_show.add_subparsers()

parser_show_logs = show_subparser.add_parser(
    "logs",
    help="show a digest of logs of the firewall",
    description="show dropped packets. Columns are : source_ip " +
    "destination_ip service age"
)
parser_show_logs.set_defaults(func=parser_helper("show_logs"))
parser_show_logs.add_argument(
    "-w",
    "--with-hostnames",
    action="store_true",
    help="display hostname along source_ip",
)
show_logs_subparser = parser_show_logs.add_subparsers()

parser_show_logs_time = show_logs_subparser.add_parser(
    "since",
    help="restrict logs to those since [period] seconds",
)
parser_show_logs_time.add_argument("period", type=int, help="age in seconds")
parser_show_logs_time.set_defaults(func=parser_helper("show_logs"))
parser_show_logs_limit = show_logs_subparser.add_parser(
    "last",
    help="shorten logs to latest [number] hits",
)
parser_show_logs_limit.add_argument("number",
                                    type=int,
                                    help="of latest hits to show")
parser_show_logs_limit.set_defaults(func=parser_helper("show_logs"))

parser_show_realm = show_subparser.add_parser(
    "realm",
    help="show current realm details",
    description="show current realm details",
)
parser_show_realm.set_defaults(func=parser_helper("show_realm"))
parser_show_realms = show_subparser.add_parser(
    "realms",
    help="show realm definitions",
    description="show realm definitions",
)
parser_show_realms.set_defaults(func=parser_helper("show_realms"))

parser_show_table = show_subparser.add_parser(
    "table",
    help="show services allowed in current realm",
    description="show services allowed in current realm",
)
parser_show_table.set_defaults(func=parser_helper("show_table"))

# For "show service", don't define help ; instead overwrite usage because it
# prints the possible arguments twice, which is way too long.
# TODO could tell to search for services with braise show services
# and then don't display "positional arguments" - see
# https://docs.python.org/3.2/library/argparse.html#formatter-class
parser_show_service = show_subparser.add_parser(
    "service",
    usage="%s %s service_name" % (parser_show.prog, "service"),
    help="print service_name's service definition",
    description="print service_name's service definition",
)
parser_show_service.add_argument("service_name", choices=service_defs)
parser_show_service.set_defaults(func=parser_helper("show_service"))
parser_show_services = show_subparser.add_parser(
    "services",
    help="show services list",
    description="show services list",
)
parser_show_services.set_defaults(func=parser_helper("show_services"))

parser_show_port = show_subparser.add_parser(
    "port",
    help="show services associated to port",
    description="show services associated to port",
)
parser_show_port.add_argument("port_name")
parser_show_port.set_defaults(func=parser_helper("show_port"))


#
# SUBPARSERS : { allow | disallow } service
#

parser_allow = subparser.add_parser("allow")
allow_subparser = parser_allow.add_subparsers()
parser_allow_service = allow_subparser.add_parser(
    "service",
    usage="%s service service_name" % parser_allow.prog,
    help="add service service_name to authorized list",
    description="add a service to authorized list",
)
parser_allow_service.add_argument(
    "-g",
    "--globally",
    action="store_true",
    help="enable the service to any client",
)
parser_allow_service.add_argument(
    "-D",
    "--docker",
    action="store_true",
    help="enable the service to docker containers",
)
parser_allow_service.add_argument(
    "-d",
    "--in-default-profile",
    action="store_true",
    help="enable in ServiceWall:default profile instead of current",
)
parser_allow_service.add_argument("service_name", choices=service_defs)
parser_allow_service.set_defaults(func=parser_helper("allow_service"))

parser_disallow = subparser.add_parser("disallow")
disallow_subparser = parser_disallow.add_subparsers()
# We could have used parser_add_service, but the choices aren't the same :
parser_disallow_service = disallow_subparser.add_parser(
    "service",
    usage="%s service service_name" % parser_disallow.prog,
    help="remove service service_name from authorized list",
    description="remove service service_name from authorized list",
)
parser_disallow_service.add_argument(
    "-d",
    "--in-default-profile",
    action="store_true",
    help="enable in ServiceWall:default profile instead of current",
)
parser_disallow_service.add_argument("service_name",
                                     choices=all_allowed_services)
parser_disallow_service.set_defaults(func=parser_helper("disallow_service"))

if ARGCOMPLETE:
    argcomplete.autocomplete(parser)

if len(argv) == 1:
    # display help message when no args are passed.
    print("\n! no argument provided.\n")
    parser.print_help()
    raise SystemExit(1)

# Here, argcomplete stops.
args = parser.parse_args()
# Here, helper callbacks get evaluated.
args.func(args)

