#!/usr/bin/env python3
from __future__ import print_function

import argparse
import json
import datetime
import os
import copy
import pprint
import re
import sys


if sys.version_info > (3, 0):
    PYTHON2 = False
    unicode = str

basename = os.path.basename(__file__)
PROPERTY_JSON_PATH = os.path.join(sys.argv[2] if len(sys.argv) > 1 else __file__)
COPY_YEAR = datetime.datetime.fromtimestamp(os.path.getmtime(PROPERTY_JSON_PATH)).year
hdr = 'This file was autogenerated by %s' % basename
license = """
LINSTOR - management of distributed storage/DRBD9 resources
Copyright (C) 2017 - %s  LINBIT HA-Solutions GmbH
Author: %s

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.""" % (
    COPY_YEAR, ', '.join(['Rene Peinthor', 'Gabor Hernadi']))


class MyPyKey(object):
    def __init__(self, keypath):
        self._keys = keypath

    def __str__(self):
        if isinstance(self._keys, list):
            return " + '/' + ".join(['consts.' + x for x in self._keys])
        return '"{k}"'.format(k=self._keys)

    def __repr__(self):
        return str(self)


def merge_props(prop_a, prop_b):
    prps = copy.deepcopy(prop_a)
    prps['properties'].update(prop_b['properties'])

    for obj in prop_b['objects']:
        for e in prop_b['objects'].get(obj, []):
            if obj not in prps['objects']:
                prps['objects'][obj] = []
            prps['objects'][obj].append(e)

    return prps


def check_duplicate_keys(propdata):
    with open("consts.json") as constfile:
        constjson = json.load(constfile)
        consts = {e['name']: e['value'] for e in constjson if e.get('type') == 'string'}

    prop_map = {}
    for prop_name in propdata['properties']:
        key = propdata['properties'][prop_name]['key']
        if isinstance(key, list):
            key = "/".join([consts[x] for x in key])
        if prop_name in prop_map:
            print("ERROR: DUPLICATE property name '{k}' for prop '{p}'".format(k=key, p=prop_name), file=sys.stderr)
            sys.exit(9)
        prop_map[prop_name] = key

    for obj in propdata['objects']:
        key_map = {}
        for prop_name in propdata['objects'][obj]:
            key = prop_map[prop_name]
            if key in key_map:
                print("ERROR: DUPLICATE property key '{k}' for prop '{p}' in obj '{o}'".format(
                    k=key, p=prop_name, o=obj), file=sys.stderr)
                sys.exit(9)
            key_map[prop_map[prop_name]] = prop_name


def check_info_prop(propdata):
    """
    Checks if all non internal properties do have an info string
    :param propdata: dict with all properties
    :return:
    """
    for pn in propdata['properties']:
        prop = propdata['properties'][pn]
        if not prop.get('internal', False) and 'info' not in prop:
            print("ERROR: Public property '{p}' does not have an info.".format(p=pn), file=sys.stderr)
            sys.exit(8)


def lang_python(data):
    print('"""')
    print('{h}\n{lic}'.format(h=hdr, lic=license))
    print('"""')
    print('import linstor.sharedconsts as consts')

    properties = data['properties']
    objects = data['objects']
    p = {}

    # prefix keys with consts.
    for prop in properties:
        key = properties[prop]['key']
        properties[prop]['key'] = MyPyKey(key)

    print('properties = \\')
    for objkey in objects:
        obj_props = objects[objkey]
        resolved = []
        for prop in obj_props:
            resolved.append(properties[prop])

        p[objkey] = resolved

    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(p)
    return True


def lang_java(data):
    properties = data['properties']
    objects = data['objects']

    license_hdr = ''
    for line in license.split('\n'):
        license_hdr += (' * ' + line).rstrip() + '\n'

    print('/*\n * %s\n%s */\n' % (hdr, license_hdr))

    _print(0, 'package com.linbit.linstor.api.prop;')
    _print(0, '')
    _print(0, 'import java.util.ArrayList;')
    _print(0, 'import java.util.Arrays;')
    _print(0, 'import java.util.List;')
    _print(0, 'import java.util.Map;')
    _print(0, 'import java.util.TreeMap;')
    _print(0, '')
    _print(0, 'public final class GeneratedPropertyRules\n{')
    _print(1,     'public static List<Property> getWhitelistedProperties()')
    _print(1,     '{')
    _print(2,         'List<Property> propertyList = new ArrayList<>();')
    for key in sorted(properties.keys()):
        prop = properties[key]
        _print(2,     'propertyList.add(')
        _print(3,         'new PropertyBuilder()')
        _print(4,             '.name("%s")' % key)
        for propKey in sorted(prop.keys()):
            if _relevant_for_java(propKey):
                propVal = prop[propKey]
                if isinstance(propVal, unicode):
                    propVal = propVal.replace('"', '\\"')
                if propKey == "key":
                    if isinstance(propVal, list):
                        _print(4, '.keyRef("%s")' % ('", "'.join(propVal)))
                    else:
                        _print(4, '.keyStr("%s")' % (propVal))
                elif propKey == "values":
                    _print(4,     '.values(')
                    _print(5,         '"%s"' % ('",\n' + _indent(5) + '"').join(sorted(propVal)))
                    _print(4,     ')')
                elif propKey == "default":
                    _print(4, '.dflt("%s")' % (propVal))
                else:
                    _print(4,     '.%s("%s")' % (propKey, propVal))
        _print(4,             '.build()')
        _print(2,     ');')
    _print(2,         'return propertyList;')
    _print(1,     '}\n')
    _print(1,     'public static Map<LinStorObject, List<String>> getWhitelistedRules()')
    _print(1,     '{')
    _print(2,         'Map<LinStorObject, List<String>> rules = new TreeMap<>();')
    for obj_name in sorted(objects.keys()):
        rule_names = sorted(objects[obj_name])
        if not rule_names:
            _print(2, 'rules.put(LinStorObject.%s, Collections.emptyList());' % _as_java_enum_name(obj_name))
        else:
            _print(2, 'rules.put(LinStorObject.%s,' % _as_java_enum_name(obj_name))
            _print(3,     'Arrays.asList(')
            _print(4,         '"%s"' % ('",\n' + _indent(4) + '"').join(rule_names))
            _print(3,     ')')
            _print(2, ');')
    _print(2,         'return rules;')
    _print(1,     '}')
    _print(0, '}\n')
    return True


def _print(indent_level, line):
    indent = _indent(indent_level)
    print('%s%s' % (indent, line.strip()))


def _indent(indent_level):
    return ' ' * 4 * indent_level


def _relevant_for_java(propKey):
    return propKey not in ['drbd_option_name', 'unit_prefix', 'drbd_res_file_section']


def _as_java_rule_name(name):
    return re.sub(r'_(.)', lambda pat: pat.group(1).upper(), name) + 'Rule'


def _as_java_enum_name(name):
    return re.sub(r'-', '_', name).upper()


def main():
    parser = argparse.ArgumentParser(prog="genproperties.py")
    parser.add_argument('language', choices=['python', 'java'])
    parser.add_argument('propfile', nargs='+')

    args = parser.parse_args()

    propdata = {
        "properties": {},
        "objects": {}
    }

    for propfile_name in args.propfile:
        with open(propfile_name) as propfile:
            propdata = merge_props(propdata, json.load(propfile))

    # check duplicate keys
    check_duplicate_keys(propdata)

    # check info prop
    check_info_prop(propdata)

    if args.language == 'python':
        lang_python(propdata)
    elif args.language == 'java':
        lang_java(propdata)


if __name__ == '__main__':
    main()
