// Contract: cc0py
// Target function: function copyNFT(uint256 collectionID, uint256 id) public
 `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 {}
}

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}

/**
 * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
 * enumerability of all the token ids in the contract as well as all token ids owned by each
 * account.
 */
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
    // Mapping from owner to list of owned token IDs
    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) private _ownedTokensIndex;

    // Array with all token ids, used for enumeration
    uint256[] private _allTokens;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) private _allTokensIndex;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

    /**
     * @dev See {IERC721Enumerable-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _allTokens.length;
    }

    /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @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` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * 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 override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}


/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    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);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
}

    struct Collection {
        address token;
        string name;
        string proof;
        uint128 count;
        uint64 testMints;
        bool open;
    }

contract cc0py is ERC721, ERC721Enumerable, Ownable {

    uint256 constant public maxTestMints = 4;
    uint256 constant public maxNFTs = 6969;
    uint256 constant public maxPerCollection = 1111;
    uint256 public maxPerAddress = 2;

    Collection[] collections;

    mapping(address => uint256) public mintsByAddress;

    string private _contractURI = "data:application/json;base64,ewogICJuYW1lIjogImNjMHB5IiwKICAiZGVzY3JpcHRpb24iOiAibm8gbWludCBjb3N0LCBubyBmZWVzLCBubyBmdXR1cmUgcHJvbWlzZXMuIHNwcmVhZCB0aHJvdWdoIGRpc2NvcmQsIGNsYWltcyBsaW1pdGVkIHRvIHR3byBwZXIgYWRkcmVzcy4gY2MwcHkgbWFuaWZlc3RvIGF2YWlsYWJsZSBhcyBhIHJlYWQgZnVuY3Rpb24gb24gdGhlIGNvbnRyYWN0LiIsCiAgImltYWdlIjogImlwZnM6Ly9RbVNtYUF5NVAzQTN4bmE2TVlkNTNwcEhxeG05QTZZNWRjN0JMdW1oWnRHbzRoIiwKICAic2VsbGVyX2ZlZV9iYXNpc19wb2ludHMiOiAwLAogICJmZWVfcmVjaXBpZW50IjogIjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIKfQ==";
    string public manifesto = "https://ipfs.io/ipfs/QmcEGGCYAVyQkQwhYXDG6n7zqFkdE3QEbJqrJvZmV7s7eD";

    constructor() ERC721("cc0py", "cc0py")  {
        _addNewCC0(0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03,"Nouns","https://ipfs.io/ipfs/QmUEbNwuLqd8P8WwSpuB5kqcdjbb3W3tpVh73PXr9HSUZx");
        _addNewCC0(0xd652d2633CDBfD5f27F50cdDb098e708fa8433f3,"XCOPY","https://ipfs.io/ipfs/QmTrAT3DAWNjbvwu84p8pH2UD1jEaAYaBosww3ZL2x8rqz");        
        _addNewCC0(0x23581767a106ae21c074b2276D25e5C3e136a68b,"Moonbirds","https://ipfs.io/ipfs/QmWRv8ZdgVQgVVjn2Bq9ndSN6KXiQTRLD5wFrqgtyEykqt");
        _addNewCC0(0xc143bbfcDBdBEd6d454803804752a064A622C1F3,"Grifters by XCOPY","https://ipfs.io/ipfs/QmbJ5tLsp9UpD7ySvW5wmWbtzfjfNkrnzfyfyjjRsjfTQ4");  
        _addNewCC0(0x42069ABFE407C60cf4ae4112bEDEaD391dBa1cdB,"CryptoDickbutts","https://ipfs.io/ipfs/QmSgBftSWazCMavEU5hFXoXGcjU7WztaXHNmtNdvRiZfjz");              
        _addNewCC0(0x1CB1A5e65610AEFF2551A50f76a87a7d3fB649C6,"CrypToadz by GREMPLIN","https://ipfs.io/ipfs/QmaDhapUHR5zfV4uJryAR5WBwQdsf8DUUZxiQoceSBa41L");
        _addNewCC0(0xbCe3781ae7Ca1a5e050Bd9C4c77369867eBc307e,"goblintown.wtf","https://ipfs.io/ipfs/QmQRiDKZ4XaH866yrCXexDp3AegV1CLMaCox5tWZmuVT8m");
        _addNewCC0(0x79FCDEF22feeD20eDDacbB2587640e45491b757f,"mfers","https://ipfs.io/ipfs/QmWW5XfBm2yfkyHMW4bv5bDB4MY3y9rLdxhTCZMorHXjgc");
    }

    function contractURI() public view returns (string memory) {
        return string(_contractURI);
    }

    function setContractURI(string memory newContractURI) external onlyOwner {
        _contractURI = newContractURI;
    }

    function _addNewCC0(address newContract, string memory name, string memory cc0Proof) internal {
        Collection memory collection;
        collection.token = newContract;
        collection.name = name;
        collection.proof = cc0Proof;
        collections.push(collection);
    }

    function testMint(uint256 collectionID, uint256 id) public onlyOwner {
        collections[collectionID].testMints++;
        require(collections[collectionID].testMints <= maxTestMints);

        _copyNFT(collectionID, id);
    }

    function togglePublic(uint256 collectionID, bool isOpen) public onlyOwner {
        collections[collectionID].open = isOpen;
    }