// File: contracts/adapters/Celer/CelerHeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { HeaderReporter } from "../HeaderReporter.sol";
import { CelerReporter } from "./CelerReporter.sol";

contract CelerHeaderReporter is HeaderReporter, CelerReporter {
    constructor(
        address headerStorage,
        uint256 adapterChain,
        address celerBus,
        uint32 celerAdapterChain
    ) HeaderReporter(headerStorage, adapterChain) CelerReporter(celerBus, celerAdapterChain) {} // solhint-disable no-empty-blocks

    function _sendPayload(bytes memory payload, address adapter) internal virtual override {
        _celerSend(payload, adapter);
    }
}

// File: contracts/adapters/Celer/CelerReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { IMessageBus } from "./interfaces/IMessageBus.sol";

abstract contract CelerReporter {
    string public constant PROVIDER = "celer";
    IMessageBus public immutable CELER_BUS;
    uint64 public immutable CELER_ADAPTER_CHAIN;

    constructor(address celerBus, uint32 celerAdapterChain) {
        CELER_BUS = IMessageBus(celerBus);
        CELER_ADAPTER_CHAIN = celerAdapterChain;
    }

    function _celerSend(bytes memory payload, address adapter) internal {
        CELER_BUS.sendMessage{ value: msg.value }(adapter, CELER_ADAPTER_CHAIN, payload);
    }
}

// File: contracts/adapters/Celer/interfaces/IMessageBus.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

interface IMessageBus {
    /**
     * @notice Send a message to a contract on another chain.
     * Sender needs to make sure the uniqueness of the message Id, which is computed as
     * hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
     * If messages with the same Id are sent, only one of them will succeed at dst chain..
     * A fee is charged in the native gas token.
     * @param _receiver The address of the destination app contract.
     * @param _dstChainId The destination chain ID.
     * @param _message Arbitrary message bytes to be decoded by the destination app contract.
     */
    function sendMessage(address _receiver, uint256 _dstChainId, bytes calldata _message) external payable;
}

// File: contracts/adapters/HeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { IHeaderReporter } from "../interfaces/IHeaderReporter.sol";
import { HeaderStorage } from "../utils/HeaderStorage.sol";

abstract contract HeaderReporter is IHeaderReporter {
    HeaderStorage public immutable HEADER_STORAGE;
    uint256 public immutable ADAPTER_CHAIN;

    event HeaderReported(address indexed emitter, uint256 indexed blockNumber, bytes32 indexed blockHeader);

    /// @dev Constructs base reporter abstracted from specific message transport
    /// @param headerStorage HeaderStorage contract on this chain to use for block hash obtaining
    /// @param adapterChain Chain ID of the adapter that is served by this reporter
    constructor(address headerStorage, uint256 adapterChain) {
        HEADER_STORAGE = HeaderStorage(headerStorage);
        ADAPTER_CHAIN = adapterChain;
    }

    function reportHeaders(uint256[] memory blockNumbers, address adapter) external payable {
        bytes32[] memory blockHeaders = HEADER_STORAGE.storeBlockHeaders(blockNumbers);
        bytes memory payload = abi.encode(blockNumbers, blockHeaders);
        _sendPayload(payload, adapter);
        for (uint i = 0; i < blockNumbers.length; i++) {
            emit HeaderReported(address(this), blockNumbers[i], blockHeaders[i]);
        }
    }

    function _sendPayload(bytes memory payload, address adapter) internal virtual;
}

// File: contracts/interfaces/IHeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

interface IHeaderReporter {
    /// @dev Reports the given block hash to a different chain according to the reporter configuration.
    /// @param blockNumbers Block numbers to report hashes for.
    /// @param adapter Adapter contract address to report hashes for.
    function reportHeaders(uint256[] memory blockNumbers, address adapter) external payable;
}

// File: contracts/utils/HeaderStorage.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

contract HeaderStorage {
    mapping(uint256 => bytes32) public headers;

    event HeaderStored(uint256 indexed blockNumber, bytes32 indexed blockHeader);

    error HeaderOutOfRange(address emitter, uint256 blockNumber);

    /// @dev Stores and returns the header for the given block.
    /// @param blockNumber Block number.
    /// @return blockHeader Block header stored.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeader(uint256 blockNumber) public returns (bytes32 blockHeader) {
        blockHeader = headers[blockNumber];
        if (blockHeader == 0) {
            blockHeader = blockhash(blockNumber);
            if (blockHeader == 0) revert HeaderOutOfRange(address(this), blockNumber);
            headers[blockNumber] = blockHeader;
            emit HeaderStored(blockNumber, blockHeader);
        }
    }

    /// @dev Stores and returns the header for an array of given blocks.
    /// @param blockNumbers Array of block numbers.
    /// @return Array of block headers.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeaders(uint256[] memory blockNumbers) public returns (bytes32[] memory) {
        bytes32[] memory blockHeaders = new bytes32[](blockNumbers.length);
        for (uint256 i = 0; i < blockNumbers.length; i++) {
            blockHeaders[i] = storeBlockHeader(blockNumbers[i]);
        }
        return blockHeaders;
    }
}