contract CurseWords is ERC721Enumerable, ReentrancyGuard, Ownable {
    // from: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol 
    function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
        require(_start + 1 >= _start, "toUint8_overflow");
        require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
        uint8 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x1), _start))
        }

        return tempUint;
    }
    
    uint256 public constant MINT_PRICE = 4269000000000000; //0.004269 ETH, guess i'll give it to you if you can ctrl-f
    
    string private mainKey = "CURSE";
    string private auxKey = "AUXCURSE";

    
    string [] private rarity = [
        "base",
        "modified",
        "rare",
        "hurtful base",
        "hurtful modified"
    ];

    string[][] private curseSuiteTM = [
        // base words
        [
            string("fuck"),
            string("shit"),
            string("piss"),
            string("ass"),
            string("bitch"),
            string("cunt"),
            string("dick")
        ],
        // modified words
        [
            string("motherfucker"),
            string("shithead"),
            string("piss baby"),
            string("asshole"),
            string("bad bitch"),
            string("cuntface"),
            string("dickhead")
        ],
        // rare words
        [        
            string("bad dude"),
            string("not nice!"),
            string("say sorry"),
            string("you made me cry"),
            string("i want to puke"),
            string("thot we were frnieds :("),
            string("please say sike")
        ],
        // hurtful base words
        [
            string("you fuck"),
            string("shit!"),
            string("piss off!"),
            string("you ass"),
            string("you bitch"),
            string("you cunt"),
            string("you dick")
        ],
        // hurtful modified words
        [
            string("you motherfucker"),
            string("you shithead"),
            string("you piss baby"),
            string("you asshole"),
            string("you bad bitch"),
            string("you cuntface"),
            string("you dickhead")
        ]
    ];

    string[] private colors = [
        "EF476F",
        "FFD166",
        "06D6A0",
        "118AB2",
        "073B4C"
    ];
    
    string[] private backgroundColors = [
        "000000",
        "000000",
        "000000",
        "000000",
        "ffffff",
        "ffffff",
        "6c70ad",
        "aba3a6"
    ];
    
    string[] private sizes = [
        "42",
        "42",
        "42",
        "42",
        "69",
        "69",
        "69",
        "69",
        "71",
        "88",
        "100",
        "420"
    ];
    
    string[] private auxSizes = [
        "500",
        "25",
        "25",
        "25",
        "16",
        "16",
        "16",
        "16",
        "14",
        "12",
        "5"
    ];
    
    string[] private fonts = [
        "arial",
        "serif",
        "sans-serif",
        "cursive",
        "fantasy",
        "fantasy",
        "monospace"
    ];
    
    string[] private fontMods = [
        "normal",
        "normal",
        "normal",
        "normal",
        "normal",
        "italic"
    ];

    string private rarestCurseWord = "*****"; // so rare you can't have it bby sowwy :'(
    
    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }
    
    function getCurseWord(uint256 tokenId) public view returns (string memory) {
        return multipluck(tokenId, mainKey, curseSuiteTM);
    }

    function getAuxCurseWord(uint256 tokenId) public view returns (string memory) {
        return multipluck(tokenId, auxKey, curseSuiteTM);
    }

    function getPrimaryColor(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "COLOR", colors);
    }

    function getAuxColor(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "AUXCOLOR", colors);
    }
    
    function getPrimarySize(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "SIZE", sizes);
    }
    
    function getAuxSize(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "AUXSIZE", auxSizes);
    }
    
    function getPrimaryFont(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "FONT", fonts);
    }
    
    function getAuxFont(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "AUXFONT", fonts);
    }
    
    function getPrimaryFontModifier(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "AUXFONT", fontMods);
    }
    
    function getPrimaryWordRarity(uint256 tokenId) public view returns (string memory) {
        uint8 index = getAssholeRankingResults(tokenId, mainKey);
        return rarity[index];
    }
    
    function getAuxWordRarity(uint256 tokenId) public view returns (string memory) {
        uint8 index = getAssholeRankingResults(tokenId, auxKey);
        return rarity[index];
    }
    
    function getBackgroundColor(uint256 tokenId) public view returns (string memory) {
        return pluck(tokenId, "BCOLOR", backgroundColors);
    }
    
    // teehee
    function getAssholeRankingResults(uint256 tokenId, string memory keyPrefix) private pure returns (uint8) {
        uint256 assholeRanking = random(string(abi.encodePacked(keyPrefix, toString(tokenId)))) % 100;
        uint8 index = 0; 
        if (assholeRanking > 96) {
            index = 2;
        } else if (assholeRanking > 88) {
            index = 1;
        }
        
        if (index == 2) {
            return index;
        }

        if ((random(string(abi.encodePacked(keyPrefix, toString(tokenId)))) % 100) > 69) {
            index = index + 3;
        }
        return index;
    }
    
    function multipluck(uint256 tokenId, string memory keyPrefix, string[][] memory multiArr) internal pure returns (string memory) {
        uint8 index = getAssholeRankingResults(tokenId, keyPrefix);
        return pluck(tokenId, keyPrefix, multiArr[index]);
    }

    function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArr) internal pure returns (string memory) {
        string memory output = sourceArr[random(string(abi.encodePacked(keyPrefix, toString(tokenId)))) % sourceArr.length];
        return output;
    }

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

        // bruh
        uint256 seed = random(string(abi.encodePacked("69forever", tokenId)));
        uint256 randomishX = seed % 69;
        uint256 randomishY = seed % 300 + 40;
        uint256 randomishAuxX = seed % 42 + 30;
        uint256 randomishAuxY = seed % 150 + 70;

        parts[0] = string(abi.encodePacked(
            '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: #',
            getPrimaryColor(tokenId),
            "; font-family: ",
            getPrimaryFont(tokenId),
            "; font-style: ",
            getPrimaryFontModifier(tokenId),
            "; font-size: ",
            getPrimarySize(tokenId),
            "px; } .aux{fill: #",
            getAuxColor(tokenId),
            '; font-family: '
        ));
        
        parts[1] = string(abi.encodePacked(
            getAuxFont(tokenId),
            '; font-size: ',
            getAuxSize(tokenId),
            'px}</style><rect width="100%" height="100%" fill="#',
            getBackgroundColor(tokenId),
            '" />',
            '<text x="', 
            uint2str(randomishX), 
            '" y="',
            uint2str(randomishY),
            '" class="base">'));

        parts[2] = getCurseWord(tokenId);

        parts[3] = string(abi.encodePacked('</text><text x="', uint2str(randomishAuxX), '" y="', uint2str(randomishAuxY), '" class="aux">'));

        parts[4] = getAuxCurseWord(tokenId);

        parts[5] = '</text></svg>';
        
        string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]));
        
        // intentionally left font out for funsies ;)
        string memory metadataStart = string(abi.encodePacked(
            '"attributes": [{"trait_type": "Primary Word", "value": "', 
            getCurseWord(tokenId), 
            '"}, {"trait_type": "Auxilliary Word", "value": "',
            getAuxCurseWord(tokenId),
            '"}, {"trait_type": "Primary Word Size", "value": "',
            getPrimarySize(tokenId),
            '"}, {"trait_type": "Auxilliary Word Size", "value": "',
            getAuxSize(tokenId)));
        string memory metadata = string(abi.encodePacked(
            metadataStart,
            '"}, {"trait_type": "Primary Word Rarity", "value": "',
            getPrimaryWordRarity(tokenId),
            '"}, {"trait_type": "Auxilliary Word Rarity", "value": "',
            getAuxWordRarity(tokenId),
            '"}, {"trait_type": "Background Color", "value": "',
            getBackgroundColor(tokenId),
            '"}], '));
        string memory json = Base64.encode(bytes(string(abi.encodePacked(
            '{"name": "Empty Curse #', 
            toString(tokenId), 
            '", "description": "Empty Curse #', 
            toString(tokenId), 
            '", "description": "Empty Curses (for Assholes) are randomly generated, minimally artistic curse words generated on-chain. They do nothing... right? BTW did I mention this is all generated on-chain? Look at the contract. I know you nerds love that shit.", ', 
            metadata, 
            '"image": "data:image/svg+xml;base64,', 
            Base64.encode(bytes(output)), '"}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));

        return output;
    }

    function claimFreeEmptyCurse(uint256 tokenId) public nonReentrant {
        require(tokenId > 0 && tokenId < 3369, "Token ID invalid");
        require(tokenId != 888, "888 is banned lmfao nerd!");
        require(tokenId != 420, "420 is also banned we don't smoke here this is a christian smart contract!!1! lmfao nerd!");
        _safeMint(_msgSender(), tokenId);
    }
    
    // .004269 to mint 3369-4199, coders get the alpha nerd followers get the finger
    function claimPaidEmptyCurse(uint256 tokenId) public nonReentrant payable {
        require(tokenId > 3368 && tokenId < 4200, "Token ID invalid");
        require(msg.value >= MINT_PRICE, "Payment too low, try .004269");
        _safeMint(_msgSender(), tokenId);
    }
    
    function whatDoesThisDo(uint256 tokenId) public nonReentrant onlyOwner {
        require(tokenId > 4199 && tokenId < 4269, "Token ID invalid");
        _safeMint(owner(), tokenId);
    }
    
    function daBagz() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
    
    function overload() public nonReentrant onlyOwner {
        // owner tokens can't be minted by randos
        for (uint i = 3528 ; i < 3596 ; i++) {
            if (!_exists(i)) {
                _safeMint(owner(), i);
            }
        }
        _safeMint(owner(), 420);
    }
    
    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("Empty Curses", "MTCRS") Ownable() {}
}
// if you're reading this fuck you, 69
function claimPaidEmptyCurse(uint256 tokenId) public nonReentrant payable {
    require(tokenId > 3368 && tokenId < 4200, "Token ID invalid");
    require(msg.value >= MINT_PRICE, "Payment too low, try .004269");
    _safeMint(_msgSender(), tokenId);
}

// .004269 to mint 3369-4199, coders get the alpha nerd followers get the finger