# -------------------------------------------------------------------------
#  This file is part of the MindStudio project.
# Copyright (c) 2025 Huawei Technologies Co.,Ltd.
#
# MindStudio 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.
# -------------------------------------------------------------------------

from functools import wraps
from typing import Callable, Union, List
from enum import Enum


class EnumInstance(Enum):
    NO_INSTANCE = 0


class CheckResult:
    def __init__(self, passed=True, msg="") -> None:
        self.passed = bool(passed)
        self.msg = msg

    def __bool__(self):
        return self.passed

    def __str__(self) -> str:
        return self.get_msg()

    def __repr__(self) -> str:
        return f"{self.passed}:{self.msg}"

    def get_msg(self):
        return "pass" if self.passed else self.msg


class WaitingRule:
    def __init__(self, func, args, kwargs, err_msg=None) -> None:
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.err_msg = f"{self.name} check failed." if err_msg is None else err_msg

    @property
    def name(self):
        return self.func.__name__

    def check(self, instence: "CheckerInstence"):
        passed = self.func(instence, *self.args, **self.kwargs)
        if isinstance(passed, CheckResult):
            return passed
        elif isinstance(passed, tuple) and len(passed) == 2:
            return CheckResult(passed[0], passed[1])
        else:
            return CheckResult(bool(passed), self.err_msg)


class Recorder:
    def __init__(self) -> None:
        self.record_set = set()

    def add_record(self, *names) -> None:
        self.record_set.update(names)

    def in_records(self, name) -> bool:
        return name in self.record_set

    def union(self, recorder: "Recorder"):
        self.record_set.update(recorder.record_set)


class RuleRunner:
    def __init__(self) -> None:
        self.waiting_rules: List[WaitingRule] = []
        self.default_rules: List[WaitingRule] = []
        self.running_default = False
        self.instence = None
        self.recorder = Recorder()

    def add_rule(self, waiting_rule):
        self.waiting_rules.append(waiting_rule)

    def set_recorder(self, recorder: Recorder):
        self.recorder = recorder

    def get_recorder(self) -> Recorder:
        return self.recorder

    def run_rule(self, waiting_rule: WaitingRule):
        if self.running_default and self.recorder.in_records(waiting_rule.name):
            return CheckResult()
        self.recorder.add_record(waiting_rule.name)
        return waiting_rule.check(self.instence)

    def implement_check(self, instence: "CheckerInstence"):
        self.instence = instence
        self.running_default = False

        for waiting_rule in self.waiting_rules:
            passed = self.run_rule(waiting_rule)
            if not passed:
                return passed

        self.running_default = True
        for waiting_rule in self.default_rules:
            passed = self.run_rule(waiting_rule)
            if not passed:
                return passed
        return CheckResult()

    def is_running(self):
        return self.instence is not None

    def as_default(self):
        self.default_rules = self.waiting_rules
        self.waiting_rules = []
        return self


class CheckerInstence:
    def __init__(self, instance=EnumInstance.NO_INSTANCE, converter=None):
        self.instance = instance
        self.converter = converter

    def has_instance(self):
        return self.instance != EnumInstance.NO_INSTANCE

    def get_instance(self):
        return self.instance

    def get_value(self):
        # for args type
        return self.instance

    def convert_instance(self):
        if self.converter is None:
            return CheckResult()

        self.instance, passed, err_msg = self.converter(self.instance)
        return CheckResult(passed, err_msg)


def rule(err_msg=None) -> Callable:
    if not isinstance(err_msg, (type(None), str)):
        raise TypeError("err_msg must be a string")

    def make_rule_wrapper(func: Callable) -> Callable:
        if not callable(func):
            raise ValueError("The @rule decorator can only be applied to functions")

        @wraps(func)
        def wrapper(checker: "Checker", *args, **kwargs) -> Union["Checker", CheckResult]:
            waiting_rule = WaitingRule(func, args, kwargs, err_msg)
            if checker.is_running():
                return checker.run_rule(waiting_rule)
            else:
                checker.add_rule(waiting_rule)

            if checker.has_instance():
                return checker.check(checker.get_instance())

            return checker

        return wrapper

    return make_rule_wrapper


class Checker(RuleRunner, CheckerInstence):
    def __init__(self, instance=EnumInstance.NO_INSTANCE, converter=None) -> None:
        RuleRunner.__init__(self)
        CheckerInstence.__init__(self, instance, converter)

    def check(self, value, will_raise=False) -> CheckResult:
        self.instance = value
        passed = self.convert_instance() and self.implement_check(self)
        if not passed and will_raise:
            raise ValueError(passed)
        return passed

    @rule(err_msg="value is None")
    def not_none(self) -> Union["Checker", CheckResult]:
        return self.instance is not None

    @rule(err_msg="value is not None")
    def is_none(self) -> Union["Checker", CheckResult]:
        return self.instance is None

    @rule()
    def not_eq(self, cmp_value) -> Union["Checker", CheckResult]:
        return self.instance != cmp_value, f"equal {cmp_value}"

    @rule()
    def eq(self, cmp_value) -> Union["Checker", CheckResult]:
        return self.instance == cmp_value, f"not equal {cmp_value}?"

    @rule()
    def is_type(self, *types) -> Union["Checker", CheckResult]:
        return self.not_none() and isinstance(self.instance, types), f"is not {types}"

    @rule()
    def any(self, *rules: "Checker") -> Union["Checker", CheckResult]:
        passed = CheckResult()
        for running_rule in rules:
            if self.running_default:
                running_rule.set_recorder(self.recorder)
            passed = running_rule.check(self.instance)
            if passed:
                if not self.running_default:
                    self.recorder.union(running_rule.get_recorder())
                return passed

        return passed

    @rule()
    def anti(self, anti_rule: "Checker") -> Union["Checker", CheckResult]:
        anti_rule.set_recorder(self.recorder)
        passed = anti_rule.check(self.instance)

        return not passed, f"not {str(passed)}"
