// Contract: MonstersAndVillains
// Target function: function tokenURI(uint256 tokenId) external view returns (string memory)
 qty * price, "Wrong amount sent");

        wlmints[_msgSender()] += qty;

        for(uint256 i = 0; i < qty; i++) {
            _safeMint(_msgSender(), numberMinted + 1);
            numberMinted ++;
        }
    }

    function mintVillain(uint256 t1, uint256 t2, uint256 t3, uint256 t4) external reentryLock {
        require(villActive, "Not open to the public");
        // Validate ownership in wallet
        require(ownerOf(t1) == _msgSender(), "you don't own that");
        require(ownerOf(t2) == _msgSender(), "you don't own that");
        require(ownerOf(t3) == _msgSender(), "you don't own that");
        require(ownerOf(t4) == _msgSender(), "you don't own that");
        // Validate token Ids are the right type
        require(t1 < 1025, "Wrong Type");
        require(t2 < 1025, "Wrong Type");
        require(t3 < 1025, "Wrong Type");
        require(t4 < 1025, "Wrong Type");
        // Make the sacrifice
        _burn(t1);
        _burn(t2);
        _burn(t3);
        _burn(t4);
        // grant evolved villain
        numVillains ++;
        uint256 tokenid = numVillains + 1024;
        _safeMint(_msgSender(), tokenid);

        // update the traits that went into building this tokenid
        _burned_for_villain[tokenid] = [t1, t2, t3, t4];
    }
    
    function mintSuperVillain(uint256 v1, uint256 v2) external reentryLock {
        require(suprActive, "Not open to the public");
        // Validate Ownership
        require(ownerOf(v1) == _msgSender(), "you don't own that");
        require(ownerOf(v2) == _msgSender(), "you don't own that");
        // Validate correct type
        require( (v1 > 1024)&&(v1 < 2000), "Wrong Type");
        require( (v2 > 1024)&&(v2 < 2000), "Wrong Type");
        // Make the sacrifice
        _burn(v1);
        _burn(v2);
        // grant evolved superVillain
        numSupers ++;
        uint256 tokenid = numSupers + 2000;
        _safeMint(_msgSender(), tokenid);

        // update the traits that went into building this tokenid
        for (uint256 i; i < 8; i++){
            if (i<4){
                _burned_for_super[tokenid][i] = _burned_for_villain[v1][i];
            } else {
                _burned_for_super[tokenid][i] = _burned_for_villain[v2][i-4];
            }
        }
        
    }

    // allows holders to burn their own tokens if desired
    function burn(uint256 tokenID) external {
        require(_msgSender() == ownerOf(tokenID));
        _burn(tokenID);
    }
    
    //////////////////////////////////////////////////////////////
    //////////////////// Setters and Getters /////////////////////
    //////////////////////////////////////////////////////////////
    function getStateMachine() external view returns(uint256){
        uint256 state = 0;
        if (mintActive){
            state+=1;
        }
        if (villActive){
            state+=2;
        }
        if (suprActive){
            state+=4;
        }

        return state;
    }

    function whitelistMany(address[] memory wladdresses) external onlyOwner {
        for(uint256 i=0; i < wladdresses.length; i++){
            _whitelist[wladdresses[i]] = true;
        }
    }

    function getVillainSacrifice(uint256 tokenid) external view returns(uint256[4] memory){
        return _burned_for_villain[tokenid];
    }

    function getSuperSacrifice(uint256 tokenid) external view returns(uint256[8] memory){
        return _burned_for_super[tokenid];
    }
    
    function setBaseURI(string memory newURI) external onlyOwner {
        _baseURI = newURI;
    }

    function setSupplyLimit(uint256 newLimit) external onlyOwner {
        totalMint = newLimit;
    }
    
    function activateMint() public onlyOwner {
        mintActive = true;
    }
    
    function deactivateMint() public onlyOwner {
        mintActive = false;
    }

    function activateWhitelist() public onlyOwner {
        WLActive = true;
    }
    
    function deactivateWhitelist() public onlyOwner {
        WLActive = false;
    }
    
    function activateVillains() public onlyOwner {
        villActive = true;
    }
    
    function deactivateVillains() public onlyOwner {
        villActive = false;
    }
    
    function activateSuper() public onlyOwner {
        suprActive = true;
    }
    
    function deactivateSuper() public onlyOwner {
        suprActive = false;
    }

    function totalSupply() external view returns (uint256) {
        return numberMinted + numVillains + numSupers - numberBurned; //stupid bs for etherscan's call
    }
    
    function getBalance(address tokenAddress) view external returns (uint256) {
        //return _balances[tokenAddress]; //shows 0 on etherscan due to overflow error
        return _balances[tokenAddress] / (10**15); //temporary fix to report in finneys
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: bal qry for zero address");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: own query nonexist tkn");
        return owner;
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval current owner");

        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "ERC721: caller !owner/!approved"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved nonexistent tkn");
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(operator != msg.sender, "ERC721: approve to caller");

        _operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: txfr !owner/approved");
        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual override {
        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: txfr !owner/approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    /**
     * @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 virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "txfr to non ERC721Reciever");
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: op query nonexistent tkn");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @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,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, _data),
            "txfr to non ERC721Reciever"
        );
    }

    /**
     * @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 virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        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 virtual {
        address owner = ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];
        numberBurned ++;

        emit Transfer(owner, address(0), tokenId);
    }

    /**
     * @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 virtual {
        require(ownerOf(tokenId) == from, "ERC721: txfr token not owned");
        require(to != address(0), "ERC721: txfr to 0x0 address");
        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver(to).onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("txfr to non ERC721Reciever");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
    
    // *********************** ERC721 Token Receiver **********************
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     */
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4) {
        //InterfaceID=0x150b7a02
        return this.onERC721Received.selector;
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    // **************************************** Metadata Standard Functions **********
    //@dev Returns the token collection name.
    function name() external view returns (string memory){
        return _name;
    }

    //@dev Returns the token collection symbol.
    function symbol() external view returns (string memory){
        return _symbol;
    }