// Contract: Bodega
// Target function: function tokenURI(uint256 tokenId)
(address from) virtual {
        // Check registry code length to facilitate testing in environments without a deployed registry.
        if (address(operatorFilterRegistry).code.length > 0) {
            // Allow spending tokens from addresses with balance
            // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
            // from an EOA.
            if (from == msg.sender) {
                _;
                return;
            }
            if (
                !(
                    operatorFilterRegistry.isOperatorAllowed(address(this), msg.sender)
                        && operatorFilterRegistry.isOperatorAllowed(address(this), from)
                )
            ) {
                revert OperatorNotAllowed(msg.sender);
            }
        }
        _;
    }
}

interface IClaimContract {
    function initRewards(uint256 tokenId) external;
}

contract Bodega is
    Context,
    ERC165,
    IERC721,
    IERC721Metadata,
    ReentrancyGuard,
    Ownable
{
    using Address for address;
    using SafeMath for uint256;

    // Token name
    string private constant _name = "Bodega Blocks";

    // Token symbol
    string private constant _symbol = "BODEGA";

    // total number of NFTs Minted
    uint256 private _totalSupply;
    uint256 public currentSupplyIndex;

    // max supply cap
    uint256 public constant MAX_SUPPLY = 8_999;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    // base URI
    string private baseURI = "temp_image";
    string private ending = ".json";

    // cost for minting NFT
    uint256 public costETH = 157 * 10**15;

    // total supply of each NFT
    uint256 public totalClass0;
    uint256 public totalClass1;
    uint256 public totalClass2;

    // constant break points
    uint256 public constant breakPoint0 = 111;
    uint256 public constant breakPoint1 = 1_222;

    // Enable Trading
    bool public tradingEnabled = false;
    bool public whitelistEnabled = true;

    // Whitelisted Users
    mapping(address => bool) public isWhitelisted;

    // Claim Contract
    address public claimContract;

    // Whether or not to check allowed operator
    bool public checkAllowedOperator = true;

    modifier canMint(uint256 numberOfMints) {
        require(tradingEnabled, "Trading Not Enabled");
        if (whitelistEnabled) {
            require(isWhitelisted[msg.sender], "Sender Not Whitelisted");
        }
        require(numberOfMints > 0, "Invalid Input");
        _;
    }

    error OperatorNotAllowed(address operator);

    IOperatorFilterRegistry constant operatorFilterRegistry =
        IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E);

    modifier onlyAllowedOperator(address from) virtual {
        if (checkAllowedOperator == false) {
            _;
            return;
        }
        // Check registry code length to facilitate testing in environments without a deployed registry.
        if (address(operatorFilterRegistry).code.length > 0) {
            // Allow spending tokens from addresses with balance
            // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
            // from an EOA.
            if (from == msg.sender) {
                _;
                return;
            }
            if (
                !(
                    operatorFilterRegistry.isOperatorAllowed(address(this), msg.sender)
                        && operatorFilterRegistry.isOperatorAllowed(address(this), from)
                )
            ) {
                revert OperatorNotAllowed(msg.sender);
            }
        }
        _;
    }

    constructor() {
        // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
        // will not revert, but the contract will need to be registered with the registry once it is deployed in
        // order for the modifier to filter addresses.

        if (address(operatorFilterRegistry).code.length > 0) {
            operatorFilterRegistry.registerAndSubscribe(address(this), 0xFD7bfa171B5b81b79C245456E986db2f32fBFaDb); 
        }
    }

    ////////////////////////////////////////////////
    ///////////     OWNER FUNCTIONS      ///////////
    ////////////////////////////////////////////////

    function setClaimContract(address newClaim) external onlyOwner {
        claimContract = newClaim;
    }

    function setCheckAllowedOperator(bool checkOperator) external onlyOwner {
        checkAllowedOperator = checkOperator;
    }

    function enableMinting() external onlyOwner {
        tradingEnabled = true;
    }

    function disableMinting() external onlyOwner {
        tradingEnabled = false;
    }

    function disableWhitelist() external onlyOwner {
        whitelistEnabled = false;
    }

    function enableWhitelist() external onlyOwner {
        whitelistEnabled = true;
    }

    function withdraw() external onlyOwner {
        (bool s, ) = payable(msg.sender).call{value: address(this).balance}("");
        require(s);
    }

    function withdrawToken(address token_) external onlyOwner {
        require(token_ != address(0), "Zero Address");
        IERC20(token_).transfer(
            msg.sender,
            IERC20(token_).balanceOf(address(this))
        );
    }

    function setCostETH(uint256 newCostETH) external onlyOwner {
        costETH = newCostETH;
    }

    function setBaseURI(string calldata newURI) external onlyOwner {
        baseURI = newURI;
    }

    function setURIExtention(string calldata newExtention) external onlyOwner {
        ending = newExtention;
    }

    function addWhitelist(address[] calldata users) external onlyOwner {
        uint256 length = users.length;
        for (uint256 i = 0; i < length; ) {
            isWhitelisted[users[i]] = true;
            unchecked {
                ++i;
            }
        }
    }

    function ownerMint(address[] calldata users, uint256[] calldata amounts)
        external
        onlyOwner
    {
        uint256 len = users.length;
        for (uint256 i = 0; i < len; ) {
            for (uint256 j = 0; j < amounts[i]; j++) {
                _safeMint(users[i], currentSupplyIndex);
            }
            unchecked {
                ++i;
            }
        }
    }

    ////////////////////////////////////////////////
    ///////////     PUBLIC FUNCTIONS     ///////////
    ////////////////////////////////////////////////

    /**
     * Mints `numberOfMints` NFTs To Caller
     */
    function mintFinalPortion(uint256 numberOfMints)
        external
        payable
        canMint(numberOfMints)
    {
        require(currentSupplyIndex >= breakPoint1, "Mint Exceeds Breakpoint");
        _mintETH(numberOfMints);
    }

    /**
     * Mints `numberOfMints` NFTs To Caller
     */
    function mintFirstPortion(uint256 numberOfMints)
        external
        payable
        canMint(numberOfMints)
    {
        require(
            currentSupplyIndex + numberOfMints <= breakPoint1,
            "Mint Exceeds Breakpoint"
        );
        _mintETH(numberOfMints);
    }

    function burn(uint256 tokenID) external {
        require(
            _isApprovedOrOwner(_msgSender(), tokenID),
            "caller not owner nor approved"
        );
        _burn(tokenID);
    }

    receive() external payable {}

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public override {
        address wpowner = ownerOf(tokenId);
        require(to != wpowner, "ERC721: approval to current owner");

        require(
            _msgSender() == wpowner || isApprovedForAll(wpowner, _msgSender()),
            "ERC721: not approved or owner"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address _operator, bool approved)
        public
        override
    {
        _setApprovalForAll(_msgSender(), _operator, approved);
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId),
            "caller not owner nor approved"
        );
        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public override onlyAllowedOperator(from) {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId),
            "caller not owner nor approved"
        );
        _safeTransfer(from, to, tokenId, _data);
    }

    ////////////////////////////////////////////////
    ///////////    INTERNAL FUNCTIONS    ///////////
    ////////////////////////////////////////////////

    function _mintETH(uint256 numberOfMints) internal {
        require(costETH * numberOfMints <= msg.value, "Incorrect Value Sent");
        for (uint256 i = 0; i < numberOfMints; ) {
            _safeMint(msg.sender, currentSupplyIndex);
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId) internal {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, ""),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal {
        require(!_exists(tokenId), "ERC721: token already minted");
        require(currentSupplyIndex < MAX_SUPPLY, "All NFTs Have Been Minted");

        // set owner of tokenId
        _owners[tokenId] = to;

        // Increment necessary values for tracking, no need to check for overflow
        unchecked {
            _balances[to]++;
            _totalSupply++;
            currentSupplyIndex++;
            if (tokenId < breakPoint0) {
                totalClass0++;
            } else if (tokenId < breakPoint1) {
                totalClass1++;
            } else {
                totalClass2++;
            }
        }

        // Cancel trading if breakpoint is exceeded
        if (currentSupplyIndex == breakPoint1) {
            tradingEnabled = false;
        }

        // init rewards
        IClaimContract(claimContract).initRewards(tokenId);

        // Emit initial ID transfer
        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        require(_exists(tokenId), "Token Does Not Exist");

        // owner of token
        address _owner = ownerOf(tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        // decrement balance
        _balances[_owner] -= 1;
        delete _owners[tokenId];

        // decrement total supply
        _totalSupply -= 1;

        // decrement total class quantity
        if (tokenId < breakPoint0) {
            totalClass0--;
        } else if (tokenId < breakPoint1) {
            totalClass1--;
        } else {
            totalClass2--;
        }

        // emit transfer
        emit Transfer(_owner, address(0), tokenId);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `_data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal {
        _transfer(from, to, tokenId);
        require(
            _checkOnERC721Received(from, to, tokenId, _data),
            "ERC721: non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal nonReentrant {
        require(ownerOf(tokenId) == from, "Incorrect owner");
        require(to != address(0), "zero address");
        require(balanceOf(from) > 0, "Zero Balance");

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        // Allocate balances
        _balances[from] = _balances[from].sub(1);
        _balances[to] += 1;
        _owners[tokenId] = to;

        // emit transfer
        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address wpowner,
        address _operator,
        bool approved
    ) internal {
        require(wpowner != _operator, "ERC721: approve to caller");
        _operatorApprovals[wpowner][_operator] = approved;
        emit ApprovalForAll(wpowner, _operator, approved);
    }

    ////////////////////////////////////////////////
    ///////////      READ FUNCTIONS      ///////////
    ////////////////////////////////////////////////

    function owner() external view returns (address) {
        return this.getOwner();
    }

    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }

    function getIDsByOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256[] memory ids = new uint256[](balanceOf(_owner));
        if (balanceOf(_owner) == 0) return ids;
        uint256 count = 0;
        for (uint256 i = 0; i < currentSupplyIndex; i++) {
            if (_owners[i] == _owner) {
                ids[count] = i;
                count++;
            }
        }
        return ids;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC165, IERC165)
        returns (bool)
    {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address wpowner) public view override returns (uint256) {
        require(wpowner != address(0), "query for the zero address");
        return _balances[wpowner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view override returns (address) {
        address wpowner = _owners[tokenId];
        require(wpowner != address(0), "query for nonexistent token");
        return wpowner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public pure override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public pure override returns (string memory) {
        return _symbol;
    }