from slither.tools.upgradeability.checks.abstract_checks import (
    AbstractCheck,
    CheckClassification,
    CHECK_INFO,
)
from slither.utils.output import Output


class WereConstant(AbstractCheck):
    ARGUMENT = "were-constant"
    IMPACT = CheckClassification.HIGH

    HELP = "Variables that should be constant"
    WIKI = "https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-that-should-be-constant"
    WIKI_TITLE = "Variables that should be constant"

    # region wiki_description
    WIKI_DESCRIPTION = """
Detect state variables that should be `constant̀`.
"""
    # endregion wiki_description

    # region wiki_exploit_scenario
    WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Contract{
    uint variable1;
    uint constant variable2;
    uint variable3;
}

contract ContractV2{
    uint variable1;
    uint variable2;
    uint variable3;
}
```
Because `variable2` is not anymore a `constant`, the storage location of `variable3` will be different.
As a result, `ContractV2` will have a corrupted storage layout.
"""
    # endregion wiki_exploit_scenario

    # region wiki_recommendation
    WIKI_RECOMMENDATION = """
Do not remove `constant` from a state variables during an update.
"""
    # endregion wiki_recommendation

    REQUIRE_CONTRACT = True
    REQUIRE_CONTRACT_V2 = True

    def _check(self) -> list[Output]:
        contract_v1 = self.contract
        contract_v2 = self.contract_v2

        if contract_v2 is None:
            raise Exception("were-constant requires a V2 contract")
        state_variables_v1 = contract_v1.state_variables
        state_variables_v2 = contract_v2.state_variables

        v2_additional_variables = len(state_variables_v2) - len(state_variables_v1)
        v2_additional_variables = max(v2_additional_variables, 0)

        # We keep two index, because we need to have them out of sync if v2
        # has additional non constant variables
        idx_v1 = 0
        idx_v2 = 0

        results = []
        while idx_v1 < len(state_variables_v1):
            state_v1 = contract_v1.state_variables[idx_v1]
            if len(state_variables_v2) <= idx_v2:
                break

            state_v2 = contract_v2.state_variables[idx_v2]

            if state_v2:
                if state_v1.is_constant:
                    if not state_v2.is_constant:
                        # If v2 has additional non constant variables, we need to skip them
                        if (
                            state_v1.name != state_v2.name or state_v1.type != state_v2.type
                        ) and v2_additional_variables > 0:
                            v2_additional_variables -= 1
                            idx_v2 += 1
                            continue
                        info: CHECK_INFO = [state_v1, " was constant, but ", state_v2, "is not.\n"]
                        json = self.generate_result(info)
                        results.append(json)

            idx_v1 += 1
            idx_v2 += 1

        return results


class BecameConstant(AbstractCheck):
    ARGUMENT = "became-constant"
    IMPACT = CheckClassification.HIGH

    HELP = "Variables that should not be constant"
    WIKI = "https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-that-should-not-be-constant"
    WIKI_TITLE = "Variables that should not be constant"

    # region wiki_description
    WIKI_DESCRIPTION = """
Detect state variables that should not be `constant̀`.
"""
    # endregion wiki_description

    # region wiki_exploit_scenario
    WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Contract{
    uint variable1;
    uint variable2;
    uint variable3;
}

contract ContractV2{
    uint variable1;
    uint constant variable2;
    uint variable3;
}
```
Because `variable2` is now a `constant`, the storage location of `variable3` will be different.
As a result, `ContractV2` will have a corrupted storage layout.
"""
    # endregion wiki_exploit_scenario

    # region wiki_recommendation
    WIKI_RECOMMENDATION = """
Do not make an existing state variable `constant`.
"""
    # endregion wiki_recommendation

    REQUIRE_CONTRACT = True
    REQUIRE_CONTRACT_V2 = True

    def _check(self) -> list[Output]:
        contract_v1 = self.contract
        contract_v2 = self.contract_v2

        if contract_v2 is None:
            raise Exception("became-constant requires a V2 contract")

        state_variables_v1 = contract_v1.state_variables
        state_variables_v2 = contract_v2.state_variables

        v2_additional_variables = len(state_variables_v2) - len(state_variables_v1)
        v2_additional_variables = max(v2_additional_variables, 0)

        # We keep two index, because we need to have them out of sync if v2
        # has additional non constant variables
        idx_v1 = 0
        idx_v2 = 0

        results = []
        while idx_v1 < len(state_variables_v1):
            state_v1 = contract_v1.state_variables[idx_v1]
            if len(state_variables_v2) <= idx_v2:
                break

            state_v2 = contract_v2.state_variables[idx_v2]

            if state_v2:
                if state_v1.is_constant:
                    if not state_v2.is_constant:
                        # If v2 has additional non constant variables, we need to skip them
                        if (
                            state_v1.name != state_v2.name or state_v1.type != state_v2.type
                        ) and v2_additional_variables > 0:
                            v2_additional_variables -= 1
                            idx_v2 += 1
                            continue
                elif state_v2.is_constant:
                    info: CHECK_INFO = [state_v1, " was not constant but ", state_v2, " is.\n"]
                    json = self.generate_result(info)
                    results.append(json)

            idx_v1 += 1
            idx_v2 += 1

        return results
