contract Aggregator is Ownable {
  using SafeMath for uint256;

  mapping(address => address) public ownerToSpender;

  address public routerV2;

  address public immutable WETH;

  event RouterChanged(address _router);

  event WithdrawDustToken(address _token, address _to, uint256 _amount);

  event WithdrawDustETH(address _to, uint256 _amount);

  constructor(address _WETH) {
    WETH = _WETH;
  }

  function setRouter(address _routerV2) external onlyOwner {
    routerV2 = _routerV2;
    emit RouterChanged(_routerV2);
  }

  function multicall(
    address _token0,
    address _token1, 
    address[] calldata _recipients, 
    uint256[] calldata _amountIns, 
    uint256[] calldata _maxOuts, 
    address _factory, 
    bool _isBuy,
    bool _isPercent,
    bytes32[] memory _data
  ) external payable {
    require(_recipients.length == _amountIns.length && _maxOuts.length == _amountIns.length, "Invalid parameters");

    uint256 totalAmount;
    address sender = msg.sender;

    for (uint256 i; i < _amountIns.length; ++i) {
      totalAmount = totalAmount.add(_amountIns[i]);
    }

    if (_token0 == WETH) {
      IWETH(WETH).deposit{value: msg.value}();
      require(totalAmount <= msg.value, "ETH is less than total amount");
      IWETH(WETH).approve(routerV2, totalAmount);
      sender = address(this);
    }

    {
      address token0 = _token0;
      address token1 = _token1;
      address[] calldata recipients = _recipients;
      uint256[] calldata amountIns = _amountIns;
      uint256[] calldata maxOuts = _maxOuts;
      bytes32[] memory data = _data;
      address factory = _factory;

      if (_isBuy) {
        IRouterV2(routerV2).buy(
          sender, 
          token0, 
          token1, 
          recipients, 
          amountIns, 
          maxOuts, 
          factory, 
          data
        );
      } else {
        bool isPercent = _isPercent;
        IRouterV2(routerV2).sell(
          token0, 
          token1, 
          recipients, 
          amountIns, 
          factory,
          isPercent, 
          data
        );
      }
    }
  }

  function multiSendETH(address[] calldata _recipients, uint256[] calldata _amounts) external payable {
    require(_recipients.length == _amounts.length, "Invalid parameters");

    uint256 totalAmount;
    for (uint256 i; i < _recipients.length; ++i) {
      (bool success, ) = _recipients[i].call{ value: _amounts[i]}("");
      require(success, "Address: unable to send value, recipient may have reverted");
      totalAmount = totalAmount.add(_amounts[i]);
    }

    require(totalAmount <= msg.value, 'Insufficient amount');
  }

  function multiSendToken(address _token, address[] calldata _recipients, uint256[] calldata _amounts) external {
    require(_recipients.length == _amounts.length, "Invalid parameters");

    for (uint256 i; i < _recipients.length; ++i) {
      TransferHelper.safeTransferFrom(_token, msg.sender, _recipients[i], _amounts[i]);
    }
  }

  // To receive ETH from uniswapV2Router when swapping
  receive() external payable {}

  // Withdraw dust tokens
  function withdrawDustToken(address _token, address _to)
    external
    onlyOwner
    returns (bool _sent)
  {
      require(_token != WETH, "Can't withdraw WETH");
      uint256 _amount = IERC20(_token).balanceOf(address(this));
      _sent = IERC20(_token).transfer(_to, _amount);
      emit WithdrawDustToken(_token, _to, _amount);
  }

  // Withdraw Dust ETH
  function withdrawDustETH(address _to) external onlyOwner {
    uint256 _amount = address(this).balance;
    (bool success, ) = _to.call{ value: _amount}("");
    require(success, "Address: unable to send value, recipient may have reverted");
    emit WithdrawDustETH(_to, _amount);
  }
}
// To receive ETH from uniswapV2Router when swapping