#!/usr/bin/python3

import argparse
import json
import os
import re
import sys

import clickreviews.snapd_base_declaration as snapd_base_declaration

decl = {}

# If local_copy is None, then this will check the server to see if
# we are up to date. However, if we are working within the development
# tree, use it unconditionally.
local_copy = None
branch_fn = os.path.join(os.path.dirname(__file__),
                         '../data/snapd-base-declaration.yaml')
if os.path.exists(branch_fn):
    local_copy = branch_fn
p = snapd_base_declaration.SnapdBaseDeclaration(local_copy)
# TODO: don't hardcode
base_decl_series = "16"
base_decl = p.decl[base_decl_series]


def _verify_alias(alias):
    # from snapd validate.go
    pat = re.compile(r'^[a-zA-Z0-9][-_.a-zA-Z0-9]*$')
    if not pat.search(alias):
        raise Exception("'%s' is malformed (must match " % alias +
                        "'^[a-zA-Z0-9][-_.a-zA-Z0-9]*$')")
    if 'auto-aliases' in decl and alias in decl['auto-aliases']:
        raise Exception("'%s' should only be declared once" % alias)


def add_alias(e):
    tmp = e.split(':')
    if len(tmp) != 2:
        raise Exception("'%s' should be '<alias>:<command>'" % e)
    alias = tmp[0]
    _verify_alias(alias)
    command = tmp[1]
    _verify_alias(command)

    if 'auto-aliases' not in decl:
        decl['auto-aliases'] = []

    decl['auto-aliases'].append({"name": alias, "target": command})


def _verify_snap_id(id):
    # from snapd ifacedecls.go
    pat = re.compile(r'^[a-z0-9A-Z]{32}$')
    if not pat.search(id):
        raise Exception("'%s' is malformed (must match '^[a-z0-9A-Z]{32}$')"
                        % id)
    if 'refresh-control' in decl and id in decl['refresh-control']:
        raise Exception("'%s' should only be declared once" % id)


def add_refresh_control(id):
    _verify_snap_id(id)

    if 'refresh-control' not in decl:
        decl['refresh-control'] = []

    decl['refresh-control'].append(id)


def _verify_interface(iface):
    found = False
    if "slots" in base_decl and iface in base_decl["slots"]:
        found = True
    elif "plugs" in base_decl and iface in base_decl["plugs"]:
        found = True

    return found


def bool2str(value):
    if value is True or value is False:
        if value:
            return "true"
        return "false"
    return value


def add_interface(side, iface, key, value):
    if not _verify_interface(iface):
        raise Exception("Invalid interface '%s'" % iface)

    if side not in decl:
        decl[side] = {}

    if iface not in decl[side]:
        decl[side][iface] = {}

    if key not in decl[side][iface]:
        decl[side][iface][key] = bool2str(value)
    else:
        raise Exception("'%s' already specified for '%s'" % (key, iface))


def add_missing():
    for side in ["slots", "plugs"]:
        if side not in decl:
            continue
        if side not in base_decl:
            raise Exception("Could not find '%s' in base declaration" % side)

        for iface in decl[side]:
            if iface not in base_decl[side]:
                continue

            # TODO: support alternate constraints
            if isinstance(base_decl[side][iface], list):
                print("INFO: skipping base alternation constraints")
                continue

            for cstr in ['installation', 'connection', 'auto-connection']:
                in_base = False
                cstr_b = ""
                for perm in ['allow', 'deny']:
                    cstr_b = '%s-%s' % (perm, cstr)
                    if cstr_b in base_decl[side][iface]:
                        in_base = True
                        break

                cstr_s = "allow-%s" % cstr
                if in_base and cstr_s not in decl[side][iface]:
                    print("WARN: adding missing '%s' for '%s' from base decl"
                          % (cstr, iface))
                    decl[side][iface][cstr_b] = \
                        bool2str(base_decl[side][iface][cstr_b])


def print_decl():
    def _print_key(key):
        print(json.dumps(decl[key], sort_keys=True, indent=2))
    if "slots" in decl:
        print("slots:")
        _print_key("slots")
    if "plugs" in decl:
        print("plugs:")
        _print_key("plugs")
    if "auto-aliases" in decl:
        print("aliases (v2):")
        _print_key("auto-aliases")
        # compatibility
        print("auto-aliases (compat):")
        aliases = []
        for item in decl['auto-aliases']:
            aliases.append(item["name"])
        aliases.sort()
        print(json.dumps(aliases, sort_keys=True, indent=2))

    if "refresh-control" in decl:
        print("refresh-control:")
        _print_key("refresh-control")


def main():
    parser = argparse.ArgumentParser(
        prog='create-snap-declaration',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description='Output data suitable for adding to snap declaration')
    parser.add_argument('--slot-installation', type=str,
                        help='list of interfaces to allow installation')
    parser.add_argument('--slot-connection', type=str,
                        help='list of interfaces to allow connection')
    parser.add_argument('--slot-auto-connection', type=str,
                        help='list of interfaces to allow auto-connection')
    parser.add_argument('--plug-installation', type=str,
                        help='list of interfaces to allow installation')
    parser.add_argument('--plug-connection', type=str,
                        help='list of interfaces to allow connection')
    parser.add_argument('--plug-auto-connection', type=str,
                        help='list of interfaces to allow auto-connection')
    parser.add_argument('--auto-aliases', type=str,
                        help='list of auto-aliases '
                             '(<alias name>:<target command>)')
    parser.add_argument('--refresh-control', type=str,
                        help='list of snap IDs to be gated by this snap')
    args = parser.parse_args()

    if args.slot_installation:
        for i in args.slot_installation.split(','):
            add_interface("slots", i, "allow-installation", True)

    if args.slot_connection:
        for i in args.slot_connection.split(','):
            add_interface("slots", i, "allow-connection", True)

    if args.slot_auto_connection:
        for i in args.slot_auto_connection.split(','):
            add_interface("slots", i, "allow-auto-connection", True)

    if args.plug_installation:
        for i in args.plug_installation.split(','):
            add_interface("plugs", i, "allow-installation", True)

    if args.plug_connection:
        for i in args.plug_connection.split(','):
            add_interface("plugs", i, "allow-connection", True)

    if args.plug_auto_connection:
        for i in args.plug_auto_connection.split(','):
            add_interface("plugs", i, "allow-auto-connection", True)

    if args.auto_aliases:
        for i in args.auto_aliases.split(','):
            add_alias(i)

    if args.refresh_control:
        for i in args.refresh_control.split(','):
            add_refresh_control(i)

    add_missing()

    print_decl()

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("Aborted.")
        sys.exit(1)
