contract CurveSwapper is ISwapper, Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    struct Route {
        address[] pools;
        int128[] indexes;
    }

    mapping(address => mapping(address => Route)) private routes;

    function setRoute(
        address _from,
        address _to,
        address[] memory _pools,
        address[] memory _tokens,
        int128[] memory _indexes
    ) public onlyOwner {
        require(_indexes.length >= 2, "CurveSwapper: INVALID_PATH");
        require(
            _pools.length.mul(2) == _indexes.length,
            "CurveSwapper: INVAILD_LENGTH"
        );
        Route storage route = routes[_from][_to];
        route.pools = _pools;
        route.indexes = _indexes;

        for (uint256 i = 0; i < route.pools.length; i++) {
            if (IERC20(_tokens[i]).allowance(address(this), _pools[i]) == 0) {
                IERC20(_tokens[i]).safeApprove(_pools[i], type(uint256).max);
            }
        }
    }

    // pools[]    a   b   c   d   pool.length  * 2 = indexes.length
    // indexes[]  0 1 1 2 2 3 3 4

    function getAmountsOut(uint256 _amount, Route memory route)
        public
        view
        returns (uint256[] memory amounts)
    {
        amounts = new uint256[](route.pools.length + 1);
        amounts[0] = _amount;
        for (uint256 i = 0; i < route.pools.length; i++) {
            amounts[i + 1] = ICurve(route.pools[i]).get_dy_underlying(
                route.indexes[i.mul(2)],
                route.indexes[i.mul(2).add(1)],
                amounts[i]
            );
        }
    }

    function swapToken(
        address _from, // ignore
        address _to, // ignore
        uint256 _amount,
        uint256 _minAmountOut,
        address _beneficiary // ignore
    ) public override {
        IERC20(_from).safeTransferFrom(msg.sender, address(this), _amount);

        Route memory route = routes[_from][_to];
        uint256[] memory amounts = getAmountsOut(_amount, route);
        uint256 amountOut = amounts[amounts.length - 1];
        require(
            amountOut >= _minAmountOut,
            "CurveSwapper: INSUFFICIENT_OUTPUT_AMOUNT"
        );

        for (uint256 i = 0; i < route.pools.length; i++) {
            ICurve(route.pools[i]).exchange_underlying(
                route.indexes[i.mul(2)],
                route.indexes[i.mul(2).add(1)],
                amounts[i],
                amounts[i + 1]
            );
        }

        require(
            IERC20(_to).balanceOf(address(this)) >= amountOut,
            "CurveSwapper: INVALID_SWAP_RESULT"
        );
        IERC20(_to).safeTransfer(_beneficiary, amountOut);
    }
}
function getAmountsOut(uint256 _amount, Route memory route)
    public
    view
    returns (uint256[] memory amounts)
{
    amounts = new uint256[](route.pools.length + 1);
    amounts[0] = _amount;
    for (uint256 i = 0; i < route.pools.length; i++) {
        amounts[i + 1] = ICurve(route.pools[i]).get_dy_underlying(
            route.indexes[i.mul(2)],
            route.indexes[i.mul(2).add(1)],
            amounts[i]
        );
    }
}

// pools[]    a   b   c   d   pool.length  * 2 = indexes.length
// indexes[]  0 1 1 2 2 3 3 4