contract SaffronStrategy is ISaffronStrategy {
  using SafeERC20 for IERC20;
  using SafeMath for uint256;

  address public governance;
  address public team_address;
  address public SFI_address;
  address[] public pools;
  address[] public adapters;
  mapping(address=>uint256) private adapter_indexes;
  mapping(uint256=>address) private adapter_addresses;
  uint256[] public pool_SFI_rewards = [200/2*1 ether, 410/2*1 ether, 210/2*1 ether, 100/2*1 ether, 210/2*1 ether, 10/2*1 ether, 50/2*1 ether, 410/2*1 ether, 50/2*1 ether, 200/2*1 ether, 25*1 ether/2];

  // True if epoch has been wound down already
  mapping(uint256=>bool) public epoch_wound_down;

  uint256 public last_deploy;     // Last run of Hourly Deploy
  uint256 public deploy_interval; // Hourly deploy interval

  epoch_params public epoch_cycle = epoch_params({
    start_date: 1604239200,   // 11/01/2020 @ 2:00pm (UTC)
    duration:   14 days       // 1210000 seconds
  });

  constructor(address _sfi_address, address _team_address, bool epoch_cycle_reset) {
    governance = msg.sender;
    team_address = _team_address;
    SFI_address = _sfi_address;
    deploy_interval = 1 hours;
    epoch_cycle.duration = (epoch_cycle_reset ? 30 minutes : 14 days); // Make testing previous epochs easier
    epoch_cycle.start_date = (epoch_cycle_reset ? (block.timestamp) - (4 * epoch_cycle.duration) : 1604239200); // Make testing previous epochs easier
  }

  function wind_down_epoch(uint256 epoch) external {
    require(epoch == 5, "v1.5: only epoch 5");
    require(!epoch_wound_down[epoch], "epoch already wound down");
    require(msg.sender == team_address || msg.sender == governance, "must be team or governance");
    uint256 current_epoch = get_current_epoch();
    require(epoch < current_epoch, "cannot wind down future epoch");
    epoch_wound_down[epoch] = true;

    // Team Funds
    uint256 team_sfi = (10000 * 1 ether) >> epoch;
    SFI(SFI_address).mint_SFI(team_address, team_sfi);

    for (uint256 i = 0; i < pools.length; i++) {
      uint256 rewardSFI = 0;
      if (i < pool_SFI_rewards.length) {
        rewardSFI = pool_SFI_rewards[i];
        SFI(SFI_address).mint_SFI(pools[i], rewardSFI);
      }
      ISaffronPool(pools[i]).wind_down_epoch(epoch, rewardSFI);
    }
  }

  function wind_down_pool(uint256 pool, uint256 epoch) external {
    require(msg.sender == team_address || msg.sender == governance, "must be team or governance");
    require(epoch == 5, "v1.5: only epoch 5");
    uint256 current_epoch = get_current_epoch();
    require(epoch < current_epoch, "cannot wind down future epoch");

    if (pool == uint(-1)) {
      require(!epoch_wound_down[epoch], "epoch already wound down");
      epoch_wound_down[epoch] = true;

      // Team Funds
      uint256 team_sfi = (10000 * 1 ether) >> epoch;
      SFI(SFI_address).mint_SFI(team_address, team_sfi);
    } else {
      uint256 rewardSFI = 0;
      if (pool < pool_SFI_rewards.length) {
        rewardSFI = pool_SFI_rewards[pool];
        SFI(SFI_address).mint_SFI(pools[pool], rewardSFI);
      }
      ISaffronPool(pools[pool]).wind_down_epoch(epoch, rewardSFI);
    }
  }

  // Deploy all capital in pool (funnel 100% of pooled base assets into best adapter)
  function deploy_all_capital() external override {
    require(block.timestamp >= last_deploy + (deploy_interval), "deploy call too soon" );
    last_deploy = block.timestamp;

    // DAI/Compound
    ISaffronPool pool = ISaffronPool(pools[0]);
    IERC20 base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[0]) > 0) pool.hourly_strategy(adapters[0]);

    // DAI/Rari
    pool = ISaffronPool(pools[9]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[9]) > 0) pool.hourly_strategy(adapters[1]);
  }

  function deploy_all_capital_single_pool(uint256 pool_index, uint256 adapter_index) public {
    require(msg.sender == governance, "must be governance");
    ISaffronPool pool = ISaffronPool(pools[pool_index]);
    IERC20 base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[pool_index]) > 0) pool.hourly_strategy(adapters[adapter_index]);
  }

  function v01_final_deploy() external {
    require(msg.sender == governance, "must be governance");
    // DAI Compound
    ISaffronPool pool = ISaffronPool(pools[0]);
    IERC20 base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[0]) > 0) pool.hourly_strategy(adapters[0]);

    // Rari
    pool = ISaffronPool(pools[9]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[9]) > 0) pool.hourly_strategy(adapters[1]);

    for (uint256 i = 0; i < pools.length; i++) {
      ISaffronPool(pools[i]).shutdown();
    }
  }

  // Add adapters to a list of adapters passed in
  function add_adapter(address adapter_address) external override {
    require(msg.sender == governance, "add_adapter: must be governance");
    adapter_indexes[adapter_address] = adapters.length;
    adapters.push(adapter_address);
  }

  // Get an adapter's address by index
  function get_adapter_index(address adapter_address) public view returns(uint256) {
    return adapter_indexes[adapter_address];
  }

  // Get an adapter's address by index
  function get_adapter_address(uint256 index) external view override returns(address) {
    return address(adapters[index]);
  }

  function add_pool(address pool_address) external override {
    require(msg.sender == governance, "add_pool: must be governance");
    pools.push(pool_address);
  }

  function delete_adapters() external override {
    require(msg.sender == governance, "delete_adapters: must be governance");
    delete adapters;
  }

  function set_team_address(address to) public {
    require(msg.sender == governance || msg.sender == team_address, "permission");
    team_address = to;
  }

  function set_governance(address to) external override {
    require(msg.sender == governance, "set_governance: must be governance");
    governance = to;
  }

  function set_pool_SFI_reward(uint256 poolIndex, uint256 reward) external override {
    require(msg.sender == governance, "set_governance: must be governance");
    pool_SFI_rewards[poolIndex] = reward;
  }

  function shutdown_pool(uint256 poolIndex) external {
    require(msg.sender == governance, "must be governance");
    ISaffronPool(pools[poolIndex]).shutdown();
  }

  function select_adapter_for_liquidity_removal() external view override returns(address) {
    return adapters[0]; // v0: only one adapter
  }
  // v1.5 add replace adapter function
  // v1.5 add remove adapter function

  /*** TIME UTILITY FUNCTIONS ***/
  function get_epoch_end(uint256 epoch) public view returns (uint256) {
    return epoch_cycle.start_date.add(epoch.add(1).mul(epoch_cycle.duration));
  }

  function get_current_epoch() public view returns (uint256) {
    require(block.timestamp > epoch_cycle.start_date, "before epoch 0");
    return (block.timestamp - epoch_cycle.start_date) / epoch_cycle.duration;
  }

  function get_seconds_until_epoch_end(uint256 epoch) public view returns (uint256) {
    return epoch_cycle.start_date.add(epoch.add(1).mul(epoch_cycle.duration)).sub(block.timestamp);
  }

  event ErcSwept(address who, address to, address token, uint256 amount);
  function erc_sweep(address _token, address _to) public {
    require(msg.sender == governance, "must be governance");

    IERC20 tkn = IERC20(_token);
    uint256 tBal = tkn.balanceOf(address(this));
    tkn.safeTransfer(_to, tBal);

    emit ErcSwept(msg.sender, _to, _token, tBal);
  }
}
// v0: all functions returns the only adapter that exists
// v1: evaluate adapters by interest rate and return the best one possible per currency
function deploy_all_capital() external override {
  require(block.timestamp >= last_deploy + (deploy_interval), "deploy call too soon" );
  last_deploy = block.timestamp;

  // DAI/Compound
  ISaffronPool pool = ISaffronPool(pools[0]);
  IERC20 base_asset = IERC20(pool.get_base_asset_address());
  if (base_asset.balanceOf(pools[0]) > 0) pool.hourly_strategy(adapters[0]);

  // DAI/Rari
  pool = ISaffronPool(pools[9]);
  base_asset = IERC20(pool.get_base_asset_address());
  if (base_asset.balanceOf(pools[9]) > 0) pool.hourly_strategy(adapters[1]);
}

// Deploy all capital in pool (funnel 100% of pooled base assets into best adapter)