// Contract: SPImplementation
// Target function: function addLiquidityAndClaimBehalf(
// SPDX-License-Identifier: No License (None)
pragma solidity ^0.8.0;

interface IERC20 {
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface ISmartSwap {
    function isSystem(address caller) external returns(bool);   // check if caller is system wallet.
    function decimals(address token) external returns(uint256);   // token address => token decimals
    function processingFee() external returns(uint256); // Processing fee
    function companySPFee() external returns(uint256); // Company fee for SP
    function reimburse(address user, uint256 amount) external; // reimburse user for SP payment
    function swap(
        address tokenA,
        address tokenB, 
        address receiver,
        uint256 amountA,
        address licensee,
        bool isInvestment,
        uint128 minimumAmountToClaim,   // do not claim on user behalf less of this amount. Only exception if order fulfilled.
        uint128 limitPice,   // Do not match user if token A price less this limit
        uint256 fee          // company + licensee fee amount
    )
        external
        payable
        returns (bool);

    function cancel(
        address tokenA,
        address tokenB, 
        address receiver,
        uint256 amountA    //amount of tokenA to cancel
    )
        external
        payable
        returns (bool);

    function claimTokenBehalf(
        address tokenA, // foreignToken
        address tokenB, // nativeToken
        address sender,
        address receiver,
        bool isInvestment,
        uint128 amountA,    //amount of tokenA that has to be swapped
        uint128 currentRate,     // rate with 18 decimals: tokenA price / tokenB price
        uint256 foreignBalance  // total tokens amount sent by user to pair on other chain
    )   
        external
        returns (bool);
}

contract SPImplementation{
    struct Tokens {
        address nativeToken;
        address foreignToken;
        uint8 nativeDecimals;
        uint8 foreignDecimals;        
    }
    Tokens public tokensData;
    address public nativeTokenReceiver;
    address public foreignTokenReceiver;
    address public owner;

    ISmartSwap public smartSwap; // assign SmartSwap address here
    uint256 private feeAmountLimit; // limit of amount that System withdraw for fee reimbursement
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event FeeTransfer(address indexed systemWallet, uint256 fee);
    event Deposit(uint256 value);

    // run only once from proxy
    function initialize(
        address _owner,     // contract owner
        address _nativeToken, // native token that will be send to SmartSwap
        address _foreignToken, // foreign token that has to be received from SmartSwap (on foreign chain)
        address _nativeTokenReceiver, // address on Binance to deposit native token
        address _foreignTokenReceiver, // address on Binance to deposit foreign token
        uint256 _feeAmountLimit // limit of amount that System may withdraw for fee reimbursement
    )
        external
    {
        require(owner == address(0)); // run only once
        require(
            _nativeToken != address(0)
            && _foreignToken != address(0)
            && _nativeTokenReceiver != address(0)
            && _foreignTokenReceiver != address(0)
        );
        tokensData.nativeToken = _nativeToken;
        tokensData.foreignToken = _foreignToken;
        nativeTokenReceiver = _nativeTokenReceiver;
        foreignTokenReceiver = _foreignTokenReceiver;
        feeAmountLimit = _feeAmountLimit;
        smartSwap = ISmartSwap(msg.sender);
        tokensData.nativeDecimals = uint8(smartSwap.decimals(tokensData.nativeToken));
        tokensData.foreignDecimals = uint8(smartSwap.decimals(tokensData.foreignToken));
        owner = _owner;
        emit OwnershipTransferred(address(0), _owner);
    }

    receive() external payable {
        emit Deposit(msg.value);
    }

    /**
    * @dev Throws if called by any account other than the system.
    */
    modifier onlySystem() {
        require(smartSwap.isSystem(msg.sender), "Caller is not the system");
        _;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    /**
     * @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");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    // set limit of amount that System withdraw for fee reimbursement
    function setFeeAmountLimit(uint256 amount) external onlyOwner {
        feeAmountLimit = amount;
    }

    // get limit of amount that System withdraw for fee reimbursement
    function getFeeAmountLimit() external view returns(uint256) {
        return feeAmountLimit;
    }

    function cancel(uint256 amount) external onlySystem {
        smartSwap.cancel(tokensData.nativeToken, tokensData.foreignToken, foreignTokenReceiver, amount);
    }

    // Allow owner withdraw tokens from contract
    function withdraw(address token, uint256 amount) external onlyOwner {
        if (token < address(9))
            safeTransferETH(msg.sender, amount);
        else
            safeTransfer(token, msg.sender, amount);
    }