contract AdjectiveNounVerb is ERC721Enumerable, ReentrancyGuard, Ownable {
    string[] private adjectives = [
        "afraid",
        "all",
        "angry",
        "beige",
        "big",
        "bitter",
        "blue",
        "brave",
        "bumpy",
        "deep",
        "dirty",
        "solid",
        "some",
        "sour",
        "spicy",
        "spotty",
        "stale",
        "strange",
        "strong",
        "stupid",
        "tangy",
        "tasty",
        "thin",
        "thirty",
        "three",
        "tiny",
        "tired",
        "happy",
        "yellow",
        "young",
        "yummy"
    ];

    string[] private nouns = [
        "apes",
        "animals",
        "dogs",
        "baths",
        "memes",
        "otters",
        "owls",
        "pandas",
        "vitaliks",
        "panthers",
        "turkeys",
        "turtles",
        "vans",
        "tigers",
        "walruses",
        "koalas",
        "windows",
        "wolves",
        "wombats",
        "worms",
        "zoos"
    ];

    string[] private verbs = [
        "boogie",
        "snoop",
        "argue",
        "attack",
        "bake",
        "bathe",
        "battle",
        "dance",
        "beam",
        "chew",
        "clap",
        "deliver",
        "design",
        "eat",
        "greet",
        "impress",
        "invent",
        "march",
        "mate",
        "wink",
        "work",
        "twerk"
    ];

    string[] private descriptors = ["gm", "wow", "gmi", "lfg", "looks rare"];

    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }

    function getAdjectives(string memory id)
        public
        view
        returns (string memory)
    {
        return pluck(id, "ADJECTIVES", adjectives, false);
    }

    function getNouns(string memory id) public view returns (string memory) {
        return pluck(id, "NOUNS", nouns, false);
    }

    function getVerbs(string memory id) public view returns (string memory) {
        return pluck(id, "VERBS", verbs, false);
    }

    function getGreatness(string memory id)
        public
        view
        returns (string memory)
    {
        return pluck(id, "DESCRIPTOR", descriptors, true);
    }

    function getForegroundColor(string memory id, string memory keyPrefix)
        public
        view
        returns (string memory)
    {
        uint256 color = (random(string(abi.encodePacked(keyPrefix, id))) % 55) +
            200;
        bytes memory colorHexString = toHexString(color);
        colorHexString = abi.encodePacked(colorHexString[2], colorHexString[3]);
        return string(colorHexString);
    }

    function getBackgroundColor(string memory id, string memory keyPrefix)
        public
        view
        returns (string memory)
    {
        uint256 color = (random(string(abi.encodePacked(keyPrefix, id))) % 100);
        bytes memory colorHexString = toHexString(color);
        colorHexString = abi.encodePacked(colorHexString[2], colorHexString[3]);
        return string(colorHexString);
    }

    function pluck(
        string memory tokenId,
        string memory keyPrefix,
        string[] memory sourceArray,
        bool isRare
    ) internal view returns (string memory) {
        uint256 rand = random(string(abi.encodePacked(keyPrefix, tokenId)));
        string memory output = sourceArray[rand % sourceArray.length];
        if (isRare == true) {
            uint256 greatness = rand % 50;
            if (greatness > 48) {
                output = sourceArray[4];
            } else if (greatness > 45) {
                output = sourceArray[3];
            } else if (greatness > 40) {
                output = sourceArray[2];
            } else if (greatness > 33) {
                output = sourceArray[1];
            } else {
                output = sourceArray[0];
            }
        }
        return output;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        string memory id = toString(tokenId);

        string[17] memory parts;
        parts[
            0
        ] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 512 512"><style>.base { fill: #';

        parts[1] = getBackgroundColor(id, "R");

        parts[2] = getBackgroundColor(id, "G");

        parts[3] = getBackgroundColor(id, "B");

        parts[
            4
        ] = '; font-family: sans-serif; font-size: 64px; }.small { font-size: 32px }</style><rect width="100%" height="100%" fill="#';

        parts[5] = getForegroundColor(id, "R");

        parts[6] = getForegroundColor(id, "G");

        parts[7] = getForegroundColor(id, "B");

        parts[
            8
        ] = '" /><text id="adjective" x="50%" y="35%" dominant-baseline="middle" text-anchor="middle" class="base">';

        parts[9] = getAdjectives(id);

        parts[
            10
        ] = '</text><text id="noun" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" class="base">';

        parts[11] = getNouns(id);

        parts[
            12
        ] = '</text><text id="verb" x="50%" y="65%" dominant-baseline="middle" text-anchor="middle" class="base">';

        parts[13] = getVerbs(id);

        parts[
            14
        ] = '</text><text id="descriptor" x="50%" y="5%" dominant-baseline="middle" text-anchor="middle" class="base small">';

        parts[15] = getGreatness(id);

        parts[
            16
        ] = '</text><animate href="#adjective" attributeName="y" from="35%" to="20%" values="35%; 20%; 35%" dur="0.5s" begin="click" repeatCount="indefinite" fill="freeze" id="anim2"/><animate href="#noun" attributeName="x" from="30%" to="70%" values="50%; 30%; 50%; 70%; 50%;" dur="1s" begin="click" repeatCount="indefinite" fill="freeze" id="anim3"/><animate href="#verb" attributeName="y" from="65%" to="80%" values="65%; 80%; 65%;" dur="0.2s" begin="click" repeatCount="indefinite" fill="freeze" id="anim3"/><animate href="#descriptor" attributeName="x" from="50%" to="50%" values="-5%; 105%;" dur="4s" begin="click" repeatCount="indefinite" fill="freeze" id="anim"/></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": "Adjective Noun Verb ID #',
                        toString(tokenId),
                        '", "description": "Randomly generated adjectives, nouns and verbs", "image": "data:image/svg+xml;base64,',
                        Base64.encode(bytes(output)),
                        '", "attributes": [{ "trait_type": "Adjective", "value": "',
                        parts[9],
                        '" }, { "trait_type": "Noun", "value": "',
                        parts[11],
                        '" }, { "trait_type": "Verb", "value": "',
                        parts[13],
                        '" }, { "trait_type": "Greatness", "value": "',
                        parts[15],
                        '" }]}'
                    )
                )
            )
        );
        output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }

    function claim(uint256 tokenId) public nonReentrant {
        require(tokenId > 0 && tokenId < 901, "Token ID invalid");
        _safeMint(_msgSender(), tokenId);
    }

    function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
        require(tokenId > 900 && tokenId < 937, "Token ID invalid");
        _safeMint(owner(), tokenId);
    }

    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (bytes 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 (bytes 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 buffer;
    }

    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT license
        // 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);
    }

    constructor() ERC721("AdjectiveNounVerb", "ANV") Ownable() {}
}
function toHexString(uint256 value) internal pure returns (bytes 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.
 */