contract Shaman is ERC721 {

    modifier callerIsUser() {
        require(tx.origin == msg.sender, "The caller is another contract");
        _;
    }

    modifier claimStarted() {
        require(
            startClaimDate != 0 && startClaimDate <= block.timestamp,
            "You are too early"
        );

        _;
    }

    uint256 private startClaimDate = 1638536400; //3rd Dec 2021 1PM UTC
    uint256 private claimPrice =  0.1 ether;
    uint256 private totalTokens = 6000;
    uint256 private totalMintedTokens = 0;
    uint128 private basisPoints = 10000;
    string private baseURI =
        "https://ipfs.io/ipfs/QmUdhuHfJmN8eFvSEWFpE2LtMXiUtXGHXQUUS8PZPahfSd/";

    DJENERATES private djen = DJENERATES(address(0x7d05c8D8cC1baC936eA09308a9E94823986f8321));
    mapping(address => uint256) private claimedTokensPerWallet;

    mapping(uint16 => uint16[]) private availableTokens;

    constructor() ERC721("DJENERATES - SPIRIT ANIMALS", "SA") {}

    /**
     * @dev Allows to withdraw the Ether in the contract
     */
    function withdraw() external onlyOwner {
        uint256 totalBalance = address(this).balance;
        payable(msg.sender).transfer(totalBalance);
    }

    /**
     * @dev Sets the base URI for the API that provides the NFT data.
     */
    function setBaseTokenURI(string memory _uri) external onlyOwner {
        baseURI = _uri;
    }

    /**
     * @dev Sets the claim price for each token
     */
    function setClaimPrice(uint256 _claimPrice) external onlyOwner {
        claimPrice = _claimPrice;
    }

    /**
     * @dev Populates the available tokens
     */
    function addAvailableTokens(uint16 num,uint16 from, uint16 to)
        external
        onlyOwner
    {
       for (uint16 i = from; i <= to; i++) {
            availableTokens[num].push(i);
        }
    }

    /**
     * @dev Sets the date that users can start claiming tokens
     */
    function setStartClaimDate(uint256 _startClaimDate)
        external
        onlyOwner
    {
        startClaimDate = _startClaimDate;
    }

    /**
     * @dev Claim a single token
     */
    function claimToken(uint16 animal) external callerIsUser claimStarted {
        require(availableTokens[animal].length > 0, "No tokens left to be claimed for this Animal");
        uint256 balance= djen.balanceOf(msg.sender);
        require(
            balance > 0,"You are not holding any DJEN for free claim"
        );
        require(
            balance > claimedTokensPerWallet[msg.sender],"You are not eligible for more free claim"
        );

        claimedTokensPerWallet[msg.sender]++;
        totalMintedTokens++;

        _mint(msg.sender, getTokenToBeClaimed(animal));
    }

    /**
     * @dev Claim up to 50 tokens at once
     */
    function claimTokens(uint16 animal,uint256 amount)
        external
        payable
        callerIsUser
        claimStarted
    {
        require(
            msg.value >= claimPrice * amount,
            "Not enough Ether to claim the tokens"
        );
        require(availableTokens[animal].length >= amount, "No tokens left to be claimed for this Animal");

        uint256[] memory tokenIds = new uint256[](amount);

        totalMintedTokens += amount;

        for (uint256 i; i < amount; i++) {
            tokenIds[i] = getTokenToBeClaimed(animal);
        }

        _batchMint(msg.sender, tokenIds);
    }

    /**
     * @dev Returns the tokenId by index
     */
    function tokenByIndex(uint256 tokenId) external view returns (uint256) {
        require(
            _exists(tokenId),
            "ERC721: operator query for nonexistent token"
        );

        return tokenId;
    }

    /**
     * @dev Returns the base URI for the tokens API.
     */
    function baseTokenURI() external view returns (string memory) {
        return baseURI;
    }

    /**
     * @dev Returns how many tokens are still available to be claimed
     */
    function getAvailableTokens(uint16 animal) external view returns (uint256) {
        return availableTokens[animal].length;
    }

    /**
     * @dev Returns the claim price
     */
    function getClaimPrice() external view returns (uint256) {
        return claimPrice;
    }

    /**
     * @dev Returns the total supply
     */
    function totalSupply() external view virtual returns (uint256) {
        return totalMintedTokens;
    }

    // Private and Internal functions

    /**
     * @dev Returns a random available token to be claimed
     */
    function getTokenToBeClaimed(uint16 animal) private returns (uint256) {
        uint256 random = _getRandomNumber(animal,availableTokens[animal].length);
        uint256 tokenId = uint256(availableTokens[animal][random]);

        availableTokens[animal][random] = availableTokens[animal][availableTokens[animal].length - 1];
        availableTokens[animal].pop();

        return tokenId;
    }

    /**
     * @dev Generates a pseudo-random number.
     */
    function _getRandomNumber(uint16 animal,uint256 _upper) private view returns (uint256) {
        uint256 random = uint256(
            keccak256(
                abi.encodePacked(
                    availableTokens[animal].length,
                    blockhash(block.number - 1),
                    block.coinbase,
                    block.difficulty,
                    msg.sender
                )
            )
        );

        return random % _upper;
    }

    /**
     * @dev See {ERC721}.
     */
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }
}
function totalSupply() external view virtual returns (uint256) {
    return totalMintedTokens;
}

/**
 * @dev Returns the total supply
 */