contract Pool is AdminRole, DaoRole {
    using SafeMath for uint256;
    using SafeERC20 for IERC20Detailed;

    address public oneInchAddr;

    address internal constant _ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    uint256 internal constant _ETH_DECIMALS = 18;
    uint256 internal constant _MAX_QTY = (10**28); // 10B tokens
    uint256 internal constant _MAX_DECIMALS = 18;
    uint256 internal constant _PRECISION = (10**18);

    event Swapped(address srcToken, address destToken, uint256 actSrcAmount, uint256 actDestAmount);
    event Transferred(address asset, address receiver, uint256 amount);

    //  --------------------
    //  CONSTRUCTOR
    //  --------------------


    constructor (address oneInchAddr_) {
        require(oneInchAddr_ != address(0), "Pool: Invalid 1inch address!");
        oneInchAddr = oneInchAddr_;
    }


    //  --------------------
    //  PUBLIC
    //  --------------------


    function swap(
        address srcToken,
        uint256 srcAmount,
        uint256 minPrice,
        uint256 maxPrice,
        address destToken,
        bytes memory oneInchData
    ) public onlyDao returns (
        uint256 actualDestAmount,
        uint256 actualSrcAmount
    ) {
        require(minPrice < maxPrice, "swap: Incorrect min/max prices!");
        require(srcToken != destToken, "swap: Incorrect path of swap!");

        uint256 dInS; // price of dest token denominated in src token
        uint256 sInD; // price of src token denominated in dest token

        // 1inch trading
        (
            dInS,
            sInD,
            actualDestAmount,
            actualSrcAmount
        ) = _oneInchTrade(
            srcToken,
            srcAmount,
            destToken,
            oneInchData
        );
        
        // NOTE: Slippage is the part of 1Inch data.
        // require(minPrice <= dInS && dInS <= maxPrice, "swap: Incorrect min/max output!");

        emit Swapped(srcToken, destToken, actualSrcAmount, actualDestAmount);
    }

    function transfer(address token, address receiver, uint256 amount) public onlyAdmin {
        if (token == _ETH_ADDRESS) {
            (bool success, ) = receiver.call{value: amount}("");
            require(success, "transfer: Failed to send Ether!");
        }

        IERC20Detailed(token).safeTransfer(receiver, amount);

        emit Transferred(token, receiver, amount);
    }

    function updateOneInchAddress(address newOneInch) public onlyAdmin {
        require(newOneInch != address(0), "updateOneInchAddress: Invalid oneInch address!");

        oneInchAddr = newOneInch;
    }

    function getPoolBalance(address asset) public view returns (uint256) {
        return _getBalance(asset, address(this));
    }


    //  --------------------
    //  INTERNAL
    //  --------------------


    /**
     * @notice Wrapper function for doing token conversion on 1inch
     * @param _srcToken the token to convert from
     * @param _srcAmount the amount of tokens to be converted
     * @param _destToken the destination token
     * @return _destPriceInSrc the price of the dest token, in terms of source tokens
     *         _srcPriceInDest the price of the source token, in terms of dest tokens
     *         _actualDestAmount actual amount of dest token traded
     *         _actualSrcAmount actual amount of src token traded
     */
    function _oneInchTrade(
        address _srcToken,
        uint256 _srcAmount,
        address _destToken,
        bytes memory _calldata
    )
        internal
        returns (
            uint256 _destPriceInSrc,
            uint256 _srcPriceInDest,
            uint256 _actualDestAmount,
            uint256 _actualSrcAmount
        )
    {
        uint256 beforeSrcBalance = _getBalance(_srcToken, address(this));
        uint256 beforeDestBalance = _getBalance(_destToken, address(this));
        // Note: _actualSrcAmount is being used as msgValue here, because otherwise we'd run into the stack too deep error
        if (_srcToken != _ETH_ADDRESS) {
            _actualSrcAmount = 0;
            IERC20Detailed(_srcToken).approve(oneInchAddr, 0);
            IERC20Detailed(_srcToken).approve(oneInchAddr, _srcAmount);
        } else {
            _actualSrcAmount = _srcAmount;
        }

        // trade through 1inch proxy
        (bool success, ) = oneInchAddr.call{value: _actualSrcAmount}(_calldata);
        require(success, "_oneInchTrade: External 1inch issue!");

        // calculate trade amounts and price
        _actualDestAmount = _getBalance(_destToken, address(this)).sub(
            beforeDestBalance
        );
        _actualSrcAmount = beforeSrcBalance.sub(
            _getBalance(_srcToken, address(this))
        );
        require(_actualDestAmount > 0 && _actualSrcAmount > 0);
        _destPriceInSrc = _calcRateFromQty(
            _actualDestAmount,
            _actualSrcAmount,
            _getDecimals(_destToken),
            _getDecimals(_srcToken)
        );
        _srcPriceInDest = _calcRateFromQty(
            _actualSrcAmount,
            _actualDestAmount,
            _getDecimals(_srcToken),
            _getDecimals(_destToken)
        );
    }

    /**
     * @notice Calculates the rate of a trade. The rate is the price of the source token in the dest token, in 18 decimals.
     * Note: the rate is on the token level, not the wei level, so for example if 1 Atoken = 10 Btoken, then the rate
     * from A to B is 10 * 10**18, regardless of how many decimals each token uses.
     * @param srcAmount amount of source token
     * @param destAmount amount of dest token
     * @param srcDecimals decimals used by source token
     * @param dstDecimals decimals used by dest token
     */
    function _calcRateFromQty(
        uint256 srcAmount,
        uint256 destAmount,
        uint256 srcDecimals,
        uint256 dstDecimals
    ) internal pure returns (uint256) {
        require(srcAmount <= _MAX_QTY && destAmount <= _MAX_QTY, "_calcRateFromQty: More then allowed max!");

        if (dstDecimals >= srcDecimals) {
            require((dstDecimals - srcDecimals) <= _MAX_DECIMALS);
            return ((destAmount * _PRECISION) /
                ((10**(dstDecimals - srcDecimals)) * srcAmount));
        } else {
            require((srcDecimals - dstDecimals) <= _MAX_DECIMALS);
            return ((destAmount *
                _PRECISION *
                (10**(srcDecimals - dstDecimals))) / srcAmount);
        }
    }

    /**
     * @notice Get the token balance of an account
     * @param token the token to be queried
     * @param addr the account whose balance will be returned
     * @return token balance of the account
     */
    function _getBalance(address token, address addr)
        internal
        view
        returns (uint256)
    {
        if (token == _ETH_ADDRESS) {
            return uint256(addr.balance);
        }

        return uint256(IERC20Detailed(token).balanceOf(addr));
    }

    /**
     * @notice Get the number of decimals of a token
     * @param token the token to be queried
     * @return number of decimals
     */
    function _getDecimals(address token) internal view returns (uint256) {
        if (token == _ETH_ADDRESS) {
            return uint256(_ETH_DECIMALS);
        }

        return uint256(IERC20Detailed(token).decimals());
    }
}
/// @notice DETF pool smart contract
/// @author D-ETF.com
/// @dev The Pool contract keeps all underlaying tokens of DETF token.
/// Contract allowed to do swaps and change the ratio of the underlaying token, after governance contract approval.
function swap(
    address srcToken,
    uint256 srcAmount,
    uint256 minPrice,
    uint256 maxPrice,
    address destToken,
    bytes memory oneInchData
) public onlyDao returns (
    uint256 actualDestAmount,
    uint256 actualSrcAmount
) {
    require(minPrice < maxPrice, "swap: Incorrect min/max prices!");
    require(srcToken != destToken, "swap: Incorrect path of swap!");

    uint256 dInS; // price of dest token denominated in src token
    uint256 sInD; // price of src token denominated in dest token

    // 1inch trading
    (
        dInS,
        sInD,
        actualDestAmount,
        actualSrcAmount
    ) = _oneInchTrade(
        srcToken,
        srcAmount,
        destToken,
        oneInchData
    );
        
    // NOTE: Slippage is the part of 1Inch data.
    // require(minPrice <= dInS && dInS <= maxPrice, "swap: Incorrect min/max output!");

    emit Swapped(srcToken, destToken, actualSrcAmount, actualDestAmount);
}

//  --------------------
//  PUBLIC
//  --------------------