abstract contract MerkleAirdrop is MerkleProof, IAirdrop {

    mapping (uint256 => AirdropSettings) internal _settings;
    uint256 internal numSettings;

    mapping (uint256 => mapping(uint256 => uint256)) internal _redeemedData;
    mapping (uint256 => mapping(address => uint256)) internal _redeemedDataQuantities;
    mapping (uint256 => mapping(address => uint256)) internal _totalDataQuantities;

    /// @notice airdrops check to see if proof is redeemed
    /// @param drop the id of the airdrop
    /// @param redeemer the merkle proof
    /// @return isRedeemed the amount of tokens redeemed
    function redeemed(uint256 drop, address redeemer) external view override returns (bool isRedeemed) {

       isRedeemed = _redeemed(drop, redeemer);

    }

    /// @notice airdrops check to see if proof is redeemed
    /// @param drop the id of the airdrop
    /// @param recipient the merkle proof
    /// @return isRedeemed the amount of tokens redeemed
    function _redeemed(uint256 drop, address recipient) internal view returns (bool isRedeemed) {

        uint256 red = _totalDataQuantities[drop][recipient];
        uint256 tot = _redeemedDataQuantities[drop][recipient]; // i
        return red != 0 && red == tot;

    }

    /// @notice redeem tokens for airdrop
    /// @param drop the airdrop id
    /// @param leaf the index of the token in the airdrop
    /// @param recipient the beneficiary of the tokens
    /// @param amount tje amount of tokens to redeem
    /// @param merkleProof the merkle proof of the token
    function _redeem(uint256 drop, uint256 leaf, address recipient, uint256 amount, uint256 total, bytes32[] memory merkleProof) internal {

        // check to see if redeemed already
        uint256 _redeemedAmt = _redeemedDataQuantities[drop][recipient];
        uint256 _redeemedttl = _totalDataQuantities[drop][recipient];
        _redeemedttl = _redeemedAmt > 0 ? _redeemedttl : total;

        require(_redeemedAmt + amount <= _redeemedttl, "You have already redeemed this amount");
        _totalDataQuantities[drop][recipient] = _redeemedttl;
        _redeemedDataQuantities[drop][recipient] += amount; // increment amount redeemed

        bool valid = verify(
            bytes32 (_settings[drop].whitelistHash),
            bytes32 (leaf),
            merkleProof
        );

        // Check the merkle proof
        require(valid, "Merkle proof failed");

    }

    /// @notice add a new airdrop
    /// @param _airdrop the id of the airdrop
    function _addAirdrop(AirdropSettings memory _airdrop) internal{

        require(_settings[uint256(_airdrop.whitelistId)].whitelistId != _airdrop.whitelistId, "Airdrop already exists");
        _settings[uint256(uint256(_airdrop.whitelistId))] = _airdrop;

    }

    /// @notice Get the token sale settings
    /// @return settings the token sale settings
    function airdrop(uint256 drop) external view override returns (AirdropSettings memory settings) {

            require(_settings[drop].whitelistId == drop, "Airdrop does not exist");
            settings = _settings[drop];

    }

    // init the airdrop, rejecting the tx if already initialized
    function initMerkleAirdrops(AirdropSettings[] calldata settingsList) public {

        require(numSettings == 0, "Airdrops already initialized");
        require(settingsList.length > 0, "No airdrops provided");

        for (uint256 i = 0; i < settingsList.length; i++) {
            _addAirdrop(settingsList[i]);
        }
    }

}
function _addAirdrop(AirdropSettings memory _airdrop) internal{

    require(_settings[uint256(_airdrop.whitelistId)].whitelistId != _airdrop.whitelistId, "Airdrop already exists");
    _settings[uint256(uint256(_airdrop.whitelistId))] = _airdrop;

}

/// @notice add a new airdrop
/// @param _airdrop the id of the airdrop