contract StrategyCurveIBVoterProxy is BaseStrategy {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    address public constant gauge =
        address(0xF5194c3325202F456c95c1Cf0cA36f8475C1949F); // Curve Iron Bank Gauge contract, v2 is tokenized, held by Yearn's voter
    ICurveStrategyProxy public proxy =
        ICurveStrategyProxy(
            address(0x9a165622a744C20E3B2CB443AeD98110a33a231b)
        ); // Yearn's Updated v3 StrategyProxy

    uint256 public optimal = 0;

    ICurveFi public constant curve =
        ICurveFi(address(0x2dded6Da1BF5DBdF597C45fcFaa3194e53EcfeAF)); // Curve Iron Bank Pool
    address public voter = address(0xF147b8125d2ef93FB6965Db97D6746952a133934); // Yearn's veCRV voter
    address public crvRouter =
        address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); // default to sushiswap, more CRV liquidity there
    address[] public crvPath;

    ICrvV3 public constant crv = ICrvV3(address(0xD533a949740bb3306d119CC777fa900bA034cd52));
    IERC20 public constant weth = IERC20(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
    IERC20 public constant dai = IERC20(address(0x6B175474E89094C44Da98b954EedeAC495271d0F));
    IERC20 public constant usdc = IERC20(address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48));
    IERC20 public constant usdt = IERC20(address(0xdAC17F958D2ee523a2206206994597C13D831ec7));

    uint256 public keepCRV = 1000;
    uint256 public constant FEE_DENOMINATOR = 10000;

    uint256 public checkLiqGauge = 0; // 1 is for TRUE value and 0 for FALSE to keep in sync with binary convention
    uint256 public constant CHECK_LIQ_GAUGE_TRUE = 1;
    uint256 public constant CHECK_LIQ_GAUGE_FALSE = 0;

    uint256 public constant USE_SUSHI = 1;
    address public constant sushiswapRouter =
        address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
    address public constant uniswapRouter = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

    constructor(address _vault) public BaseStrategy(_vault) {
        // You can set these parameters on deployment to whatever you want
        // maxReportDelay = 6300;
        // profitFactor = 100;
        // debtThreshold = 0;

        // want = crvIB, Curve's Iron Bank pool (ycDai+ycUsdc+ycUsdt)
        want.safeApprove(address(proxy), uint256(-1));

        // add approvals for crv on sushiswap and uniswap due to weird crv approval issues for setCrvRouter
        // add approvals on all tokens
        crv.approve(uniswapRouter, uint256(-1));
        crv.approve(sushiswapRouter, uint256(-1));
        dai.safeApprove(address(curve), uint256(-1));
        usdc.safeApprove(address(curve), uint256(-1));
        usdt.safeApprove(address(curve), uint256(-1));

        crvPath = new address[](3);
        crvPath[0] = address(crv);
        crvPath[1] = address(weth);
        crvPath[2] = address(dai);
    }

    function name() external view override returns (string memory) {
        // Add your own name here, suggestion e.g. "StrategyCreamYFI"
        return "StrategyCurveIBVoterProxy";
    }

    // total assets held by strategy
    function estimatedTotalAssets() public view override returns (uint256) {
        return proxy.balanceOf(gauge).add(want.balanceOf(address(this)));
    }

    function prepareReturn(uint256 _debtOutstanding)
        internal
        override
        returns (
            uint256 _profit,
            uint256 _loss,
            uint256 _debtPayment
        )
    {
        // TODO: Do stuff here to free up any returns back into `want`
        // NOTE: Return `_profit` which is value generated by all positions, priced in `want`
        // NOTE: Should try to free up at least `_debtOutstanding` of underlying position

        // if we have anything in the gauge, then harvest CRV from the gauge
        uint256 gaugeTokens = proxy.balanceOf(gauge);
        if (gaugeTokens > 0) {
            proxy.harvest(gauge);

            uint256 crvBalance = crv.balanceOf(address(this));
            uint256 _keepCRV = crvBalance.mul(keepCRV).div(FEE_DENOMINATOR);
            IERC20(address(crv)).safeTransfer(voter, _keepCRV);
            proxy.lock();
            uint256 crvRemainder = crvBalance.sub(_keepCRV);

            _sell(crvRemainder);

            if (optimal == 0) {
                uint256 daiBalance = dai.balanceOf(address(this));
                curve.add_liquidity([daiBalance, 0, 0], 0, true);
            } else if (optimal == 1) {
                uint256 usdcBalance = usdc.balanceOf(address(this));
                curve.add_liquidity([0, usdcBalance, 0], 0, true);
            } else {
                uint256 usdtBalance = usdt.balanceOf(address(this));
                curve.add_liquidity([0, 0, usdtBalance], 0, true);
            }
        }
        // serious loss should never happen, but if it does (for instance, if Curve is hacked), let's record it accurately
        uint256 assets = estimatedTotalAssets();
        uint256 debt = vault.strategies(address(this)).totalDebt;

        // if assets are greater than debt, things are working great!
        if (assets > debt) {
            _profit = want.balanceOf(address(this));
        }
        // if assets are less than debt, we are in trouble
        else {
            _loss = debt.sub(assets);
            _profit = 0;
        }

        // debtOustanding will only be > 0 in the event of revoking or lowering debtRatio of a strategy
        if (_debtOutstanding > 0) {
            uint256 stakedBal = proxy.balanceOf(gauge);
            proxy.withdraw(
                gauge,
                address(want),
                Math.min(stakedBal, _debtOutstanding)
            );

            _debtPayment = Math.min(
                _debtOutstanding,
                want.balanceOf(address(this))
            );
        }

        return (_profit, _loss, _debtPayment);
    }

    function adjustPosition(uint256 _debtOutstanding) internal override {
        // used in case there is balance of gauge tokens in the strategy
        // this should be withdrawn and added to proxy
        if (checkLiqGauge == CHECK_LIQ_GAUGE_TRUE) {
            uint256 liqGaugeBal = IGauge(gauge).balanceOf(address(this));

            if (liqGaugeBal > 0) {
                IGauge(gauge).withdraw(liqGaugeBal);
            }
        }

        uint256 _toInvest = want.balanceOf(address(this));
        want.safeTransfer(address(proxy), _toInvest);
        proxy.deposit(gauge, address(want));
    }

    function liquidatePosition(uint256 _amountNeeded)
        internal
        override
        returns (uint256 _liquidatedAmount, uint256 _loss)
    {
        uint256 wantBal = want.balanceOf(address(this));
        if (_amountNeeded > wantBal) {
            uint256 stakedBal = proxy.balanceOf(gauge);
            proxy.withdraw(
                gauge,
                address(want),
                Math.min(stakedBal, _amountNeeded - wantBal)
            );
            uint256 withdrawnBal = want.balanceOf(address(this));
            _liquidatedAmount = Math.min(_amountNeeded, withdrawnBal);

            // if _amountNeeded != withdrawnBal, then we have an error
            if (_amountNeeded != withdrawnBal) {
                uint256 assets = estimatedTotalAssets();
                uint256 debt = vault.strategies(address(this)).totalDebt;
                _loss = debt.sub(assets);
            }
        }

        return (_liquidatedAmount, _loss);
    }

    // Sells our harvested CRV into the selected output (DAI, USDC, or USDT).
    function _sell(uint256 _amount) internal {
        IUniswapV2Router02(crvRouter).swapExactTokensForTokens(
            _amount,
            uint256(0),
            crvPath,
            address(this),
            now
        );
    }

    function prepareMigration(address _newStrategy) internal override {
        uint256 gaugeTokens = proxy.balanceOf(gauge);
        if (gaugeTokens > 0) {
            proxy.withdraw(gauge, address(want), gaugeTokens);
        }
    }

    function protectedTokens()
        internal
        view
        override
        returns (address[] memory)
    {}

    // setter functions
    // These functions are useful for setting parameters of the strategy that may need to be adjusted.

    // Use to update Yearn's StrategyProxy contract as needed in case of upgrades.
    function setProxy(address _proxy) external onlyGovernance {
        proxy = ICurveStrategyProxy(_proxy);
    }

    // 1 is for TRUE value and 0 for FALSE to keep in sync with binary convention
    // checkLiqGauge TRUE = 1;
    // checkLiqGauge FALSE = 0;
    function updateCheckLiqGauge(uint256 _checkLiqGauge)
        external
        onlyAuthorized
    {
        require(_checkLiqGauge <= CHECK_LIQ_GAUGE_TRUE, "incorrect value");
        checkLiqGauge = _checkLiqGauge;
    }

    // Set the amount of CRV to be locked in Yearn's veCRV voter from each harvest. Default is 10%.
    function setKeepCRV(uint256 _keepCRV) external onlyGovernance {
        keepCRV = _keepCRV;
    }

    // 1 is for TRUE value and 0 for FALSE to keep in sync with binary convention
    // Use SushiSwap for CRV Router = 1;
    // Use Uniswap for CRV Router = 0 (or anything else);
    function setCrvRouter(uint256 _isSushiswap) external onlyAuthorized {
        if (_isSushiswap == USE_SUSHI) {
            crvRouter = sushiswapRouter;
        } else {
            crvRouter = uniswapRouter;
        }
    }

    // Set Yearn's veCRV voter address, useful in case of contract upgrade
    function setVoter(address _voter) external onlyGovernance {
        voter = _voter;
    }

    // Set optimal token to sell harvested CRV into for depositing back to Iron Bank Curve pool.
    // Default is DAI, but can be set to USDC or USDT as needed by strategist or governance.
    function setOptimal(uint256 _optimal) external onlyAuthorized {
        crvPath = new address[](3);
        crvPath[0] = address(crv);
        crvPath[1] = address(weth);

        if (_optimal == 0) {
            crvPath[2] = address(dai);
            optimal = 0;
        } else if (_optimal == 1) {
            crvPath[2] = address(usdc);
            optimal = 1;
        } else if (_optimal == 2) {
            crvPath[2] = address(usdt);
            optimal = 2;
        } else {
            require(false, "incorrect token");
        }
    }
}
function setProxy(address _proxy) external onlyGovernance {
    proxy = ICurveStrategyProxy(_proxy);
}

// setter functions
// These functions are useful for setting parameters of the strategy that may need to be adjusted.
// Use to update Yearn's StrategyProxy contract as needed in case of upgrades.