library ExchangeIssuanceLibrary {
    using SafeMath for uint256;
    using AddressArrayUtils for address[];

    // ============ Structs ============

    struct ExchangeIssuanceParams {
        address setAddress;
        uint256 quantity;
        uint8[] sendTokenExchangeIds;
        address[] sendTokens;
        uint256[] sendTokenAmounts;
        address[] receiveTokens;
        uint256[] receiveTokenAmounts;
    }

    /**
     * Validates that the quantity to issue is positive and a multiple of the Set natural unit.
     *
     * @param _set                The address of the Set
     * @param _quantity           The quantity of Sets to issue or redeem
     */
    function validateQuantity(
        address _set,
        uint256 _quantity
    )
        internal
        view
    {
        // Make sure quantity to issue is greater than 0
        require(
            _quantity > 0,
            "ExchangeIssuanceLibrary.validateQuantity: Quantity must be positive"
        );

        // Make sure Issue quantity is multiple of the Set natural unit
        require(
            _quantity.mod(ISetToken(_set).naturalUnit()) == 0,
            "ExchangeIssuanceLibrary.validateQuantity: Quantity must be multiple of natural unit"
        );
    }

    /**
     * Validates that the required Components and amounts are valid components and positive.
     * Duplicate receive token values are not allowed
     *
     * @param _receiveTokens           The addresses of components required for issuance
     * @param _receiveTokenAmounts     The quantities of components required for issuance
     */
    function validateReceiveTokens(
        address[] memory _receiveTokens,
        uint256[] memory _receiveTokenAmounts
    )
        internal
        view
    {
        uint256 receiveTokensCount = _receiveTokens.length;

        // Make sure required components array is non-empty
        require(
            receiveTokensCount > 0,
            "ExchangeIssuanceLibrary.validateReceiveTokens: Receive tokens must not be empty"
        );

        // Ensure the receive tokens has no duplicates
        require(
            !_receiveTokens.hasDuplicate(),
            "ExchangeIssuanceLibrary.validateReceiveTokens: Receive tokens must not have duplicates"
        );

        // Make sure required components and required component amounts are equal length
        require(
            receiveTokensCount == _receiveTokenAmounts.length,
            "ExchangeIssuanceLibrary.validateReceiveTokens: Receive tokens and amounts must be equal length"
        );

        for (uint256 i = 0; i < receiveTokensCount; i++) {
            // Make sure all required component amounts are non-zero
            require(
                _receiveTokenAmounts[i] > 0,
                "ExchangeIssuanceLibrary.validateReceiveTokens: Component amounts must be positive"
            );
        }
    }

    /**
     * Validates that the tokens received exceeds what we expect
     *
     * @param _vault                        The address of the Vault
     * @param _receiveTokens                The addresses of components required for issuance
     * @param _requiredBalances             The quantities of components required for issuance
     * @param _userToCheck                  The address of the user
     */
    function validatePostExchangeReceiveTokenBalances(
        address _vault,
        address[] memory _receiveTokens,
        uint256[] memory _requiredBalances,
        address _userToCheck
    )
        internal
        view
    {
        // Get vault instance
        IVault vault = IVault(_vault);

        // Check that caller's receive tokens in Vault have been incremented correctly
        for (uint256 i = 0; i < _receiveTokens.length; i++) {
            uint256 currentBal = vault.getOwnerBalance(
                _receiveTokens[i],
                _userToCheck
            );

            require(
                currentBal >= _requiredBalances[i],
                "ExchangeIssuanceLibrary.validatePostExchangeReceiveTokenBalances: Insufficient receive token acquired"
            );
        }
    }

    /**
     * Validates that the send tokens inputs are valid. Since tokens are sent to various exchanges,
     * duplicate send tokens are valid
     *
     * @param _core                         The address of Core
     * @param _sendTokenExchangeIds         List of exchange wrapper enumerations corresponding to
     *                                          the wrapper that will handle the component
     * @param _sendTokens                   The address of the send tokens
     * @param _sendTokenAmounts             The quantities of send tokens
     */
    function validateSendTokenParams(
        address _core,
        uint8[] memory _sendTokenExchangeIds,
        address[] memory _sendTokens,
        uint256[] memory _sendTokenAmounts
    )
        internal
        view
    {
        require(
            _sendTokens.length > 0,
            "ExchangeIssuanceLibrary.validateSendTokenParams: Send token inputs must not be empty"
        );

        require(
            _sendTokenExchangeIds.length == _sendTokens.length &&
            _sendTokens.length == _sendTokenAmounts.length,
            "ExchangeIssuanceLibrary.validateSendTokenParams: Send token inputs must be of the same length"
        );

        ICore core = ICore(_core);

        for (uint256 i = 0; i < _sendTokenExchangeIds.length; i++) {
            // Make sure all exchanges are valid
            require(
                core.exchangeIds(_sendTokenExchangeIds[i]) != address(0),
                "ExchangeIssuanceLibrary.validateSendTokenParams: Must be valid exchange"
            );

            // Make sure all send token amounts are non-zero
            require(
                _sendTokenAmounts[i] > 0,
                "ExchangeIssuanceLibrary.validateSendTokenParams: Send amounts must be positive"
            );
        }
    }
}
/**
 * @title ExchangeIssuanceLibrary
 * @author Set Protocol
 *
 * The ExchangeIssuanceLibrary contains functions for validating exchange order data
 */
function validateQuantity(
    address _set,
    uint256 _quantity
)
    internal
    view
{
    // Make sure quantity to issue is greater than 0
    require(
        _quantity > 0,
        "ExchangeIssuanceLibrary.validateQuantity: Quantity must be positive"
    );

    // Make sure Issue quantity is multiple of the Set natural unit
    require(
        _quantity.mod(ISetToken(_set).naturalUnit()) == 0,
        "ExchangeIssuanceLibrary.validateQuantity: Quantity must be multiple of natural unit"
    );
}

/**
 * Validates that the quantity to issue is positive and a multiple of the Set natural unit.
 *
 * @param _set                The address of the Set
 * @param _quantity           The quantity of Sets to issue or redeem
 */