// Contract: SpaceCapsules
// Target function: function testMintMany(uint amount_) public
    }

    /**
     * @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();
    }
}

interface iFont {
    function fontBase64() external view returns (string memory);
}

contract SpaceCapsules is ERC721Enumerable, Ownable {
    
    constructor() ERC721("Space Capsules", "SCAP") {}

    address public fontAddress;
    string public fontSize = "11px";
    
    function setFontAddress(address address_) external onlyOwner {
        fontAddress = address_;
    }
    function fontBase64() internal view returns (string memory) {
        return iFont(fontAddress).fontBase64();
    }
    function setFontSize(string memory string_) external onlyOwner {
        fontSize = string_;
    }

    string[] private weapons = [
        "Tesseract",
        "Hadron Collider",
        "Synchrotron Collider",
        "Cyclotron Collider",
        "Omni Beam Cannon",
        "Light Saber",
        "Cybernetic Blade",
        "Plutonium Blade",
        "Uranium Blade",
        "Mercury Blade",
        "Gravity Gun",
        "Plasma Gun",
        "Fusion Gun",
        "Ray Gun",
        "Galaxy Crystal",
        "Memory Crystal",
        "Access Crystal",
        "Mineral Sample"
    ];
    
    string[] private chest = [
        "Alien Exo-Torso",
        "Alloy Exo-Torso",
        "Carbon Exo-Torso",
        "Cloaking Chest",
        "Carapace Chest",
        "Alien Conduit Chest",
        "Cybernetic Chest",
        "Plutonium Clamshell",
        "Uranium Clamshell",
        "Biogenic Clamshell",
        "Plasteel Suit",
        "Composite Suit",
        "Alloy Chest Rig",
        "Ceramic Chest Rig",
        "Chest Rig"
    ];
    
    string[] private head = [
        "Alien Neural Implant",
        "Cyberbrain Implant",
        "Plasma Mask Implant",
        "Cloaking Mask Implant",
        "Facial Implant",
        "Alien Conduit Eyes",
        "Cybernetic Eye",
        "Respirator Mask",
        "Biogenic Mask",
        "Alloy Mask",
        "Carbon Helmet",
        "Plasteel Helmet",
        "Composite Helmet",
        "Alloy Helmet",
        "Helmet"
    ];
    
    string[] private legs = [
        "Alien Exo-Legs",
        "Alloy Exo-Legs",
        "Carbon Exo-Legs",
        "Cloaking Legs",
        "Carapace Legs",
        "Alien Conduit Legs",
        "Cybernetic Leg",
        "Plutonium Leg Plate",
        "Uranium Leg Plate",
        "Biogenic Leg Plate",
        "Plasteel Guard",
        "Composite Guard",
        "Alloy Guard",
        "Kevlar Guard",
        "Guard"
    ];
    
    string[] private vehicle = [
        "Blackhole Transporter",
        "Quantum Transporter",
        "Light-Speed Transporter",
        "Warp Drive Shuttle",
        "Invisible Shuttle",
        "Alien Space Ship",
        "Anti-Gravity Space Ship",
        "Plasma-Fuel Rocket Ship",
        "Ion-Fuel Rocket Ship",
        "Liquid-Fuel Rocket Ship",
        "Solid-Fuel Rocket Ship",
        "Weaponized Space Ship",
        "Space Ship",
        "Space Jet",
        "Observation Ship"
    ];
    
    string[] private arms = [
        "Alien Exo-Arms",
        "Alloy Exo-Arms",
        "Carbon Exo-Arms",
        "Cloaking Arms",
        "Carapace Arms",
        "Alien Conduit Arms",
        "Cybernetic Arm",
        "Plutonium Arm Plate",
        "Uranium Arm Plate",
        "Biogenic Arm Plate",
        "Plasteel Gloves",
        "Composite Gloves",
        "Alloy Gloves",
        "Kevlar Gloves",
        "Gloves"
    ];
    
    string[] private artifacts = [
        "Alien Artifact",
        "A.I. Drone",
        "Rock Sample"
    ];
    
    string[] private rings = [
        "Alien Ring",
        "Cybernetic Ring",
        "Copper Ring",
        "Plasteel Ring",
        "Alloy Ring"
    ];
    
    string[] private suffixes = [
        "of Energy",
        "of Beings",
        "of Stars",
        "of Skill",
        "of Perfection",
        "of Brilliance",
        "of Enlightenment",
        "of Protection",
        "of Anger",
        "of Rage",
        "of Fury",
        "of Destruction",
        "of the Light",
        "of Detection",
        "of Reflection",
        "of the Adalians"
    ];
    
    string[] private namePrefixes = [
        "Martian",
        "Terrestrial", 
        "Ethereal",
        "Inertia",
        "Impact",
        "Ideator",
        "Halo",
        "Gateway",
        "Exodus",
        "Eternity",
        "Empath",
        "Charged",
        "Atomic",
        "Asterite",
        "Ansible", 
        "Solstice",
        "Quasar",
        "Pulsar",
        "Orbit",
        "Metal",
        "Gamma",
        "Equinox",
        "Dark Matter",
        "Crater",
        "Chaos",
        "Aurora",
        "Comet",
        "Eclipse",
        "Meteor",
        "Nebula",
        "Phase",
        "Reflector",
        "Solar",
        "Supernova", 
        "Terminator",
        "Twilight",
        "Waning",
        "Waxing",
        "Zodiac",
        "Zenith",
        "Comet",
        "Zone"
    ];
    
    string[] private nameSuffixes = [
        "Flow",
        "Collide",
        "Mars",
        "Frequency",
        "Split",
        "Break",
        "Twist",
        "Glow",
        "Deflect",
        "Shadow",
        "Silence",
        "Crack",
        "Refract",
        "Tear",
        "Bend",
        "Form",
        "Sun",
        "Moon"
    ];

    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }
    
    function getWeapon(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "WEAPONS", weapons);
    }
    
    function getChest(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "CHEST", chest);
    }
    
    function getHead(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "HEAD", head);
    }
    
    function getLegs(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "LEGS", legs);
    }

    function getVehicle(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "VEHICLE", vehicle);
    }
    
    function getArms(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "ARMS", arms);
    }
    
    function getArtifact(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "ARTIFACTS", artifacts);
    }
    
    function getRing(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "RINGS", rings);
    }
    
    function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal view returns (string memory) {
        uint256 rand = random(string(abi.encodePacked(keyPrefix, Strings.toString(tokenId))));
        string memory output = sourceArray[rand % sourceArray.length];
        uint256 greatness = rand % 21;
        if (greatness > 14) {
            output = string(abi.encodePacked(output, " ", suffixes[rand % suffixes.length]));
        }
        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;
    }

    string public tokenName = "Space Capsule";
    string public tokenDescription = "Space Capsules are beamed down from space and contain space gear for your adventure into space exploration and colonization. They are obtained from contributing to the Message to Martians. https://messagetomartians.com";

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        string[17] memory parts;
        parts[0] = string(abi.encodePacked("data:image/svg+xml;utf8, <svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 350 350'><style>@font-face{font-family:'whatever'; src:url('data:application/octet-stream;base64,", fontBase64() ,"')}.base{font-family: 'whatever'; font-size:", fontSize, "; fill: white; }</style><rect width='100%' height='100%' fill='black' /><text x='10' y='20' class='base'>"));

        parts[1] = getWeapon(tokenId);

        parts[2] = "</text><text x='10' y='40' class='base'>";

        parts[3] = getChest(tokenId);

        parts[4] = "</text><text x='10' y='60' class='base'>";

        parts[5] = getHead(tokenId);

        parts[6] = "</text><text x='10' y='80' class='base'>";

        parts[7] = getLegs(tokenId);

        parts[8] = "</text><text x='10' y='100' class='base'>";

        parts[9] = getVehicle(tokenId);

        parts[10] = "</text><text x='10' y='120' class='base'>";

        parts[11] = getArms(tokenId);

        parts[12] = "</text><text x='10' y='140' class='base'>";

        parts[13] = getArtifact(tokenId);

        parts[14] = "</text><text x='10' y='160' class='base'>";

        parts[15] = getRing(tokenId);

        parts[16] = "</text></svg>";

        string memory attributes = string(abi.encodePacked('"attributes":[{"trait_type": "weapon", "value":"', parts[1], '"},{"trait_type": "chest", "value": "', parts[3], '"},{"trait_type": "head", "value": "', parts[5], '"},{"trait_type": "legs", "value": "', parts[7], '"},{"trait_type": "vehicle", "value": "', parts[9], '"},'));
        attributes = string(abi.encodePacked(attributes, '{"trait_type": "arms", "value": "', parts[11], '"},{"trait_type": "artifact", "value": "', parts[13], '"},{"trait_type": "ring", "value": "', parts[15], '"}]'));
        
        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 = string(abi.encodePacked('data:application/json;utf8, {"image": "', output, '",' '"name": "', tokenName,' #', Strings.toString(tokenId), '", "description": "', tokenDescription,'",', attributes, '}'));

        return json;
    }
    
    address public minter;
    
    modifier onlyMinter() {
        require(msg.sender == minter, "You are not the chosen one!");
        _;
    }
    
    function setMinter(address address_) external onlyOwner {
        minter = address_;
    }

    // function testMint() public {
    //     _safeMint(msg.sender, totalSupply());
    // }