contract MathGold is Context, Ownable, ERC20 {
    // Math contract is available at https://etherscan.io/address/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7
    address public mathContractAddress =
        0x5BDf397bB2912859Dbd8011F320a222f79A28d2E;
    IERC721Enumerable public mathContract;

    // Give out 10,000 Math Gold for every Math that a user holds
    uint256 public mathGoldPerTokenId = 10000 * (10**decimals());

    // tokenIdStart of 1
    uint256 public tokenIdStart = 1;

    // tokenIdEnd of 4196
    uint256 public tokenIdEnd = 4196;

    // Seasons are used to allow users to claim tokens regularly. Seasons are
    // decided by the DAO.
    uint256 public season = 0;

    // Track claimed tokens within a season
    // IMPORTANT: The format of the mapping is:
    // claimedForSeason[season][tokenId][claimed]
    mapping(uint256 => mapping(uint256 => bool)) public seasonClaimedByTokenId;

    constructor() Ownable() ERC20("Math Gold", "MATH") {
        mathContract = IERC721Enumerable(mathContractAddress);

        // it's fair launch my dear apes
        _mint(0x5f350bF5feE8e254D6077f8661E9C7B83a30364e, 4206969 * (10**decimals()));
    }

    /// @notice Claim Math Gold for a given Math ID
    /// @param tokenId The tokenId of the Math NFT
    function claimById(uint256 tokenId) external {
        // Follow the Checks-Effects-Interactions pattern to prevent reentrancy
        // attacks

        // Checks

        // Check that the msgSender owns the token that is being claimed
        require(
            _msgSender() == mathContract.ownerOf(tokenId),
            "MUST_OWN_TOKEN_ID"
        );

        // Further Checks, Effects, and Interactions are contained within the
        // _claim() function
        _claim(tokenId, _msgSender());
    }

    /// @notice Claim Math Gold for all tokens owned by the sender
    /// @notice This function will run out of gas if you have too much loot! If
    /// this is a concern, you should use claimRangeForOwner and claim Math
    /// Gold in batches.
    function claimAllForOwner() external {
        uint256 tokenBalanceOwner = mathContract.balanceOf(_msgSender());

        // Checks
        require(tokenBalanceOwner > 0, "NO_TOKENS_OWNED");

        // i < tokenBalanceOwner because tokenBalanceOwner is 1-indexed
        for (uint256 i = 0; i < tokenBalanceOwner; i++) {
            // Further Checks, Effects, and Interactions are contained within
            // the _claim() function
            _claim(
                mathContract.tokenOfOwnerByIndex(_msgSender(), i),
                _msgSender()
            );
        }
    }

    /// @notice Claim Math Gold for all tokens owned by the sender within a
    /// given range
    /// @notice This function is useful if you own too much Math to claim all at
    /// once or if you want to leave some Math unclaimed. If you leave Math
    /// unclaimed, however, you cannot claim it once the next season starts.
    function claimRangeForOwner(uint256 ownerIndexStart, uint256 ownerIndexEnd)
        external
    {
        uint256 tokenBalanceOwner = mathContract.balanceOf(_msgSender());

        // Checks
        require(tokenBalanceOwner > 0, "NO_TOKENS_OWNED");

        // We use < for ownerIndexEnd and tokenBalanceOwner because
        // tokenOfOwnerByIndex is 0-indexed while the token balance is 1-indexed
        require(
            ownerIndexStart >= 0 && ownerIndexEnd < tokenBalanceOwner,
            "INDEX_OUT_OF_RANGE"
        );

        // i <= ownerIndexEnd because ownerIndexEnd is 0-indexed
        for (uint256 i = ownerIndexStart; i <= ownerIndexEnd; i++) {
            // Further Checks, Effects, and Interactions are contained within
            // the _claim() function
            _claim(
                mathContract.tokenOfOwnerByIndex(_msgSender(), i),
                _msgSender()
            );
        }
    }

    /// @dev Internal function to mint Math upon claiming
    function _claim(uint256 tokenId, address tokenOwner) internal {
        // Checks
        // Check that the token ID is in range
        // We use >= and <= to here because all of the token IDs are 0-indexed
        require(
            tokenId >= tokenIdStart && tokenId <= tokenIdEnd,
            "TOKEN_ID_OUT_OF_RANGE"
        );

        // Check that Math Gold have not already been claimed this season
        // for a given tokenId
        require(
            !seasonClaimedByTokenId[season][tokenId],
            "GOLD_CLAIMED_FOR_TOKEN_ID"
        );

        // Effects

        // Mark that Math Gold has been claimed for this season for the
        // given tokenId
        seasonClaimedByTokenId[season][tokenId] = true;

        // Interactions

        // Send Math Gold to the owner of the token ID
        _mint(tokenOwner, mathGoldPerTokenId);
    }

    /// @notice Allows the DAO to mint new tokens for use within the Math
    /// Ecosystem
    /// @param amountDisplayValue The amount of Math to mint. This should be
    /// input as the display value, not in raw decimals. If you want to mint
    /// 100 Math, you should enter "100" rather than the value of 100 * 10^18.
    function daoMint(uint256 amountDisplayValue) external onlyOwner {
        _mint(owner(), amountDisplayValue * (10**decimals()));
    }

    /// @notice Allows the DAO to set a new contract address for Math. This is
    /// relevant in the event that Math migrates to a new contract.
    /// @param mathContractAddress_ The new contract address for Math
    function daoSetMathContractAddress(address mathContractAddress_)
        external
        onlyOwner
    {
        mathContractAddress = mathContractAddress_;
        mathContract = IERC721Enumerable(mathContractAddress);
    }

    /// @notice Allows the DAO to set the token IDs that are eligible to claim
    /// Math
    /// @param tokenIdStart_ The start of the eligible token range
    /// @param tokenIdEnd_ The end of the eligible token range
    /// @dev This is relevant in case a future Math contract has a different
    /// total supply of Math
    function daoSetTokenIdRange(uint256 tokenIdStart_, uint256 tokenIdEnd_)
        external
        onlyOwner
    {
        tokenIdStart = tokenIdStart_;
        tokenIdEnd = tokenIdEnd_;
    }

    /// @notice Allows the DAO to set a season for new Math Gold claims
    /// @param season_ The season to use for claiming Math
    function daoSetSeason(uint256 season_) public onlyOwner {
        season = season_;
    }

    /// @notice Allows the DAO to set the amount of Math Gold that is
    /// claimed per token ID
    /// @param mathGoldDisplayValue The amount of Math a user can claim.
    /// This should be input as the display value, not in raw decimals. If you
    /// want to mint 100 Math, you should enter "100" rather than the value of
    /// 100 * 10^18.
    function daoSetMathGoldPerTokenId(uint256 mathGoldDisplayValue)
        public
        onlyOwner
    {
        mathGoldPerTokenId = mathGoldDisplayValue * (10**decimals());
    }

    /// @notice Allows the DAO to set the season and Math Gold per token ID
    /// in one transaction. This ensures that there is not a gap where a user
    /// can claim more Math Gold than others
    /// @param season_ The season to use for claiming loot
    /// @param mathGoldDisplayValue The amount of Math a user can claim.
    /// This should be input as the display value, not in raw decimals. If you
    /// want to mint 100 Math, you should enter "100" rather than the value of
    /// 100 * 10^18.
    /// @dev We would save a tiny amount of gas by modifying the season and
    /// mathGold variables directly. It is better practice for security,
    /// however, to avoid repeating code. This function is so rarely used that
    /// it's not worth moving these values into their own internal function to
    /// skip the gas used on the modifier check.
    function daoSetSeasonAndMathGoldPerTokenID(
        uint256 season_,
        uint256 mathGoldDisplayValue
    ) external onlyOwner {
        daoSetSeason(season_);
        daoSetMathGoldPerTokenId(mathGoldDisplayValue);
    }
}
/// @title Math Gold for Terraforms holders!
/// @author Will Papper <https://twitter.com/WillPapper>
/// Modified by zefram.eth
/// @notice This contract mints Math Gold for Math holders and provides
/// administrative functions to the Math Gold DAO. It allows:
/// * Terraforms holders to claim Math Gold
/// * A DAO to set seasons for new opportunities to claim Math Gold
/// * A DAO to mint Math Gold for use within the Mathcastles ecosystem
/// @custom:unaudited This contract has not been audited. Use at your own risk.
function claimRangeForOwner(uint256 ownerIndexStart, uint256 ownerIndexEnd)
    external
{
    uint256 tokenBalanceOwner = mathContract.balanceOf(_msgSender());

    // Checks
    require(tokenBalanceOwner > 0, "NO_TOKENS_OWNED");

    // We use < for ownerIndexEnd and tokenBalanceOwner because
    // tokenOfOwnerByIndex is 0-indexed while the token balance is 1-indexed
    require(
        ownerIndexStart >= 0 && ownerIndexEnd < tokenBalanceOwner,
        "INDEX_OUT_OF_RANGE"
    );

    // i <= ownerIndexEnd because ownerIndexEnd is 0-indexed
    for (uint256 i = ownerIndexStart; i <= ownerIndexEnd; i++) {
        // Further Checks, Effects, and Interactions are contained within
        // the _claim() function
        _claim(
            mathContract.tokenOfOwnerByIndex(_msgSender(), i),
            _msgSender()
        );
    }
}

/// @notice Claim Math Gold for all tokens owned by the sender within a
/// given range
/// @notice This function is useful if you own too much Math to claim all at
/// once or if you want to leave some Math unclaimed. If you leave Math
/// unclaimed, however, you cannot claim it once the next season starts.