// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/interface/IORManager.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {BridgeLib} from "../library/BridgeLib.sol";

interface IORManager {
    event ChainInfoUpdated(uint64 indexed id, BridgeLib.ChainInfo chainInfo);
    event ChainTokenUpdated(uint64 indexed id, BridgeLib.TokenInfo tokenInfo);
    event EbcsUpdated(address[] ebcs, bool[] statuses);
    event SubmitterFeeUpdated(address submitter);
    event ProtocolFeeUpdated(uint64 protocolFee);
    event MinChallengeRatioUpdated(uint64 minChallengeRatio);
    event ChallengeUserRatioUpdated(uint64 challengeUserRatio);
    event FeeChallengeSecondUpdated(uint64 feeChallengeSecond);
    event FeeTakeOnChallengeSecondUpdated(uint64 feeTakeOnChallengeSecond);
    event MaxMDCLimitUpdated(uint64 maxMDCLimit);
    event ExtraTransferContractsUpdated(uint64[] chainIds, uint[] extraTransferContracts);

    function registerChains(uint64 enableTime, BridgeLib.ChainInfo[] calldata chains_) external;

    function updateChainSpvs(uint64 enableTime, uint64 id, address[] calldata spvs, uint[] calldata indexs) external;

    function getChainInfo(uint64 id) external view returns (BridgeLib.ChainInfo memory);

    function updateChainTokens(
        uint64 enableTime,
        uint64[] memory ids,
        BridgeLib.TokenInfo[] calldata tokenInfos
    ) external;

    function getChainTokenInfo(uint64 id, uint token) external view returns (BridgeLib.TokenInfo memory);

    function ebcIncludes(address ebc) external view returns (bool);

    function updateEbcs(address[] calldata ebcs_, bool[] calldata statuses) external;

    function submitter() external view returns (address);

    function updateSubmitter(uint64 enableTime, address submitter_) external;

    function protocolFee() external view returns (uint64);

    function updateProtocolFee(uint64 enableTime, uint64 protocolFee_) external;

    function minChallengeRatio() external view returns (uint64);

    function updateMinChallengeRatio(uint64 enableTime, uint64 minChallengeRatio_) external;

    function challengeUserRatio() external view returns (uint64);

    function updateChallengeUserRatio(uint64 enableTime, uint64 challengeUserRatio_) external;

    function feeChallengeSecond() external view returns (uint64);

    function updateFeeChallengeSecond(uint64 enableTime, uint64 feeChallengeSecond_) external;

    function feeTakeOnChallengeSecond() external view returns (uint64);

    function updateFeeTakeOnChallengeSecond(uint64 enableTime, uint64 feeTakeOnChallengeSecond_) external;

    function maxMDCLimit() external view returns (uint64);

    function updateMaxMDCLimit(uint64 maxMDCLimit_) external;

    function getExtraTransferContract(uint64 chainId) external view returns (uint);

    function updateExtraTransferContracts(
        uint64 enableTime,
        uint64[] calldata chainIds,
        uint[] calldata extraTransferContracts
    ) external;
}

// File: contracts/library/BridgeLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library BridgeLib {
    struct TokenInfo {
        uint token; // uint160(address) will overflow in the token used for starknet
        address mainnetToken;
        uint8 decimals;
    }

    struct ChainInfo {
        uint64 id;
        uint192 batchLimit;
        uint64 minVerifyChallengeSourceTxSecond;
        uint64 maxVerifyChallengeSourceTxSecond;
        uint64 minVerifyChallengeDestTxSecond;
        uint64 maxVerifyChallengeDestTxSecond;
        uint nativeToken;
        address[] spvs;
    }
}

// File: contracts/library/ConstantsLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library ConstantsLib {
    uint constant RATIO_MULTIPLE = 1000000;

    uint constant MIN_ENABLE_DELAY = 120; // Unit: second

    uint constant DEALER_WITHDRAW_DELAY = 3600; // Unit: second

    uint constant WITHDRAW_DURATION = 3360; // Unit: second

    uint constant LOCK_DURATION = 240; // Unit: second
    // uint constant DEALER_WITHDRAW_DELAY = 30; // Unit: second for test off-chain

    // uint constant WITHDRAW_DURATION = 300; // Unit: second for test off-chain

    // uint constant LOCK_DURATION = 180; // Unit: second for test off-chain

    uint constant EBC_AMOUNT_PARAMS_MODULUS = 10000;
}

