// File: contracts/interface/IORSpvData.sol



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

interface IORSpvData {
    struct InjectionBlocksRoot {
        uint256 startBlockNumber;
        bytes32 blocksRoot;
    }

    event BlockIntervalUpdated(uint64 blockInterval);
    event InjectOwnerUpdated(address injectOwner);
    event HistoryBlocksRootSaved(uint256 indexed startBlockNumber, bytes32 blocksRoot, uint256 blockInterval);

    function blockInterval() external view returns (uint64);

    function updateBlockInterval(uint64 blockInterval_) external;

    function saveHistoryBlocksRoots() external;

    function getStartBlockNumber(bytes32 blocksRoot) external view returns (uint);

    function injectOwner() external view returns (address);

    function updateInjectOwner(address injectOwner_) external;

    function injectBlocksRoots(
        bytes32 blocksRoot0,
        bytes32 blocksRoot1,
        InjectionBlocksRoot[] calldata injectionBlocksRoots
    ) external;
}

// 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(uint256[] memory arr, uint256 element) internal pure returns (bool) {
        for (uint256 i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(uint256[] memory arr, uint256[] memory elements) internal pure returns (bool) {
        for (uint256 i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint256 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 (uint256 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 (uint256 i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint256 j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

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

    function calculateChallengeIdentNum(
        uint64 sourceTxTime,
        uint64 sourceChainId,
        uint64 sourceTxBlockNum,
        uint64 sourceTxIndex
    ) internal pure returns (uint256) {
        uint256 challengeIdentNum;

        assembly {
            challengeIdentNum := add(
                shl(192, sourceTxTime),
                add(shl(128, sourceChainId), add(shl(64, sourceTxBlockNum), sourceTxIndex))
            )
        }
        return challengeIdentNum;
    }
}

// File: contracts/ORSpvData.sol



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

import {HelperLib} from "./library/HelperLib.sol";
import {IORSpvData} from "./interface/IORSpvData.sol";

contract ORSpvData is IORSpvData {
    using HelperLib for bytes;

    address public manager;

    uint64 private _blockInterval = 192;
    address private _injectOwner;
    mapping(bytes32 => uint) private _blocksRoots; // [start ..._blockInterval... end]'s blocks root => startBlockNumber

    constructor(address manager_, address injectOwner_) {
        require(manager_ != address(0), "MZ");
        manager = manager_;

        if (injectOwner_ != address(0)) {
            _injectOwner = injectOwner_;
            emit InjectOwnerUpdated(injectOwner_);
        }
    }

    modifier onlyManager() {
        require(msg.sender == manager, "Forbidden: caller is not the manager");
        _;
    }

    function blockInterval() external view returns (uint64) {
        return _blockInterval;
    }

    function updateBlockInterval(uint64 blockInterval_) external onlyManager {
        require(blockInterval_ >= 2 && blockInterval_ <= 256, "IOF");
        require(blockInterval_ % 2 == 0, "IV");

        _blockInterval = blockInterval_;

        emit BlockIntervalUpdated(blockInterval_);
    }

    function _calculateRoot(uint startBlockNumber) internal view returns (bytes32) {
        uint len = _blockInterval / 2;
        bytes32 root;
        assembly {
            let leaves := mload(0x40)
            mstore(0x40, add(leaves, mul(len, 0x20)))

            // The lowest layer is calculated separately from other layers to save gas
            for {
                let i := 0
                let leavesPtr := leaves
            } lt(i, len) {
                i := add(i, 1)
            } {
                let ix2 := mul(i, 2)
                let data := mload(0x40)
                mstore(data, blockhash(add(startBlockNumber, ix2)))
                mstore(add(data, 0x20), blockhash(add(startBlockNumber, add(ix2, 1))))

                mstore(leavesPtr, keccak256(data, 0x40))

                // Release memory
                data := mload(0x40)

                leavesPtr := add(leavesPtr, 0x20)
            }

            for {

            } gt(len, 1) {
                len := add(div(len, 2), mod(len, 2))
            } {
                for {
                    let i := 0
                    let leavesPtr := leaves
                } lt(i, len) {
                    i := add(i, 2)
                } {
                    // Default
                    let ptrL := add(leaves, mul(i, 0x20))
                    mstore(leavesPtr, mload(ptrL))

                    // When i+1 < len, hash(ptrL connect ptrR)
                    if lt(add(i, 1), len) {
                        let ptrR := add(ptrL, 0x20)

                        let data := mload(0x40)
                        mstore(data, mload(ptrL))
                        mstore(add(data, 0x20), mload(ptrR))

                        mstore(leavesPtr, keccak256(data, 0x40))

                        // Release memory
                        data := mload(0x40)
                    }

                    leavesPtr := add(leavesPtr, 0x20)
                }
            }

            root := mload(leaves)
        }

        return root;
    }

    function saveHistoryBlocksRoots() external {
        uint256 currentBlockNumber = block.number;
        uint256 bi = _blockInterval;
        uint256 startBlockNumber = currentBlockNumber - 256;
        uint256 batchLen;
        unchecked {
            uint256 m = startBlockNumber % bi;
            if (m > 0) {
                startBlockNumber += bi - m;
            }

            batchLen = (currentBlockNumber - 1 - startBlockNumber) / bi;
        }

        // Reject when batchLen == 0, save gas
        require(batchLen > 0, "IBL");

        for (uint256 i = 0; i < batchLen; ) {
            bytes32 root = _calculateRoot(startBlockNumber);

            if (_blocksRoots[root] == 0 && root != bytes32(0)) {
                _blocksRoots[root] = startBlockNumber;
                emit HistoryBlocksRootSaved(startBlockNumber, root, bi);
            }

            unchecked {
                startBlockNumber += bi;
                i++;
            }
        }
    }

    function getStartBlockNumber(bytes32 blocksRoot) external view returns (uint) {
        return _blocksRoots[blocksRoot];
    }

    function injectOwner() external view returns (address) {
        return _injectOwner;
    }

    function updateInjectOwner(address injectOwner_) external onlyManager {
        _injectOwner = injectOwner_;
        emit InjectOwnerUpdated(injectOwner_);
    }

    function injectBlocksRoots(
        bytes32 blocksRoot0,
        bytes32 blocksRoot1,
        InjectionBlocksRoot[] calldata injectionBlocksRoots
    ) external {
        require(msg.sender == _injectOwner, "Forbidden: caller is not the inject owner");

        uint blockNumber0 = _blocksRoots[blocksRoot0];
        uint blockNumber1 = _blocksRoots[blocksRoot1];

        require(blockNumber0 < blockNumber1, "SNLE");

        // Make sure the blockNumber0 and blockNumber1 at storage
        require(blockNumber0 != 0, "SZ");
        require(blockNumber1 != 0, "EZ"); // This logic may never be false

        uint256 i = 0;
        uint256 ni = 0;
        for (; i < injectionBlocksRoots.length; ) {
            unchecked {
                ni = i + 1;
            }

            InjectionBlocksRoot memory ibsr = injectionBlocksRoots[i];

            require(blockNumber0 < ibsr.startBlockNumber, "IBLE0");
            require(blockNumber1 > ibsr.startBlockNumber, "IBGE1");
            require(ibsr.startBlockNumber % _blockInterval == 0, "IIB");
            require(_blocksRoots[ibsr.blocksRoot] == 0, "BE");

            _blocksRoots[ibsr.blocksRoot] = ibsr.startBlockNumber;
            emit HistoryBlocksRootSaved(ibsr.startBlockNumber, ibsr.blocksRoot, _blockInterval);

            i = ni;
        }
    }
}