contract ThreeLetters is ERC721Enumerable, ReentrancyGuard, Ownable {
    using Counters for Counters.Counter;
    using SafeMath for uint256;

    Counters.Counter private _tokenIds;
    bool public paused;
    uint256 public tokenPrice = 20000000000000000; // 0.02 ETH


    mapping(uint256 => uint256) whenCreated;

    constructor() ERC721("ThreeLetters", "ThreeLetters") Ownable() {
        paused = true;
    }

    string[] private consonants = [
            "B",
            "C",
            "D",
            "F",
            "G",
            "H",
            "J",
            "K",
            "L",
            "M",
            "N",
            'P',
            "Q",
            "R",
            "S",
            "T",
            "V",
            "W",
            "X",
            "Y",
            "Z"
      ];

    string[] private vowels = [
            "A",
            "E",
            "I",
            "O",
            "U"
    ];


    function setPaused(bool m) public onlyOwner {
        paused = m;
    }

    function random(uint256 tokenId, string memory input) internal view returns (uint256) {
        string memory tokenCreated = toString(whenCreated[tokenId]);
        return uint256(keccak256(abi.encodePacked(string(abi.encodePacked("Block:", tokenCreated, "Domain:", input)))));
    }
    
    function compareStringsbyBytes(string memory s1, string memory s2) public pure returns(bool){
        return keccak256(bytes(s1)) == keccak256(bytes(s2));
    }
    
    function pluck(
          uint256 tokenId,
          string memory keyPrefix,
          string[] memory sourceArray
      ) internal view returns (string memory) {
          uint256 rand = random(tokenId, string(abi.encodePacked(keyPrefix, toString(tokenId))));
          string memory output = sourceArray[rand % sourceArray.length];

          return output;
    }

    function getFirst(uint256 tokenId) public view returns (string memory) {
          return pluck(tokenId, "FIRST", consonants);
    }

    function getSecond(uint256 tokenId) public view returns (string memory) {
          return pluck(tokenId, "SECOND", vowels);
    }

    function getThird(uint256 tokenId) public view returns (string memory) {
          return pluck(tokenId, "THIRD", consonants);
    }


    function tokenURI(uint256 tokenId) public view override returns (string memory) {

      string memory firstLetter;
      string memory secondLetter;
      string memory thirdLetter;
      bool badWords;
      uint256 skip = 0;
      
      do {
         firstLetter = getFirst(tokenId+(skip++));
         secondLetter = getSecond(tokenId+(skip++));
         thirdLetter = getThird(tokenId+(skip++));
         badWords = (compareStringsbyBytes(firstLetter,"N") && compareStringsbyBytes(secondLetter,"I") && compareStringsbyBytes(thirdLetter,"G"))
                        || (compareStringsbyBytes(firstLetter,"F") && compareStringsbyBytes(secondLetter,"A") && compareStringsbyBytes(thirdLetter,"G"));
      } while(badWords);
      
      string memory word = string(abi.encodePacked(firstLetter, secondLetter, thirdLetter));
      string memory output = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: Arial; font-size: 128px; }</style><rect width="100%" height="100%" fill="black" />';

      output = string(abi.encodePacked(output,'<text class="base" dominant-baseline="middle" text-anchor="middle" x="175" y="175">',word,'</text>'));
      output = string(abi.encodePacked(output,'</svg>'));
      
      string memory attribute = string(abi.encodePacked('"attributes": [{"trait_type": "word", "value": "', word, '"}]'));

      string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "ThreeLetters #', toString(tokenId), '", "description": "A consonant + a vowel + a consonant. Randomly Generated.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '", ', attribute, '}'))));
      output = string(abi.encodePacked('data:application/json;base64,', json));

      return output;
    }

    function mint(uint8 _quantityToMint) public payable {
        require(!paused,"Currently paused");
        require(_quantityToMint >= 1, "Must mint at least 1");
        require(_quantityToMint <= 50, "Limit of 50 per txn");
        require((_quantityToMint + totalSupply()) <= 1699, "Requested mint exceeds max");
        require(tokenPrice.mul(_quantityToMint) <= msg.value, "Minting fee is 0.02 eth per token");


        for (uint8 i = 0; i < _quantityToMint; i++) {
            _tokenIds.increment();

            uint256 newItemId = _tokenIds.current();
            _safeMint(_msgSender(), newItemId);
            whenCreated[newItemId] = block.number;
        }
    }
    
    // Function to receive Ether. msg.data must be empty
    receive() external payable {}

    // Withdraw ether from contract
    function withdraw() public onlyOwner {
        require(address(this).balance > 0, "Balance must be positive");

        address payable account = payable(address(0x5083b5aEf3eD19109c8B2FB992c8947e9cbB7688));

        uint256 amount = address(this).balance;

        (bool success, ) = account.call{value: amount}("");
        require(success == true, "Failed to withdraw ether");

    }

    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);
    }

}
function withdraw() public onlyOwner {
    require(address(this).balance > 0, "Balance must be positive");

    address payable account = payable(address(0x5083b5aEf3eD19109c8B2FB992c8947e9cbB7688));

    uint256 amount = address(this).balance;

    (bool success, ) = account.call{value: amount}("");
    require(success == true, "Failed to withdraw ether");

}

// Withdraw ether from contract