contract NounishPebbles is ERC721A, Ownable, ReentrancyGuard {
    uint16 public constant MAX_TOTAL_TOKENS = 727;

    address private POINTLESS_PEBBLES;
    bool private _halt_mint = true;
    bool private _is_revealed;
    string private _URI;

    // this is a packed array of booleans
    mapping(uint256 => uint256) private _token_used;


    constructor(address pointlesPebblesContract_) ERC721A("NounishPebbles", "NOUNPBLS") {
        POINTLESS_PEBBLES = pointlesPebblesContract_;
    }

    function mint(uint256 quantity) external nonReentrant {
        require(_halt_mint == false, "Halted");
        require(totalSupply() < MAX_TOTAL_TOKENS, "fin.");
        require(totalSupply()+quantity < MAX_TOTAL_TOKENS, ">maxTokens");
        require(quantity > 0, "quantity>0");

        PointlessPebbles pointLessPebbles = PointlessPebbles(POINTLESS_PEBBLES);

        uint256 elegibleForRedemption;
        uint256 alreadyClaimed;
        uint256 balanceOf = pointLessPebbles.balanceOf(msg.sender);
        
        for (uint i = 0; i < balanceOf; i++) {
            uint256 tokenId = pointLessPebbles.tokenOfOwnerByIndex(msg.sender,i);
            if (isClaimed(tokenId)) {
                alreadyClaimed +=1;
            }
        }

        elegibleForRedemption = balanceOf - alreadyClaimed;

        require(quantity<=elegibleForRedemption, "claiming too many");

        for (uint i = alreadyClaimed; i < alreadyClaimed+quantity; i++) {
            uint256 tokenId = pointLessPebbles.tokenOfOwnerByIndex(msg.sender,i);
            _setClaimed(tokenId);
        }

        _safeMint(msg.sender, quantity);
    }

    function isClaimed(uint256 tokenId_) public view returns (bool) {
        uint256 claimedWordIndex = tokenId_ / 256;
        uint256 claimedBitIndex = tokenId_ % 256;
        uint256 claimedWord = _token_used[claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        return claimedWord & mask == mask;
    }

    function _setClaimed(uint256 tokenId_) private {
        uint256 claimedWordIndex = tokenId_ / 256;
        uint256 claimedBitIndex = tokenId_ % 256;
        _token_used[claimedWordIndex] = _token_used[claimedWordIndex] | (1 << claimedBitIndex);
    }


    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), 'tokenId?');

        // include the token index if revealed
        if (_is_revealed) {
            return string(abi.encodePacked(_URI, toString(tokenId)));
        } 

        // otherwise return the URI
        return string(_URI);
    }

    /**
     * @dev Halt minting 
    */
    function setHaltMint(bool v) public onlyOwner() {
        _halt_mint = v;
    }

    /**
     * @dev Withdraw ether from this contract (Callable by owner)
    **/
    function withdraw() public onlyOwner() {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    /**
     * @dev Set URI 
    */
    function setURI(string memory v) public onlyOwner() {
        _URI = v;
    }

    /**
     * @dev Set reveal 
    */
    function setIsReveal(bool v) public onlyOwner() {
        _is_revealed = v;
    }

	/////////////////////////////////////////////
	function toString(uint256 value) internal pure returns (string memory) {
		// Inspired by OraclizeAPI's implementation - MIT licence
		// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

		if (value == 0) {
			return "0";
		}
		uint256 temp = value;
		uint256 digits;
		while (temp != 0) {
			digits++;
			temp /= 10;
		}
		bytes memory buffer = new bytes(digits);
		while (value != 0) {
			digits -= 1;
			buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
			value /= 10;
		}
		return string(buffer);
	}
}
function setURI(string memory v) public onlyOwner() {
    _URI = v;
}

/**
 * @dev Set URI
 */