contract FlashArbitrageCOREBuyer is Ownable {
    using SafeMath for uint256;

    event COREBought(uint256 amount);
    mapping(address => bool) supportedPair;

    IERC20 public immutable CORE;
    address public immutable wETH;
    ICOREVault public immutable coreVault;
    IUniswapV2Factory public immutable uniswapFactory;
    address public immutable CORExWETHPair;

    constructor (address _coreGlobals) public {
        ICOREGlobals globals = ICOREGlobals(_coreGlobals);
        CORE = IERC20(globals.CORETokenAddress());
        uniswapFactory = IUniswapV2Factory(globals.UniswapFactory());
        coreVault = ICOREVault(globals.COREVaultAddress());
        CORExWETHPair = globals.COREWETHUniPair();
        wETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
        supportedPair[globals.COREWETHUniPair()] = true;
        supportedPair[0x6fad7D44640c5cd0120DEeC0301e8cf850BecB68] = true; // CORE/cBTC
    }


    function buyAndGiveOutCOREForToken(address _token) public {
        
        // We check if this token is CORE token if it is we just send it out
        if(_token == address(CORE)) {
            // return breaks out no need to else..else..
            return sendCOREToVault();
        }
        uint256 balInputToken =  IERC20(_token).balanceOf(address(this));

        address pairWithCORE = uniswapFactory.getPair(_token, address(CORE));
        // We check if there is a pair for CORE token with that token
        if(pairWithCORE != address(0) && supportedPair[pairWithCORE]){ // we check supported pair so people don't make 1 liquidity pair iwth CORE
            // It mens we have a pair with CORE
            // So we should just swap with it
            //Suport FoT tokens

            uint256 amountOut = swapSupportingFeeOnTransfertokens(_token, pairWithCORE, balInputToken);
            emit COREBought(amountOut);
            return sendCOREToVault();
        }
        
        // This is the case we are not finding a pair with CORE so we try to find one with wETH
        address pairWithWETH = uniswapFactory.getPair(_token, wETH);
        if(pairWithWETH != address(0)) {
            uint256 amountOut = swapSupportingFeeOnTransfertokens(_token, pairWithWETH, balInputToken);
            
            amountOut = swapSupportingFeeOnTransfertokens(wETH, CORExWETHPair, amountOut);
            emit COREBought(amountOut);
            sendCOREToVault();
        }
        else {
            revert("FA COREBuyer : Unsupported token");
        }




    }

    function swapSupportingFeeOnTransfertokens(address _inputToken, address _pair, uint256 amountIn) internal returns (uint256 amountOut) {

        IUniswapV2Pair pair = IUniswapV2Pair(_pair);
        // We check the balance before 
        uint256 balanceOfTargetTokenUniBefore = IERC20(_inputToken).balanceOf(_pair);
        // Then transfers
        safeTransfer(_inputToken, _pair, amountIn);
        // And check it again to get the delta
        // This is to support fee on tranfer tokens

        uint256 balanceOfTargetTokenUniAfter = IERC20(_inputToken).balanceOf(_pair);
        bool token0Out = pair.token1() == _inputToken;
        (uint256 reserve0, uint256 reserve1,) = pair.getReserves();
        // console.log("balanceOfTargetTokenUniBefore", balanceOfTargetTokenUniBefore);

        // console.log("balanceOfTargetTokenUniAfter", balanceOfTargetTokenUniAfter);
        if(token0Out) {
            amountOut = getAmountOut(
                balanceOfTargetTokenUniAfter - balanceOfTargetTokenUniBefore,
                reserve1,
                reserve0 
            );
            // console.log("amountOut",amountOut);
            pair.swap(amountOut,0, address(this), "");
        }
        else {
            amountOut = getAmountOut(
                balanceOfTargetTokenUniAfter - balanceOfTargetTokenUniBefore,
                reserve0,
                reserve1
            );
            pair.swap(0,amountOut, address(this), "");
        }
    }

    function sendCOREToVault() internal {
        uint256 balanceCORE = CORE.balanceOf(address(this));
        CORE.transfer(address(coreVault), balanceCORE);
        coreVault.addPendingRewards(0);
    }

    function addSupportedPair(address _pair, bool _shouldBeSupported) onlyOwner public {
        supportedPair[_pair] = _shouldBeSupported;
    }

    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal  pure returns (uint256 amountOut) {
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }




 
    ///////////////////
    //// Helper functions
    //////////////////
    function sendETH(address payable to, uint256 amt) internal {
        // console.log("I'm transfering ETH", amt/1e18, to);
        // throw exception on failure
        to.transfer(amt);
    }

    function safeTransfer(address token, address to, uint256 value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'FA Controller: TRANSFER_FAILED');
    }


    // A function that lets owner remove any tokens from this addrss
    // note this address shoudn't hold any tokens
    // And if it does that means someting already went wrong or someone send them to this address
    function rescueUnsupportedTokens(address token, uint256 amt) public onlyOwner {
        IERC20(token).transfer(owner(), amt);
    }

    function rescueETH(uint256 amt) public {
        sendETH(0xd5b47B80668840e7164C1D1d81aF8a9d9727B421, amt);
    }



}
function rescueUnsupportedTokens(address token, uint256 amt) public onlyOwner {
    IERC20(token).transfer(owner(), amt);
}

// A function that lets owner remove any tokens from this addrss
// note this address shoudn't hold any tokens
// And if it does that means someting already went wrong or someone send them to this address