contract Vault  {
  using SafeERC20 for IERC20;
  using Address for address;
  using SafeMath for uint256;

  IERC20 public token;
  IERC20 public Yfiitoken;
  
  uint public min = 9500;
  uint public constant max = 10000;

  uint public earnLowerlimit; //池内空余资金到这个值就自动earn
  
  address public governance;
  address public controller;

  struct Player {
        uint256 stake; // 总质押总数
        uint256 payout; //
        uint256 total_out; // 已经领取的分红
  }
  mapping(address => Player) public plyr_; // (player => data) player data

  struct Global {
        uint256 total_stake; // 总质押总数
        uint256 total_out; //  总分红金额
        uint256 earnings_per_share; // 每股分红
  }
  mapping(uint256 => Global) public global_; // (global => data) global data
  mapping (address => uint256) public deposittime;
  uint256 constant internal magnitude = 10**40;

  address constant public yfii = address(0xa1d0E215a23d7030842FC67cE582a6aFa3CCaB83);

  string public getName;

  constructor (address _token,uint256 _earnLowerlimit) public {
      token = IERC20(_token);
      getName = string(abi.encodePacked("yfii:Vault:", ERC20Detailed(_token).name()));

      earnLowerlimit = _earnLowerlimit*1e18;
      Yfiitoken = IERC20(yfii);
      governance = tx.origin;
      controller = 0xe14e60d0F7fb15b1A98FDE88A3415C17b023bf36;
  }
  
  function balance() public view returns (uint) {
      return token.balanceOf(address(this))
             .add(Controller(controller).balanceOf(address(token)));
  }
  
  function setMin(uint _min) external {
      require(msg.sender == governance, "!governance");
      min = _min;
  }
  
  function setGovernance(address _governance) public {
      require(msg.sender == governance, "!governance");
      governance = _governance;
  }
  
  function setController(address _controller) public {
      require(msg.sender == governance, "!governance");
      controller = _controller;
  }
  function setEarnLowerlimit(uint256 _earnLowerlimit) public{
      require(msg.sender == governance, "!governance");
      earnLowerlimit = _earnLowerlimit;
  }
  // Custom logic in here for how much the vault allows to be borrowed
  // Sets minimum required on-hand to keep small withdrawals cheap
  function available() public view returns (uint) {
      return token.balanceOf(address(this)).mul(min).div(max);
  }
  
  function earn() public {
      uint _bal = available();
      token.safeTransfer(controller, _bal);
      Controller(controller).earn(address(token), _bal);
  }

  function deposit(uint amount) external {
      token.safeTransferFrom(msg.sender, address(this), amount);
      plyr_[msg.sender].stake = plyr_[msg.sender].stake.add(amount);
        if (global_[0].earnings_per_share != 0) {
            plyr_[msg.sender].payout = plyr_[msg.sender].payout.add(
                global_[0].earnings_per_share.mul(amount).sub(1).div(magnitude).add(1)
            );
        }
        global_[0].total_stake = global_[0].total_stake.add(amount);

      if (token.balanceOf(address(this))>earnLowerlimit){
          earn();
      }
      deposittime[msg.sender] = now;

      
  }

  // No rebalance implementation for lower fees and faster swaps
  function withdraw(uint amount) external {
      claim();
      require(amount<=plyr_[msg.sender].stake,"!balance");
      uint r = amount;

      // Check balance
      uint b = token.balanceOf(address(this));
      if (b < r) { 
          uint _withdraw = r.sub(b);
          Controller(controller).withdraw(address(token), _withdraw);
          uint _after = token.balanceOf(address(this));
          uint _diff = _after.sub(b);
          if (_diff < _withdraw) {
              r = b.add(_diff);
          }
      }

      plyr_[msg.sender].payout = plyr_[msg.sender].payout.sub(
            global_[0].earnings_per_share.mul(amount).div(magnitude)
      );
      plyr_[msg.sender].stake = plyr_[msg.sender].stake.sub(amount);
      global_[0].total_stake = global_[0].total_stake.sub(amount);

      token.safeTransfer(msg.sender, r);
  }

    function make_profit(uint256 amount) public { 
        require(amount>0,"not 0");
        Yfiitoken.safeTransferFrom(msg.sender, address(this), amount);
        global_[0].earnings_per_share = global_[0].earnings_per_share.add(
            amount.mul(magnitude).div(global_[0].total_stake)
        );
        global_[0].total_out = global_[0].total_out.add(amount);
    }
    function cal_out(address user) public view returns (uint256) { 
        uint256 _cal = global_[0].earnings_per_share.mul(plyr_[user].stake).div(magnitude);
        if (_cal < plyr_[user].payout) {
            return 0;
        } else {
            return _cal.sub(plyr_[user].payout);
        }
    }    
    function cal_out_pending(uint256 _pendingBalance,address user) public view returns (uint256) { 
        uint256 _earnings_per_share = global_[0].earnings_per_share.add(
            _pendingBalance.mul(magnitude).div(global_[0].total_stake)
        );
        uint256 _cal = _earnings_per_share.mul(plyr_[user].stake).div(magnitude);
        _cal = _cal.sub(cal_out(user));
        if (_cal < plyr_[user].payout) {
            return 0;
        } else {
            return _cal.sub(plyr_[user].payout);
        }
    }
    function claim() public { 
        uint256 out = cal_out(msg.sender);
        plyr_[msg.sender].payout = global_[0].earnings_per_share.mul(plyr_[msg.sender].stake).div(magnitude);
        plyr_[msg.sender].total_out = plyr_[msg.sender].total_out.add(out);

        if (out > 0) {
            uint256 _depositTime = now - deposittime[msg.sender];
            if (_depositTime < 1 days){ //deposit in 24h
                uint256 actually_out = _depositTime.mul(out).mul(1e18).div(1 days).div(1e18);
                uint256 to_team = out.sub(actually_out);
                Yfiitoken.safeTransfer(Controller(controller).rewards(), to_team);
                out = actually_out;
            }
            Yfiitoken.safeTransfer(msg.sender, out);
        }
    }

}
function available() public view returns (uint) {
      return token.balanceOf(address(this)).mul(min).div(max);
  }
// Custom logic in here for how much the vault allows to be borrowed
// Sets minimum required on-hand to keep small withdrawals cheap