#!/usr/bin/env python3
# coding=utf-8
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import json
import logging
import getopt
import sys
import os
import stat
from collections import OrderedDict
import mds_util as utils


def save_file(of_name, model_new):
    if os.path.exists(of_name):
        with os.fdopen(os.open(of_name, os.O_RDONLY, stat.S_IRUSR), "r") as load_f:
            if json.load(load_f) == model_new:
                logging.info("schema 未发生更改")
                return

    with os.fdopen(
        os.open(
            of_name, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IWUSR | stat.S_IRUSR
        ),
        "w",
    ) as load_f:
        json.dump(model_new, load_f, indent=4)
        logging.info("schema 已经更改")


def merge_props_and_data(prop, properties, prop_data, check_props):
    if prop not in properties:
        properties[prop] = {}
    else:
        for check_prop in check_props:
            if properties[prop].get(check_prop, False):
                prop_data[check_prop] = properties[prop].get(check_prop)
    properties[prop] = prop_data


def merge_when_intf_exist(model, intf, item, check_props):
    if item not in intf:
        if item in model:
            model.pop(item)
        return

    if item not in model:
        model[item] = {}

    properties = model[item]
    for prop in list(properties):
        if prop in intf[item]:
            continue
        label = "方法" if item == "methods" else "信号"
        raise RuntimeError(f"{label} {prop} 在mdb_interface中没有被定义")

    for prop, prop_data in intf[item].items():
        merge_props_and_data(prop, properties, prop_data, check_props)


def copy_when_exist(model, intf, prop):
    if prop in intf:
        model[prop] = intf[prop]


def merge_model_intf(intf_data, model_intf):
    check_props = ["usage", "alias", "primaryKey", "uniqueKey", "privilege", "default", "featureTag", "critical",
        "notAllowNull"]
    mdb_props = intf_data.get("properties", {})
    if "properties" not in model_intf:
        model_intf["properties"] = {}
    model_props = model_intf["properties"]
    for prop in model_props.keys():
        if prop not in mdb_props:
            raise RuntimeError(f"属性 {prop} 在mdb_interface中没有被定义")
    if mdb_props and "virtual" in intf_data:
        model_props["priority"] = {
            "baseType": "U8",
            "default": 0
            if "priority" not in model_intf
            else model_intf["priority"],
        }
    for prop, prop_data in mdb_props.items():
        merge_props_and_data(prop, model_props, prop_data, check_props)

    merge_when_intf_exist(model_intf, intf_data, "methods", check_props)
    merge_when_intf_exist(model_intf, intf_data, "signals", check_props)
    copy_when_exist(model_intf, intf_data, "package")
    copy_when_exist(model_intf, intf_data, "virtual")
    copy_when_exist(model_intf, intf_data, "default")


def append_object_prop_intf(mds_data, mdb_data):
    object_prop_intf = "bmc.kepler.Object.Properties"
    if object_prop_intf not in mds_data:
        mds_data[object_prop_intf] = {}
    if object_prop_intf not in mdb_data:
        mdb_data.append(object_prop_intf)


def merge_model_class(class_name, mds_class, mdb_obj, mdb_path):
    if "package" in mdb_obj[class_name]:
        mds_class["package"] = mdb_obj[class_name]["package"]
    append_object_prop_intf(mds_class["interfaces"], mdb_obj[class_name]["interfaces"])
    for intf_name in mdb_obj[class_name]["interfaces"]:
        intf_json = utils.get_intf(intf_name, mdb_path)
        if "implement" in intf_json[intf_name]:
            mds_class["interfaces"][intf_name] = utils.generate_default(
                intf_json, mdb_path
            )[intf_name]
        if "defs" in intf_json:
            mds_class["interfaces"][intf_name]["defs"] = intf_json["defs"]

        merge_model_intf(intf_json[intf_name], mds_class["interfaces"][intf_name])


def get_class_name(path):
    return list(filter(None, path.split("/")))[-1]


def get_parent_path(origin_model, class_data):
    if "parent" in class_data and class_data["parent"] in origin_model and \
                "path" in origin_model[class_data["parent"]]:
        return get_parent_path(origin_model, origin_model[class_data["parent"]]) \
            + "/" + utils.cut_ids(class_data["path"])
    else:
        return utils.cut_ids(class_data["path"])


def check_class_property_name_conflict(class_name, class_data):
    prop_names = {}

    for prop_name, prop_config in class_data.get("properties", {}).items():
        name = prop_config.get('alias', prop_name)
        if name in prop_names:
            raise RuntimeError(f"在{class_name}类中发现重名私有属性{prop_name}")
        else:
            prop_names[name] = True

    for interface, intf_data in class_data.get("interfaces", {}).items():
        for prop_name, prop_config in intf_data.get("properties", {}).items():
            name = prop_config.get('alias', prop_name)
            if name in prop_names:
                raise RuntimeError(f"在{class_name}类的{interface}接口中发现重名资源树属性{prop_name}")
            else:
                prop_names[name] = True


def check_property_name_conflict(origin_model):
    for class_name, class_data in origin_model.items():
        check_class_property_name_conflict(class_name, class_data)


def merge_model(origin_model, mdb_path):
    for class_name, class_data in origin_model.items():
        if "path" in class_data and class_name != "defs":
            class_path = get_parent_path(origin_model, class_data)
            mdb_obj = utils.get_path(class_name, mdb_path, class_path)
            merge_model_class(class_name, class_data, mdb_obj, mdb_path)

    check_property_name_conflict(origin_model)


def save_merged_json(of_name, model):
    paths = of_name.split("/")
    paths.pop()
    merged_json_path = os.path.realpath(("/").join(paths))
    if not os.path.exists(merged_json_path):
        os.mkdir(merged_json_path)
    save_file(of_name, model)


def generate(if_name, of_name, mdb_path):
    load_dict = {}
    if os.path.exists(if_name):
        load_f = os.fdopen(os.open(if_name, os.O_RDONLY, stat.S_IRUSR), "r")
        load_dict = OrderedDict(json.load(load_f))
        load_f.close()

    merge_model(load_dict, mdb_path)
    save_merged_json(of_name, load_dict)


def usage():
    logging.info("gen_schema.py -i <inputfile> -o <outfile>")


def main(argv):
    m_input = ""
    output = ""
    mdb_path = ""
    try:
        opts, _ = getopt.getopt(
            argv, "hi:o:d:", ["help", "input=", "out=", "mdb_interfac_path"]
        )
    except getopt.GetoptError:
        help()
        return
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage()
            return
        elif opt in ("-i", "--input"):
            m_input = arg
        elif opt in ("-o", "--out"):
            output = arg
        elif opt in ("-d", "--dir"):
            mdb_path = arg
        else:
            raise RuntimeError("不支持的选项: {}".format(opt))
    if not m_input or not output:
        usage()
        return
    generate(m_input, output, mdb_path)


if __name__ == "__main__":
    main(sys.argv[1:])
