// Contract: Plooto
// Target function: function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner
 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();
    }
}

contract Plooto is ERC721Enumerable, ReentrancyGuard, Ownable {
    string[] private atmosphere = [
        "Hydrogen",
    "Helium",
    "Ethane",
    "Phosphine",
    "Methane",
    "Acetylene",
    "Oxygen",
    "Carbon Dioxide",
    "Nitrogen",
    "Sodium",
    "Potassium",
    "Sulfer",
    "Carbon",
    "Argon",
    "Ammonia",
    "Hydrogen Sulfide",
    "Neon",
    "Diacetylene",
    "Methylacetylene",
    "Cyanoacetylene",
    "Acetylene",
    "Propane",
    "Carbon Monoxide",
    "Cyanogen",
    "Hydrogen Cyanide",
    "Hydrogen Oxide"
    ];

    string[] private mineral = [
        "Native elements", "Oxides", "Hydroxides", "Sulfide", "Sulfates", "Carbonates", "Phosphates", "Halides", "Silicates", "Orthosilicates", "Ring silicates", "Chain silicates", "Sheet silicates", "Framework silicates"
    ];

    string[] private orbit = [
        "Inclined orbit",
    "Polar orbit",
    "Polar Sun-synchronous orbit",
    "Ecliptic orbit",
    "Equatorial orbit",
    "Near Equatorial orbit",
    "Prograde orbit",
    "Retrograde orbit",
    "Circular orbit",
    "Elliptic orbit",
    "Frozen orbit",
    "Decaying orbit"
    ];

    string[] private planetCore = [
        "Solid", "Liquid", "Hollow", "Gas", "Molten", "Superfluid", "Supersolid"
    ];

    string[] private planetType = [
        "Carbon planet",
    "Chthonian planet",
    "Desert planet",
    "Eyeball planet",
    "Helium planet",
    "Iron planet",
    "Ocean planet",
    "Ploonet",
    "Trojan planet",
    "Ice giant",
    "Ice planet",
    "Lava planet",
    "Protoplant",
    "Puffy planet",
    "Silicate planet",
    "Terrestrial planet",
    "Circumbinary planet",
    "Double planet",
    "Eccentric gas giant",
    "Exoplanet",
    "Goldilocks planet",
    "Inner planet",
    "Outer planet",
    "Pulsar planet",
    "Rogue planet",
    "Mesoplanet",
    "Planetar"
    ];

    string[] private population = [
        "Extinct", "Primitive", "Mechanical", "Singularity", "Humanoid", "Xokhokka", "Vurbosso", "Loppix", "Ajiia", "Serruf", "Heirdos", "Konka", "Myrion"
    ];

    string[] private solarsystem = ["Strix",
    "Yed Prior S",
    "Kitt W",
    "Sirius X",
    "Helios",
    "Bellatrix T",
    "Ioxa",
    "Aedon",
    "Tartarus",
    "Aeetes",
    "Leda",
    "Latona",
    "Griffin",
    "Ypsolaria",
    "Kroejtjek",
    "Hossagri",
    "Meleager",
    "Pelops",
    "Magnum H",
    "Polyxena",
    "Marsyas",
    "Phaedra",
    "Poseido",
    "Pleiades",
    "Narcissus",
    "Stheno",
    "Thoon",
    "Periboea",
    "Tityos",
    "Gaia",
    "Chaos",
    "Yed Prior S",
    "Oenoe",
    "Achlys",
    "Gadflies",
    "Nemea",
    "Persephone",
    "Celaeno",
    "Empusa",
    "Faunus",
    "TCS U",
    "Icarus",
    "Daedalus",
    "Furud N",
    "Rhadamanthus",
    "Atreus",
    "Hellhound",
    "Pan",
    "Furies",
    "Syrma V",
    "Zosma Y",
    "Hylonome",
    "Pygmalion",
    "Atalanta"];

    string[] private star = [
        "Hot Blue Star",
    "Red Dwarf Star",
    "Red Giant Star",
    "Yellow Dwarf Star",
    "Orange Dwarf Star",
    "Red Dwarf Star",
    "White Dwarf Star",
    "Brown Dwarf Star",
    "Black Dwarf Star",
    "Blue Giant Star",
    "Blue Supergiant Star",
    "Red Giant Star",
    "Red Supergiant Star",
    "Neutron Star",
    "Black Hole",
    "Pulsar",
    "Black Star",
    "Boson Star",
    "Dark Energy Star",
    "Dark Matter Star",
    "Dark Star",
    "Electroweak Star",
    "Gravastar",
    "Hyperon Star",
    "Planck Star",
    "Quark Star",
    "Quasi-Star",
    "Strange Star",
    "White Hole"
    ];

    string[] private prefixes = [
        "Astral", "Astronomical", "Celestial", "Cosmic", "Extraterrestrial", "Galactic", "Intergalactic", "Interstellar", "Lunar", "Martian", "Meteoric", "Sidereal", "Stellar", "Magical"
    ];

    string[] private namePrefixes = [
        "Temporary",
        "Permanent",
        "Cyber",
        "Secret"

    ];

    string[] private nameSuffixes = [
        "Level I", "Level II", "Level III", "Level IV", "Level V"
    ];

    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }

    function getAtmosphere(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "ATMOSPHERE", atmosphere);
    }

    function getMineral(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "MINERAL", mineral);
    }

    function getOrbit(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "ORBIT", orbit);
    }

    function getPlanetCore(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "PLANET CORE", planetCore);
    }

    function getPlanetType(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "PLANET TYPE", planetType);
    }

    function getPopulation(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "POPULATION", population);
    }

    function getSolarsystem(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "SOLARSYSTEM", solarsystem);
    }

    function getStar(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "STAR", star);
    }

    function pluck(
        uint256 tokenId,
        string memory keyPrefix,
        string[] memory sourceArray
    ) internal view returns (string memory) {
        uint256 rand = random(
            string(abi.encodePacked(keyPrefix, toString(tokenId)))
        );
        string memory output = sourceArray[rand % sourceArray.length];
        uint256 greatness = rand % 21;
        if (greatness > 14) {
            output = string(
                abi.encodePacked(prefixes[rand % prefixes.length], " ", output)
            );
        }
        if (greatness >= 19) {
            string[2] memory name;
            name[0] = namePrefixes[rand % namePrefixes.length];
            name[1] = nameSuffixes[rand % nameSuffixes.length];
            if (greatness == 19) {
                output = string(
                    abi.encodePacked('"', name[0], " ", name[1], '" ', output)
                );
            } else {
                output = string(
                    abi.encodePacked(
                        '"',
                        name[0],
                        " ",
                        name[1],
                        '" ',
                        output,
                        " +1"
                    )
                );
            }
        }
        return output;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        string[17] memory parts;
        parts[
            0
        ] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 420 420"><style>.base { fill: white; font-family: monospace; font-size: 14px; }</style><rect width="100%" height="100%" fill="#222222" /><text x="10" y="20" class="base">';

        parts[1] = getAtmosphere(tokenId);

        parts[2] = '</text><text x="10" y="40" class="base">';

        parts[3] = getMineral(tokenId);

        parts[4] = '</text><text x="10" y="60" class="base">';

        parts[5] = getOrbit(tokenId);

        parts[6] = '</text><text x="10" y="80" class="base">';

        parts[7] = getPlanetCore(tokenId);

        parts[8] = '</text><text x="10" y="100" class="base">';

        parts[9] = getPlanetType(tokenId);

        parts[10] = '</text><text x="10" y="120" class="base">';

        parts[11] = getPopulation(tokenId);

        parts[12] = '</text><text x="10" y="140" class="base">';

        parts[13] = getSolarsystem(tokenId);

        parts[14] = '</text><text x="10" y="160" class="base">';

        parts[15] = getStar(tokenId);

        parts[16] = "</text></svg>";

        string memory output = string(
            abi.encodePacked(
                parts[0],
                parts[1],
                parts[2],
                parts[3],
                parts[4],
                parts[5],
                parts[6],
                parts[7],
                parts[8]
            )
        );
        output = string(
            abi.encodePacked(
                output,
                parts[9],
                parts[10],
                parts[11],
                parts[12],
                parts[13],
                parts[14],
                parts[15],
                parts[16]
            )
        );

        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "Planet #',
                        toString(tokenId),
                        '", "description": "Plooto is an on-chain factory to create unique planets protected by the Guardians DAO. Feel free to use your planets in any way you want.", "image": "data:image/svg+xml;base64,',
                        Base64.encode(bytes(output)),
                        '"}'
                    )
                )
            )
        );
        output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }

    function claim(uint256 tokenId) public nonReentrant payable {
        require(tokenId > 0 && tokenId < 8000, "Token ID invalid");
        require(msg.value == 0.02 ether, "You need to send 0.02 Ether"); // Cost 0.02 ETH per claim()
        _safeMint(_msgSender(), tokenId);
    }
    
    function withdraw() onlyOwner public {
      payable(msg.sender).transfer(address(this).balance);
    }