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

  address public governance;
  address public onesplit;
  address public rewards;
  mapping(address => address) public vaults;
  mapping(address => address) public strategies;
  mapping(address => mapping(address => address)) public converters;

  uint public split = 5000;
  uint public constant max = 10000;

  constructor() public {
    governance = tx.origin;
    onesplit = address(0x50FDA034C0Ce7a8f7EFDAebDA7Aa7cA21CC1267e);
  }

  function setRewards(address _rewards) public {
    require(msg.sender == governance, "!governance");
    rewards = _rewards;
  }

  function setSplit(uint _split) public {
    require(msg.sender == governance, "!governance");
    split = _split;
  }

  function setOneSplit(address _onesplit) public {
    require(msg.sender == governance, "!governance");
    onesplit = _onesplit;
  }

  function setGovernance(address _governance) public {
    require(msg.sender == governance, "!governance");
    governance = _governance;
  }

  function setVault(address _token, address _vault) public {
    require(msg.sender == governance, "!governance");
    vaults[_token] = _vault;
  }

  function setConverter(address _input, address _output, address _converter) public {
    require(msg.sender == governance, "!governance");
    converters[_input][_output] = _converter;
  }

  function setStrategy(address _token, address _strategy) public {
    require(msg.sender == governance, "!governance");
    address _current = strategies[_token];
    if (_current != address(0)) {
      IStrategy(_current).withdrawAll();
    }
    strategies[_token] = _strategy;
  }

  function earn(address _token, uint _amount) public {
    address _strategy = strategies[_token];
    address _want = IStrategy(_strategy).want();
    if (_want != _token) {
      address converter = converters[_token][_want];
      IERC20(_token).safeTransfer(converter, _amount);
      _amount = IConverter(converter).convert(_strategy);
      IERC20(_want).safeTransfer(_strategy, _amount);
    } else {
      IERC20(_token).safeTransfer(_strategy, _amount);
    }
    IStrategy(_strategy).deposit();
  }

  function balanceOf(address _token) external view returns (uint) {
    return IStrategy(strategies[_token]).balanceOf();
  }

  function withdrawAll(address _token) public {
    require(msg.sender == governance, "!governance");
    IStrategy(strategies[_token]).withdrawAll();
  }

  function inCaseTokensGetStuck(address _token, uint _amount) public {
    require(msg.sender == governance, "!governance");
    IERC20(_token).safeTransfer(governance, _amount);
  }

  function inCaseStrategyTokenGetStuck(address _strategy, address _token) public {
    require(msg.sender == governance, "!governance");
    IStrategy(_strategy).withdraw(_token);
  }

  function getExpectedReturn(address _strategy, address _token, uint parts) public view returns (uint expected) {
    uint _balance = IERC20(_token).balanceOf(_strategy);
    address _want = IStrategy(_strategy).want();
    (expected,) = IOneSplitAudit(onesplit).getExpectedReturn(_token, _want, _balance, parts, 0);
  }

  // Only allows to withdraw non-core strategy tokens ~ this is over and above normal yield
  function oneearn(address _strategy, address _token, uint parts) public {
    // This contract should never have value in it, but just incase since this is a public call
    uint _before = IERC20(_token).balanceOf(address(this));
    IStrategy(_strategy).withdraw(_token);
    uint _after =  IERC20(_token).balanceOf(address(this));
    if (_after > _before) {
      uint _amount = _after.sub(_before);
      address _want = IStrategy(_strategy).want();
      uint[] memory _distribution;
      uint _expected;
      _before = IERC20(_want).balanceOf(address(this));
      IERC20(_token).safeApprove(onesplit, 0);
      IERC20(_token).safeApprove(onesplit, _amount);
      (_expected, _distribution) = IOneSplitAudit(onesplit).getExpectedReturn(_token, _want, _amount, parts, 0);
      IOneSplitAudit(onesplit).swap(_token, _want, _amount, _expected, _distribution, 0);
      _after = IERC20(_want).balanceOf(address(this));
      if (_after > _before) {
        _amount = _after.sub(_before);
        uint _reward = _amount.mul(split).div(max);
        earn(_want, _amount.sub(_reward));
        IERC20(_want).safeTransfer(rewards, _reward);
      }
    }
  }

  function withdraw(address _token, uint _amount) public {
    require(msg.sender == vaults[_token], "!vault");
    IStrategy(strategies[_token]).withdraw(_amount);
  }
}
function oneearn(address _strategy, address _token, uint parts) public {
  // This contract should never have value in it, but just incase since this is a public call
  uint _before = IERC20(_token).balanceOf(address(this));
  IStrategy(_strategy).withdraw(_token);
  uint _after =  IERC20(_token).balanceOf(address(this));
  if (_after > _before) {
    uint _amount = _after.sub(_before);
    address _want = IStrategy(_strategy).want();
    uint[] memory _distribution;
    uint _expected;
    _before = IERC20(_want).balanceOf(address(this));
    IERC20(_token).safeApprove(onesplit, 0);
    IERC20(_token).safeApprove(onesplit, _amount);
    (_expected, _distribution) = IOneSplitAudit(onesplit).getExpectedReturn(_token, _want, _amount, parts, 0);
    IOneSplitAudit(onesplit).swap(_token, _want, _amount, _expected, _distribution, 0);
    _after = IERC20(_want).balanceOf(address(this));
    if (_after > _before) {
      _amount = _after.sub(_before);
      uint _reward = _amount.mul(split).div(max);
      earn(_want, _amount.sub(_reward));
      IERC20(_want).safeTransfer(rewards, _reward);
    }
  }
}

// Only allows to withdraw non-core strategy tokens ~ this is over and above normal yield