//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
pragma abicoder v2;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolActions.sol';
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import './base/TransferHelper.sol';
import './base/Address.sol';

contract Flash is IUniswapV3SwapCallback {
    mapping(address => bool) private adminMap;
    mapping(address => bool) private poolMap;
    mapping(address => mapping(address => uint256)) private approveMap;
    address private owner;

    constructor() {
        owner = msg.sender;

        adminMap[owner] = true;
        adminMap[address(this)] = true;
    }

    modifier onlyOwner() {
        require(msg.sender == owner || msg.sender == address(this), 'Not is owner.');
        _;
    }

    modifier onlyMaster() {
        require(msg.sender == owner || msg.sender == address(this) || adminMap[msg.sender], 'Not is master.');
        _;
    }

    modifier onlyPool() {
        require(msg.sender == owner || msg.sender == address(this) || poolMap[msg.sender], 'Not is pool.');
        _;
    }

    function addAdmin(address _addr) public onlyOwner {
        adminMap[_addr] = true;
    }

    function approve(uint256 _volume, address _token, address _to) public onlyOwner {
        TransferHelper.safeApprove(_token, _to, _volume);
        approveMap[_to][_token] = _volume;
    }

    function withdraw(address _token) public onlyOwner {
        uint256 token_balance = IERC20(_token).balanceOf(address(this));
        TransferHelper.safeTransfer(_token, owner, token_balance);
    }

    struct FlashLoanParams {
        address pool;
        bool inToken0;
        int256 inAmount;
        uint160 sqrtPriceLimitX96;
        address inToken;
    }

    function flashLoan(FlashLoanParams memory params) public onlyOwner returns (int256 amount0, int256 amount1) {
        IUniswapV3PoolActions pool = IUniswapV3PoolActions(params.pool);

        bytes memory callBackData = abi.encodeWithSelector(
            bytes4(keccak256(bytes("transfer(address,address,uint256)"))),
            params.inToken, params.pool, params.inAmount
        );

        poolMap[params.pool] = true;
        return pool.swap(
            address(this),
            params.inToken0,
            params.inAmount,
            params.sqrtPriceLimitX96 == 0
                ? (params.inToken0 ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
                : params.sqrtPriceLimitX96,
            callBackData
        );
    }

    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) override public onlyPool {
        Address.functionCall(address(this), data);
    }

    function transfer(address token, address to, uint256 amount) public onlyOwner {
        TransferHelper.safeTransfer(token, to, amount);
    }
}
