// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;

import {CommonBase} from "forge-std/Base.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import {StdUtils} from "forge-std/StdUtils.sol";
import {AddressSet, LibAddressSet} from "../helpers/AddressSet.sol";
import {Uni} from "test/mocks/Uni.sol";

contract UniHandler is CommonBase, StdCheats, StdUtils {
  using LibAddressSet for AddressSet;

  Uni public uni;

  // delegator -> delegatee
  mapping(address => address) private _delegatee;
  
  // delegatee -> delegators
  mapping(address => AddressSet) private _delegators;

  constructor(Uni _uni) {
    uni= _uni;
  }

  function approve(address spender, uint _amount) external returns (bool)
  {
    _amount = bound(_amount, 0, type(uint96).max);
    vm.startPrank(msg.sender);
    uni.approve(spender, _amount);
    vm.stopPrank();
    return true;
  }

  // Track delegations performed by the users directly via the UNI token
  function transfer(address dst, uint _amount) external returns (bool)
  {
    // bound to the max available amount
    vm.startPrank(msg.sender);
    uint256 balance =  uni.balanceOf(msg.sender);
    _amount = bound(_amount, 0, balance);
    uni.transfer(dst, _amount);
    vm.stopPrank();
    return true;
  }

  // Track delegations performed by users directly via the UNI token
  function delegate(address delegatee)  public  
  {
    address prev_delegatee = _delegatee[msg.sender];
    _delegators[prev_delegatee].remove(msg.sender);
    _delegators[delegatee].add(msg.sender);
    _delegatee[msg.sender] = delegatee;

    vm.startPrank(msg.sender);
    uni.delegate(delegatee);
    vm.stopPrank();
  }

  // Advance the specified number of blocks. 
  // Needed to trigger UNI's block-numbers-based votes accounting
  function roll(uint16 advance) public  
  {
    vm.roll(block.number + advance);
  }

  function addDelegator(uint256 acc, address delegator) external view returns (uint256) {
    return acc + uni.balanceOf(delegator);
  }

  function sumDelegatorVotes(address delegatee)
    public view
    returns (uint256)
  {
    return _delegators[delegatee].reduce(0, this.addDelegator);
  }  
}