// File: contracts/library/HelperLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library HelperLib {
    function hash(bytes memory data) internal pure returns (bytes32) {
        return keccak256(data);
    }

    function includes(uint[] memory arr, uint element) internal pure returns (bool) {
        for (uint i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(uint[] memory arr, uint[] memory elements) internal pure returns (bool) {
        for (uint i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

            unchecked {
                i++;
            }
        }
        return true;
    }

    function includes(address[] memory arr, address element) internal pure returns (bool) {
        for (uint i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(address[] memory arr, address[] memory elements) internal pure returns (bool) {
        for (uint i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

            unchecked {
                i++;
            }
        }
        return true;
    }
}

// File: contracts/ORManager.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import "./interface/IORManager.sol";
import {VersionAndEnableTime} from "./VersionAndEnableTime.sol";
import {HelperLib} from "./library/HelperLib.sol";

contract ORManager is IORManager, Ownable, VersionAndEnableTime {
    using HelperLib for bytes;

    // Ownable._owner use a slot
    // VersionAndEnableTime._version and _enableTime use a slot

    // Warning: the following order and type changes will cause state verification changes
    mapping(uint64 => BridgeLib.ChainInfo) private _chains;
    mapping(bytes32 => BridgeLib.TokenInfo) private _chainTokens; // hash(chainId, token) => TokenInfo
    mapping(address => bool) private _ebcs;
    address private _submitter;
    uint64 private _protocolFee;
    uint64 private _minChallengeRatio = 20000; // 10,000 percent
    uint64 private _challengeUserRatio; // 10,000 percent
    uint64 private _feeChallengeSecond;
    uint64 private _feeTakeOnChallengeSecond;
    uint64 private _maxMDCLimit = 2 ** 64 - 1;
    mapping(uint64 => uint) private _extraTransferContracts; // Cross-address transfer contracts. chainId => contractAddress

    constructor(address owner_) {
        require(owner_ != address(0), "OZ");
        _transferOwnership(owner_);
    }

    // TODO: setting the same chainId or token affect the protocol?
    function registerChains(uint64 enableTime, BridgeLib.ChainInfo[] calldata chains_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint i = 0; i < chains_.length; i++) {
                // TODO: There may be some settings that need to restrict modification

                // TODO: ORMakerDeposit.checkChallenge use maxVerifyChallengeSourceTxSecond, maxVerifyChallengeSourceTxSecond cannot be modified.
                //       Or make some adjustments when using

                _chains[chains_[i].id] = chains_[i];
                emit ChainInfoUpdated(chains_[i].id, chains_[i]);
            }
        }
    }

    function updateChainSpvs(
        uint64 enableTime,
        uint64 id,
        address[] calldata spvs,
        uint[] calldata indexs
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint i = 0; i < spvs.length; i++) {
                if (i < indexs.length) {
                    _chains[id].spvs[indexs[i]] = spvs[i];
                } else {
                    _chains[id].spvs.push(spvs[i]);
                }
            }
        }
        emit ChainInfoUpdated(id, _chains[id]);
    }

    function getChainInfo(uint64 id) external view returns (BridgeLib.ChainInfo memory) {
        return _chains[id];
    }

    function updateChainTokens(
        uint64 enableTime,
        uint64[] calldata ids,
        BridgeLib.TokenInfo[] calldata tokenInfos
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint i = 0; i < ids.length; i++) {
                // TODO: If the token of layer2 changes, how should it be handled here?
                bytes32 key = abi.encodePacked(ids[i], tokenInfos[i].token).hash();
                _chainTokens[key] = tokenInfos[i];
                emit ChainTokenUpdated(ids[i], tokenInfos[i]);
            }
        }
    }

    function getChainTokenInfo(uint64 id, uint token) external view returns (BridgeLib.TokenInfo memory) {
        bytes32 key = abi.encodePacked(id, token).hash();
        return _chainTokens[key];
    }

    function ebcIncludes(address ebc) external view returns (bool) {
        return _ebcs[ebc];
    }

    function updateEbcs(address[] calldata ebcs_, bool[] calldata statuses) external onlyOwner {
        unchecked {
            for (uint i = 0; i < ebcs_.length; i++) {
                if (i < statuses.length) {
                    _ebcs[ebcs_[i]] = statuses[i];
                } else {
                    _ebcs[ebcs_[i]] = true;
                }
            }
        }
        emit EbcsUpdated(ebcs_, statuses);
    }

    function submitter() external view returns (address) {
        return _submitter;
    }

    function updateSubmitter(uint64 enableTime, address submitter_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _submitter = submitter_;
        emit SubmitterFeeUpdated(_submitter);
    }

    function protocolFee() external view returns (uint64) {
        return _protocolFee;
    }

    function updateProtocolFee(uint64 enableTime, uint64 protocolFee_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _protocolFee = protocolFee_;
        emit ProtocolFeeUpdated(_protocolFee);
    }

    function minChallengeRatio() external view returns (uint64) {
        return _minChallengeRatio;
    }

    function updateMinChallengeRatio(uint64 enableTime, uint64 minChallengeRatio_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _minChallengeRatio = minChallengeRatio_;
        emit MinChallengeRatioUpdated(_minChallengeRatio);
    }

    function challengeUserRatio() external view returns (uint64) {
        return _challengeUserRatio;
    }

    function updateChallengeUserRatio(uint64 enableTime, uint64 challengeUserRatio_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _challengeUserRatio = challengeUserRatio_;
        emit ChallengeUserRatioUpdated(_challengeUserRatio);
    }

    function feeChallengeSecond() external view returns (uint64) {
        return _feeChallengeSecond;
    }

    function updateFeeChallengeSecond(uint64 enableTime, uint64 feeChallengeSecond_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _feeChallengeSecond = feeChallengeSecond_;
        emit FeeChallengeSecondUpdated(_feeChallengeSecond);
    }

    function feeTakeOnChallengeSecond() external view returns (uint64) {
        return _feeTakeOnChallengeSecond;
    }

    function updateFeeTakeOnChallengeSecond(uint64 enableTime, uint64 feeTakeOnChallengeSecond_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _feeTakeOnChallengeSecond = feeTakeOnChallengeSecond_;
        emit FeeTakeOnChallengeSecondUpdated(_feeTakeOnChallengeSecond);
    }

    function maxMDCLimit() external view returns (uint64) {
        return _maxMDCLimit;
    }

    function updateMaxMDCLimit(uint64 maxMDCLimit_) external onlyOwner {
        _maxMDCLimit = maxMDCLimit_;
        emit MaxMDCLimitUpdated(_maxMDCLimit);
    }

    function getExtraTransferContract(uint64 chainId) external view returns (uint) {
        return _extraTransferContracts[chainId];
    }

    function updateExtraTransferContracts(
        uint64 enableTime,
        uint64[] calldata chainIds,
        uint[] calldata extraTransferContracts
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        require(chainIds.length == extraTransferContracts.length, "CEOF");

        for (uint i = 0; i < chainIds.length; i++) {
            _extraTransferContracts[chainIds[i]] = extraTransferContracts[i];
        }
        emit ExtraTransferContractsUpdated(chainIds, extraTransferContracts);
    }
}

// File: contracts/VersionAndEnableTime.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {ConstantsLib} from "./library/ConstantsLib.sol";

contract VersionAndEnableTime {
    uint192 private _version;
    uint64 private _enableTime;

    // TODO: modify requires more gas
    // modifier versionIncreaseAndEnableTime(uint64 enableTime) {
    //     require(enableTime - block.timestamp >= ConstantsLib.MIN_ENABLE_DELAY, "OFET");

    //     _;
    //     _version += 1;

    //     _enableTime = enableTime;
    // }

    function versionIncreaseAndEnableTime(uint64 enableTime) public {
        require(enableTime - block.timestamp >= ConstantsLib.MIN_ENABLE_DELAY, "OFET");

        _version += 1;
        _enableTime = enableTime;
    }

    function getVersionAndEnableTime() external view returns (uint192 version, uint64 enableTime) {
        return (_version, _enableTime);
    }
}