// SPDX-License-Identifier: -
// License: https://license.clober.io/LICENSE.pdf

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Create2.sol";

import "./interfaces/CloberOrderBook.sol";
import "./interfaces/CloberMarketSwapCallbackReceiver.sol";
import "./interfaces/CloberRouter.sol";
import "./interfaces/CloberMarketFactory.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/CloberOrderNFT.sol";
import "./Errors.sol";

contract MarketRouter is CloberMarketSwapCallbackReceiver, CloberRouter {
    using SafeERC20 for IERC20;

    bool private constant _BID = true;
    bool private constant _ASK = false;

    CloberMarketFactory private immutable _factory;

    modifier checkDeadline(uint64 deadline) {
        if (block.timestamp > deadline) {
            revert Errors.CloberError(Errors.DEADLINE);
        }
        _;
    }

    constructor(address factory) {
        _factory = CloberMarketFactory(factory);
    }

    function cloberMarketSwapCallback(
        address inputToken,
        address,
        uint256 inputAmount,
        uint256,
        bytes calldata data
    ) external payable {
        // check if caller is registered market
        if (_factory.getMarketHost(msg.sender) == address(0)) {
            revert Errors.CloberError(Errors.ACCESS);
        }

        (address payer, bool useNative) = abi.decode(data, (address, bool));

        // transfer input tokens
        if (useNative) {
            uint256 nativeAmount = address(this).balance - msg.value;
            (inputAmount, nativeAmount) = nativeAmount > inputAmount
                ? (0, inputAmount)
                : (inputAmount - nativeAmount, nativeAmount);

            IWETH(inputToken).deposit{value: nativeAmount}();
            IWETH(inputToken).transfer(msg.sender, nativeAmount);
        }
        if (inputAmount > 0) {
            IERC20(inputToken).safeTransferFrom(payer, msg.sender, inputAmount);
        }

        if (address(this).balance > 0) {
            (bool success, ) = payer.call{value: address(this).balance}("");
            if (!success) {
                revert Errors.CloberError(Errors.FAILED_TO_SEND_VALUE);
            }
        }
    }

    function limitBid(LimitOrderParams calldata params)
        external
        payable
        checkDeadline(params.deadline)
        returns (uint256)
    {
        return _limitOrder(params, _BID);
    }

    function limitAsk(LimitOrderParams calldata params)
        external
        payable
        checkDeadline(params.deadline)
        returns (uint256)
    {
        return _limitOrder(params, _ASK);
    }

    function _limitOrder(LimitOrderParams calldata params, bool isBid) internal returns (uint256) {
        return
            CloberOrderBook(params.market).limitOrder{value: uint256(params.claimBounty) * 1 gwei}(
                params.user,
                params.priceIndex,
                params.rawAmount,
                params.baseAmount,
                (isBid ? 1 : 0) + (params.postOnly ? 2 : 0),
                abi.encode(msg.sender, params.useNative)
            );
    }

    function marketBid(MarketOrderParams calldata params) external payable checkDeadline(params.deadline) {
        _marketOrder(params, _BID);
    }

    function marketAsk(MarketOrderParams calldata params) external payable checkDeadline(params.deadline) {
        _marketOrder(params, _ASK);
    }

    function _marketOrder(MarketOrderParams calldata params, bool isBid) internal {
        CloberOrderBook(params.market).marketOrder(
            params.user,
            params.limitPriceIndex,
            params.rawAmount,
            params.baseAmount,
            (isBid ? 1 : 0) + (params.expendInput ? 2 : 0),
            abi.encode(msg.sender, params.useNative)
        );
    }

    function claim(uint64 deadline, ClaimOrderParams[] calldata paramsList) external checkDeadline(deadline) {
        _claim(paramsList);
    }

    function _claim(ClaimOrderParams[] calldata paramsList) internal {
        for (uint256 i = 0; i < paramsList.length; ++i) {
            ClaimOrderParams calldata params = paramsList[i];
            CloberOrderBook(params.market).claim(msg.sender, params.orderKeys);
        }
    }

    function limitBidAfterClaim(ClaimOrderParams[] calldata claimParamsList, LimitOrderParams calldata limitOrderParams)
        external
        payable
        checkDeadline(limitOrderParams.deadline)
        returns (uint256)
    {
        _claim(claimParamsList);
        return _limitOrder(limitOrderParams, _BID);
    }

    function limitAskAfterClaim(ClaimOrderParams[] calldata claimParamsList, LimitOrderParams calldata limitOrderParams)
        external
        payable
        checkDeadline(limitOrderParams.deadline)
        returns (uint256)
    {
        _claim(claimParamsList);
        return _limitOrder(limitOrderParams, _ASK);
    }

    function marketBidAfterClaim(
        ClaimOrderParams[] calldata claimParamsList,
        MarketOrderParams calldata marketOrderParams
    ) external payable checkDeadline(marketOrderParams.deadline) {
        _claim(claimParamsList);
        _marketOrder(marketOrderParams, _BID);
    }

    function marketAskAfterClaim(
        ClaimOrderParams[] calldata claimParamsList,
        MarketOrderParams calldata marketOrderParams
    ) external payable checkDeadline(marketOrderParams.deadline) {
        _claim(claimParamsList);
        _marketOrder(marketOrderParams, _ASK);
    }
}
[{"inputs":[{"internalType":"address","name":"factory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"errorCode","type":"uint256"}],"name":"CloberError","type":"error"},{"inputs":[{"internalType":"uint64","name":"deadline","type":"uint64"},{"components":[{"internalType":"address","name":"market","type":"address"},{"components":[{"internalType":"bool","name":"isBid","type":"bool"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint256","name":"orderIndex","type":"uint256"}],"internalType":"struct OrderKey[]","name":"orderKeys","type":"tuple[]"}],"internalType":"struct CloberRouter.ClaimOrderParams[]","name":"paramsList","type":"tuple[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"cloberMarketSwapCallback","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"uint32","name":"claimBounty","type":"uint32"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"postOnly","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.LimitOrderParams","name":"params","type":"tuple"}],"name":"limitAsk","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"components":[{"internalType":"bool","name":"isBid","type":"bool"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint256","name":"orderIndex","type":"uint256"}],"internalType":"struct OrderKey[]","name":"orderKeys","type":"tuple[]"}],"internalType":"struct CloberRouter.ClaimOrderParams[]","name":"claimParamsList","type":"tuple[]"},{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"uint32","name":"claimBounty","type":"uint32"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"postOnly","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.LimitOrderParams","name":"limitOrderParams","type":"tuple"}],"name":"limitAskAfterClaim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"uint32","name":"claimBounty","type":"uint32"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"postOnly","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.LimitOrderParams","name":"params","type":"tuple"}],"name":"limitBid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"components":[{"internalType":"bool","name":"isBid","type":"bool"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint256","name":"orderIndex","type":"uint256"}],"internalType":"struct OrderKey[]","name":"orderKeys","type":"tuple[]"}],"internalType":"struct CloberRouter.ClaimOrderParams[]","name":"claimParamsList","type":"tuple[]"},{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"uint32","name":"claimBounty","type":"uint32"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"postOnly","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.LimitOrderParams","name":"limitOrderParams","type":"tuple"}],"name":"limitBidAfterClaim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"limitPriceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"expendInput","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.MarketOrderParams","name":"params","type":"tuple"}],"name":"marketAsk","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"components":[{"internalType":"bool","name":"isBid","type":"bool"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint256","name":"orderIndex","type":"uint256"}],"internalType":"struct OrderKey[]","name":"orderKeys","type":"tuple[]"}],"internalType":"struct CloberRouter.ClaimOrderParams[]","name":"claimParamsList","type":"tuple[]"},{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"limitPriceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"expendInput","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.MarketOrderParams","name":"marketOrderParams","type":"tuple"}],"name":"marketAskAfterClaim","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"limitPriceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"expendInput","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.MarketOrderParams","name":"params","type":"tuple"}],"name":"marketBid","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"market","type":"address"},{"components":[{"internalType":"bool","name":"isBid","type":"bool"},{"internalType":"uint16","name":"priceIndex","type":"uint16"},{"internalType":"uint256","name":"orderIndex","type":"uint256"}],"internalType":"struct OrderKey[]","name":"orderKeys","type":"tuple[]"}],"internalType":"struct CloberRouter.ClaimOrderParams[]","name":"claimParamsList","type":"tuple[]"},{"components":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint64","name":"deadline","type":"uint64"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint16","name":"limitPriceIndex","type":"uint16"},{"internalType":"uint64","name":"rawAmount","type":"uint64"},{"internalType":"bool","name":"expendInput","type":"bool"},{"internalType":"bool","name":"useNative","type":"bool"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"internalType":"struct CloberRouter.MarketOrderParams","name":"marketOrderParams","type":"tuple"}],"name":"marketBidAfterClaim","outputs":[],"stateMutability":"payable","type":"function"}]