contract Name is ERC721Enumerable, ReentrancyGuard, Ownable {
    using strings for string;
    using strings for strings.slice;

    ERC721 public constant loot = ERC721(0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7);
    IERC20 public constant agld = IERC20(0x32353A6C91143bfd6C7d363B546e62a9A2489A20);
    uint256 private constant NAME_CHANGE_BASE_PRICE = 420 * 10**18;
    
    string private constant prefixes = "Master,Right Honourable,Senator,President,Generalissimo,His Majesty,Her Majesty,His Grace,Her Grace,Lord,Archbishop,Bishop,Council,Emperor,Empress,Caesar,Chieftain,Chief Ape,Couch,Pope,King,Queen,Dr.,Ser,Prophet,Professor,Marshal,Private,Comrade,Fren,Druid,Imperator,Doge,Archon,Lord Protector,Archduke,Duke,Earl,Count,Baron,Basileus,Khan,Earth-Shaker,Mayor,Viceroy,Tribune,Chad,Cringe,Based,0x,Mohandas,Arch-ape,General Financier,DAO Dictator,Benevolent Dictator,Secretary of the Treasury,Khal,Khaleesi,Sultan";
    uint256 private constant prefixesLength = 59;

    string private constant firstNames = "Satoshi,Vitalik,Vlad,Adam,Ailmar,Darfin,Jhaan,Zabbas,Neldor,Gandor,Bellas,Daealla,Nym,Vesryn,Angor,Gogu,Malok,Rotnam,Chalia,Astra,Fabien,Orion,Quintus,Remus,Rorik,Sirius,Sybella,Azura,Dorath,Freya,Ophelia,Yvanna,Zeniya,James,Robert,John,Michael,William,David,Richard,Joseph,Thomas,Charles,Mary,Patricia,Jennifer,Linda,Elizabeth,Barbara,Susan,Jessica,Sarah,Karen,Dilibe,Eva,Matthew,Bolethe,Polycarp,Ambrogino,Jiri,Chukwuebuka,Chinonyelum,Mikael,Mira,Aniela,Samuel,Isak,Archibaldo,Chinyelu,Kerstin,Abigail,Olympia,Grace,Nahum,Elisabeth,Serge,Sugako,Patrick,Florus,Svatava,Ilona,Lachlan,Caspian,Filippa,Paulo,Darda,Linda,Gradasso,Carly,Jens,Betty,Ebony,Dennis,Martin Davorin,Laura,Jesper,Remy,Onyekachukwu,Jan,Dioscoro,Hilarij,Rosvita,Noah,Patrick,Mohammed,Chinwemma,Raff,Aron,Miguel,Dzemail,Gawel,Gustave,Efraim,Adelbert,Jody,Mackenzie,Victoria,Selam,Jenci,Ulrich,Chishou,Domonkos,Stanislaus,Fortinbras,George,Daniel,Annabelle,Shunichi,Bogdan,Anastazja,Marcus,Monica,Martin,Yuukou,Harriet,Geoffrey,Jonas,Dennis,Hana,Abdelhak,Ravil,Patrick,Karl,Eve,Csilla,Isabella,Radim,Thomas,Faina,Rasmus,Alma,Charles,Chad,Zefram,Hayden,Joseph,Andre,Irene,Molly,Cindy,Su,Stani,Ed,Janet,Cathy,Kyle,Zaki,Belle,Bella,Jessica,Amou,Steven,Olgu,Eva,Ivan,Vllad,Helga,Anya,John,Rita,Evan,Jason,Donald,Tyler,Changpeng,Sam";
    uint256 private constant firstNamesLength = 186;

    string private constant middleNames = "von,de la,chadde,mise,of,da,from,in,first of,sixth of,t11s,hi tuba,vibes,mons,zef,state,sump,sunarto,jai,mewny,amogsus,light,groovy,formerly";
    uint256 private constant middleNamesLength = 24;

    string private constant lastNames = "Nakamoto,Buterin,Zamfir,Mintz,Ashbluff,Marblemaw,Bozzelli,Fellowes,Windward,Yarrow,Yearwood,Wixx,Humblecut,Dustfinger,Biddercombe,Kicklighter,Vespertine,October,Gannon,Collymore,Stoll,Adler,Huxley,Ledger,Hayes,Ford,Finnegan,Beckett,Zimmerman,Crassus,Hendrix,Lennon,Thatcher,St. James,Cromwell,Monroe,West,Langley,Cassidy,Lopez,Jenkins,Udobata,Valova,Gresham,Frederiksen,Vasiliev,Mancini,Danicek,Okwuoma,Chibugo,Broberg,Strozak,Borkowska,Araujo,Geisler,Hidalgo,Ibekwe,Schmidt,Leehy,Rodrigue,Hines,Izmaylov,Egede,Pinette,Hakugi,McLellan,Mailhot,Lelkova,Simon,Tjangamarra,Sandgreen,Nystrom,Kjeldsen,Goncalves,Sos,Hornblower,Pelletier,Donaldson,Jackson,Rojo,Ermakov,Stornik,Lothran,Gousse,Henrichon,Onwuka,Horak,Elizondo,Mikulanc,Skotnik,Berg,Nilsson,Berg,Enyinnaya,Hermanns,Holmberg,Oliveira,Kufersin,Kwiatkowski,Courtois,Piest,Sandheaver,Woods,Ives,Dias,Grizelj,Viragh,Blau,Kodou,Torma,Sorokina,Took-Took,Allen,Melo,Bunker,Kiyomizu,Donkervoort,Maciejewska,Steffensen,Solomina,Zidek,Gotou,Bryant,Quenneville,Karlsen,Thomsen,Havlikova,Feron,Bazhenov,Amsel,Enoksen,Schneider,Kiss,Woodd,Benes,Probst,Aliyeva,Fleischer,Plain,Hoskinson,Chad,Maki,Gandhi,Zhao,Wintermute,Cronje,Felten,Yellen,Wood,Zhu,Davis,K,Delphine,Thorne,Kulechov,Nigiri,Goldfeder,Ranth,Galt,Lincoln,Trump";
    uint256 private constant lastNamesLength = 161;

    string private constant suffixes = "the Great,Jr.,Sr.,the Ape,the Magnificent,the Impaler,the Able,the Ambitious,the Astrologer,the Bad,the Bastard,the Black,the Blessed,the Bloody,the Conqueror,the Cruel,the Damned,Dracula,the Drunkard,the Elder,the Eloquent,the Enlightened,the Fair,the Farmer,the Fat,the Fearless,the Fighter,the Comfy,the Couch,the Fortunate,the Generous,the Gentle,the Glorious,the Good,the God-Given,the Grim,the Handsome,the Hammer,Hadrada,the Hidden,the Holy,the Hunter,the Illustrious,the Invincible,the Iron,the Just,the Kind,the Lame,the Last,the Lawgiver,the Learned,the Liberator,the Lion,the Mad,the Magnanimous,the Mighty,the Monk,the Mild,the Musician,the Navigator,the Nobel,the Old,the One-Eyed,the Outlaw,the Pale,the Peaceful,the Philosopher,the Pilgrim,the Pious,the Poet,the Proud,the Quiet,the Rash,the Red,the Reformer,the Saint,the Savior,the Seer,the Short,the Silent,the Simple,the Sorcerer,the Strong,the Tall,the Terrible,the Thunderbolt,the Trembling,the Tyrant,the Unlucky,the Unready,the Vain,the Virgin,the Warrior,the Weak,the White,the Wicked,the Wise,the Young,the Cuck,the Chad,the NoCoiner,.eth,da gay,the Prophet,the Paper-Handed";
    uint256 private constant suffixesLength = 105;

    mapping(uint256 => string) internal firstNameOverride;
    mapping(uint256 => string) internal lastNameOverride;
    mapping(uint256 => uint256) public numNameChanges;

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

    function getPrefix(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "PREFIX", prefixes, prefixesLength, true);
    }
    
    function getFirstName(uint256 tokenId) public view returns (string memory) {
        if (numNameChanges[tokenId] > 0) {
            return firstNameOverride[tokenId];
        }
        return pluck(tokenId, "FIRST_NAME", firstNames, firstNamesLength, false);
    }
    
    function getMiddleName(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "MIDDLE_NAME", middleNames, middleNamesLength, true);
    }
    
    function getLastName(uint256 tokenId) public view returns (string memory) {
        if (numNameChanges[tokenId] > 0) {
            return lastNameOverride[tokenId];
        }
        return pluck(tokenId, "LAST_NAME", lastNames, lastNamesLength, false);
    }

    function getSuffix(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "SUFFIX", suffixes, suffixesLength, true);
    }

    function pluck(uint256 tokenId, string memory keyPrefix, string memory sourceCSV, uint256 sourceCSVLength, bool rareTrait) internal pure returns (string memory) {
        uint256 rand = random(string(abi.encodePacked(keyPrefix, Strings.toString(tokenId))));
        if (!rareTrait || shouldGib(tokenId, keyPrefix)) {
            return getItemFromCSV(sourceCSV, rand % sourceCSVLength);
        } else {
            return "";
        }
    }
    
    function shouldGib(uint256 tokenId, string memory keyPrefix) internal pure returns (bool) {
        uint256 rand = random(string(abi.encodePacked("SHOULD_GIB", keyPrefix, Strings.toString(tokenId))));
        uint256 greatness = rand % 21;
        return (greatness >= 19);
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        string[11] memory parts;

        parts[0] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';

        parts[1] = getPrefix(tokenId);

        parts[2] = bytes(parts[1]).length > 0 ? ' ' : '';

        parts[3] = getFirstName(tokenId);

        parts[4] = ' ';

        parts[5] = getMiddleName(tokenId);

        parts[6] = bytes(parts[5]).length > 0 ? ' ' : '';

        parts[7] = getLastName(tokenId);
        
        parts[9] = getSuffix(tokenId);

        parts[8] = bytes(parts[9]).length > 0 ? ' ' : '';

        parts[10] = '</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]));
        
        string memory fullName = string(abi.encodePacked(parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8], parts[9]));
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "', fullName, '", "description": "Name is randomized adventurer name generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret. Feel free to use Name 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) external nonReentrant {
        require(tokenId > 8000 && tokenId < 8021, "Token ID invalid");
        _safeMint(_msgSender(), tokenId);
    }

    function claimForLoot(uint256 tokenId) external nonReentrant {
        require(tokenId > 0 && tokenId < 8001, "Token ID invalid");
        require(loot.ownerOf(tokenId) == msg.sender, "Not Loot owner");
        _safeMint(_msgSender(), tokenId);
    }
    
    /**
     * @notice The price for changing names starts at 420 ALGD and doubles after every change
     */
    function nameChangePrice(uint256 tokenId) public view returns (uint256) {
        return NAME_CHANGE_BASE_PRICE * (2 ** numNameChanges[tokenId]);
    }
    
    function changeName(uint256 tokenId, string calldata firstName, string calldata lastName) external nonReentrant {
        require(tokenId > 0 && tokenId < 8021, "Token ID invalid");
        require(ownerOf(tokenId) == msg.sender, "Not Name owner");
        
        // transfer AGLD to owner
        agld.transferFrom(msg.sender, owner(), nameChangePrice(tokenId));
        
        // override name
        firstNameOverride[tokenId] = firstName;
        lastNameOverride[tokenId] = lastName;
        numNameChanges[tokenId] += 1;
    }
    
    function getItemFromCSV(string memory str, uint256 index) internal pure returns (string memory) {
        strings.slice memory strSlice = str.toSlice();
        string memory separatorStr = ",";
        strings.slice memory separator = separatorStr.toSlice();
        strings.slice memory item;
        for (uint256 i = 0; i <= index; i++) {
            item = strSlice.split(separator);
        }
        return item.toString();
    }
    
    constructor() ERC721("Name", "NAME") Ownable() {}
}
function nameChangePrice(uint256 tokenId) public view returns (uint256) {
    return NAME_CHANGE_BASE_PRICE * (2 ** numNameChanges[tokenId]);
}

/**
 * @notice The price for changing names starts at 420 ALGD and doubles after every change
 */