// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@1001-digital/erc721-extensions/contracts/RandomlyAssigned.sol";

contract SpermGame is ERC721, Ownable, RandomlyAssigned {
    using Strings for uint;
    using ECDSA for bytes32;

    uint public immutable MAX_TOKENS;
    uint public immutable PUBLIC_MINT_COST = 60000000000000000; // 0.06 Ether
    uint public immutable PRESALE_MINT_COST = 44000000000000000; // 0.044 Ether
    uint internal immutable MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    string public constant PROVENANCE_HASH = "F7A2C002932960FADC377711441ADA7ABB4B32454852BF016027BA5F8185036C";

    string private baseURI;
    string private wrappedBaseURI;

    bool isRevealed;
    bool publicMintAllowed;

    address private operatorAddress;

    mapping(bytes32 => bool) public executed;

    uint[] public medalledTokenIds;

    constructor(
        string memory initialURI,
        uint _MAX_TOKENS)
    ERC721("Sperm Game", "SG")
    RandomlyAssigned(_MAX_TOKENS, 0) {
        isRevealed = false;
        publicMintAllowed = false;
        baseURI = initialURI;
        MAX_TOKENS = _MAX_TOKENS;
        operatorAddress = msg.sender;
        medalledTokenIds = new uint[]((_MAX_TOKENS / 256) + 1);
    }

    function mint(uint num) external payable ensureAvailabilityFor(num) {
        require(publicMintAllowed, "Public minting is not open");
        require(msg.value >= num * PUBLIC_MINT_COST, "Mint cost is 0.06 ETH per token");

        uint tokenId;
        for (uint i = 0; i < num; i++) {
            tokenId = nextToken();
            _safeMint(msg.sender, tokenId);
        }
    }

    function allowlistMint(uint num, uint nonce, bytes calldata signature) external payable ensureAvailabilityFor(num) {
        verifyAllowlistMint(msg.sender, num, nonce, signature);
        require(msg.value >= num * PRESALE_MINT_COST, "Mint cost is 0.044 ETH per token");

        uint tokenId;
        for (uint i = 0; i < num; i++) {
            tokenId = nextToken();
            _safeMint(msg.sender, tokenId);
        }
    }

    function devMint(uint num, uint nonce, uint rand, bytes calldata signature) external payable ensureAvailabilityFor(num) {
        verifyDevMint(msg.sender, num, nonce, rand, signature);

        uint tokenId;
        for (uint i = 0; i < num; i++) {
            tokenId = nextToken();
            _safeMint(msg.sender, tokenId);
        }
    }

    function claimMedal(uint[] calldata tokenIds, bytes[] calldata signatures) external {
        require(tokenIds.length == signatures.length, "Must have one signature per tokenId");
        for (uint i = 0; i < tokenIds.length; i++) {
            require(ownerOf(tokenIds[i]) == msg.sender, "Must be owner of the tokenId to claim medal");
            verifyTokenInFallopianPool(tokenIds[i], signatures[i]);
            setMedalled(tokenIds[i]);
        }
    }

    function unclaimMedal(uint[] calldata tokenIds) external {
        for (uint i = 0; i < tokenIds.length; i++) {
            require(ownerOf(tokenIds[i]) == msg.sender, "Must be owner of the tokenId to unclaim medal");
            unsetMedalled(tokenIds[i]);
        }
    }

    function isValidSignature(bytes32 hash, bytes calldata signature) internal view returns (bool isValid) {
        bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
        return signedHash.recover(signature) == operatorAddress;
    }

    function verifyAllowlistMint(address wallet, uint num, uint nonce, bytes calldata signature) internal {
        bytes32 msgHash = keccak256(abi.encodePacked(wallet, num, nonce));
        require(!executed[msgHash], "Transaction with this msgHash already executed");
        require(isValidSignature(msgHash, signature), "Invalid signature");
        executed[msgHash] = true;
    }

    function verifyDevMint(address wallet, uint num, uint nonce, uint rand, bytes calldata signature) internal {
        bytes32 msgHash = keccak256(abi.encodePacked(wallet, num, nonce, rand));
        require(!executed[msgHash], "Transaction with this msgHash already executed");
        require(isValidSignature(msgHash, signature), "Invalid signature");
        executed[msgHash] = true;
    }

    function verifyTokenInFallopianPool(uint tokenId, bytes calldata signature) internal view {
        bytes32 msgHash = keccak256(abi.encodePacked(tokenId));
        require(isValidSignature(msgHash, signature), "Invalid signature");
    }

    function tokenURI(uint tokenId) public view override returns (string memory) {
        if (isRevealed && !isMedalled(tokenId)) {
            return string(abi.encodePacked(baseURI, "/", tokenId.toString()));
        } else if (isRevealed && isMedalled(tokenId)) {
            return string(abi.encodePacked(wrappedBaseURI, "/", tokenId.toString()));
        } else {
            return string(abi.encodePacked(baseURI));
        }
    }

    function setTokenURI(string calldata _baseURI) external onlyOwner {
        baseURI = _baseURI;
    }

    function setWrappedBaseTokenURI(string calldata _wrappedBaseURI) external onlyOwner {
        wrappedBaseURI = _wrappedBaseURI;
    }

    function setOperatorAddress(address _address) external onlyOwner {
        operatorAddress = _address;
    }

    function togglePublicMintingAllowed() external onlyOwner {
        publicMintAllowed = !publicMintAllowed;
    }

    function toggleReveal() external onlyOwner {
        isRevealed = !isRevealed;
    }

    function withdraw() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }

    function isMedalled(uint tokenId) public view returns (bool) {
        uint[] memory bitMapList = medalledTokenIds;
        uint Y = tokenId / 256;
        uint partition = bitMapList[Y];
        if (partition == MAX_INT) {
            return true;
        }
        uint X = tokenId % 256;
        uint bit = partition & (1 << X);
        return (bit != 0);
    }

    function setMedalled(uint tokenId) internal {
        uint[] storage bitMapList = medalledTokenIds;
        uint Y = tokenId / 256;
        uint partition = bitMapList[Y];
        uint X = tokenId % 256;
        bitMapList[Y] = partition | (1 << X);
    }

    function unsetMedalled(uint tokenId) internal {
        uint[] storage bitMapList = medalledTokenIds;
        uint Y = tokenId / 256;
        uint partition = bitMapList[Y];
        uint X = tokenId % 256;
        bitMapList[Y] = partition & (0 << X);
    }
}
[{"inputs":[{"internalType":"string","name":"initialURI","type":"string"},{"internalType":"uint256","name":"_MAX_TOKENS","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"supply","type":"uint256"}],"name":"SupplyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"MAX_TOKENS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRESALE_MINT_COST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROVENANCE_HASH","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_MINT_COST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"allowlistMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"availableTokenCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"name":"claimMedal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"rand","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"devMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"executed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isMedalled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"medalledTokenIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"setOperatorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_wrappedBaseURI","type":"string"}],"name":"setWrappedBaseTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"togglePublicMintingAllowed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleReveal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"unclaimMedal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]