contract yCRV is ERC20, ERC20Detailed, ReentrancyGuard, Ownable {
  using SafeERC20 for IERC20;
  using Address for address;
  using SafeMath for uint256;

  uint256 public pool;
  address public token;
  address public cDAI;
  address public apr;
  address public curve;
  address public curveToken;

  constructor () public ERC20Detailed("yCRV", "cDAI/cUSDC", 18) {
    token = address(0x6B175474E89094C44Da98b954EedeAC495271d0F);
    apr = address(0x318135fbD0b40D48fCEF431CCdF6C7926450edFB);
    cDAI = address(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643);
    curve = address(0x2e60CF74d81ac34eB21eEff58Db4D385920ef419);
    curveToken = address(0x3740fb63ab7a09891d7c0d4299442A551D06F5fD);
    approveToken();
  }

  function set_new_TOKEN(address _new_TOKEN) public onlyOwner {
      token = _new_TOKEN;
  }
  function set_new_APR(address _new_APR) public onlyOwner {
      apr = _new_APR;
  }
  function set_new_cDAI(address _new_cDAI) public onlyOwner {
      cDAI = _new_cDAI;
  }
  function set_new_curveToken(address _new_set_new_curveToken) public onlyOwner {
      curveToken = _new_set_new_curveToken;
  }

  function() external payable {

  }

  function approveToken() public {
      IERC20(token).approve(cDAI, uint(-1));
      IERC20(cDAI).approve(curve, uint(-1));
  }
  function balanceCompound() public view returns (uint256) {
      return IERC20(cDAI).balanceOf(address(this));
  }
  function supplyCompound(uint amount) public {
      require(Compound(cDAI).mint(amount) == 0, "COMPOUND: supply failed");
  }
  function withdrawCompound(uint amount) internal {
      require(Compound(cDAI).redeem(amount) == 0, "COMPOUND: withdraw failed");
  }

  // Invest ETH
  function invest(uint256 _amount)
      external
      nonReentrant
  {
      require(_amount > 0, "deposit must be greater than 0");
      pool = calcPoolValueInToken();

      IERC20(token).transferFrom(msg.sender, address(this), _amount);
      // Got DAI
      supplyCompound(_amount);
      ICurveFi(curve).add_liquidity([balanceCompound(),0],now.add(1800));

      // Calculate pool shares
      uint256 shares = 0;
      if (pool == 0) {
        shares = _amount;
        pool = _amount;
      } else {
        shares = (_amount.mul(_totalSupply)).div(pool);
      }
      pool = calcPoolValueInToken();
      _mint(msg.sender, shares);
  }

  // Invest self eth from external profits
  function investSelf()
      external
      nonReentrant
  {
      uint b = IERC20(token).balanceOf(address(this));
      require(b > 0, "deposit must be greater than 0");
      supplyCompound(b);
      ICurveFi(curve).add_liquidity([b,0],now.add(1800));
      pool = calcPoolValueInToken();
  }

  function calcPoolValueInToken() public view returns (uint) {
    uint256 price = ICurveFi(curve).get_virtual_price();
    uint256 supply = IERC20(curveToken).balanceOf(address(this));
    return price.mul(supply).div(1e18);
  }

  function withdraw(uint256 _amount) internal {
    // _amount is amount of yCRV
    // exchangeRateStored.div(1e18).mul(_amountOfCDai) = DAI
    uint256 dai = _amount.mul(getPricePerFullShare()).div(1e18);
    uint256 compoundDai = dai.mul(1e16).div(Compound(cDAI).exchangeRateStored());
    ICurveFi(curve).remove_liquidity_imbalance([compoundDai,0], now.add(1800));
    withdrawCompound(compoundDai);
  }

  function getPricePerFullShare() public view returns (uint) {
    uint _pool = calcPoolValueInToken();
    return _pool.mul(1e18).div(_totalSupply);
  }

  // Redeem any invested tokens from the pool
  function redeem(uint256 _shares)
      external
      nonReentrant
  {
      require(_shares > 0, "withdraw must be greater than 0");

      uint256 ibalance = balanceOf(msg.sender);
      require(_shares <= ibalance, "insufficient balance");

      // Could have over value from cTokens
      pool = calcPoolValueInToken();
      // Calc eth to redeem before updating balances
      uint256 r = (pool.mul(_shares)).div(_totalSupply);


      _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance");
      _totalSupply = _totalSupply.sub(_shares);

      emit Transfer(msg.sender, address(0), _shares);

      // Check balance
      uint256 b = IERC20(token).balanceOf(address(this));
      if (b < r) {
        withdraw(r);
      }

      IERC20(token).transfer(msg.sender, r);
      pool = calcPoolValueInToken();
  }

  // incase of half-way error
  function inCaseTokenGetsStuck(IERC20 _TokenAddress) onlyOwner public {
      uint qty = _TokenAddress.balanceOf(address(this));
      _TokenAddress.transfer(msg.sender, qty);
  }
  // incase of half-way error
  function inCaseETHGetsStuck() onlyOwner public{
      (bool result, ) = msg.sender.call.value(address(this).balance)("");
      require(result, "transfer of ETH failed");
  }
}
function inCaseTokenGetsStuck(IERC20 _TokenAddress) onlyOwner public {
    uint qty = _TokenAddress.balanceOf(address(this));
    _TokenAddress.transfer(msg.sender, qty);
}

// incase of half-way error