contract MadCowNFT is ERC721Enumerable {
    using SafeMath for uint256;
    using Library for uint8;

    struct Trait {
        string traitName;
        string traitType;
        string pixels;
        uint256 pixelCount;
    }

    //Mappings
    mapping(uint256 => Trait[]) public traitTypes;
    mapping(string => bool) hashToMinted;
    mapping(uint256 => string) internal tokenIdToHash;
    mapping(address => bool) whitelistedAddresses;

    uint256 public preSaleDate = 1638259200;
    uint256 public publicSaleDate = 1638262800;

    //uint256s
    uint256 MAX_SUPPLY = 6969;
    uint256 PRICE = 69000000000000000;
    uint256 PRESALE_PRICE = 34000000000000000;
    uint256 SEED_NONCE = 0;

    //string arrays
    string[] LETTERS = [
        "a",
        "b",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h",
        "i",
        "j",
        "k",
        "l",
        "m",
        "n",
        "o",
        "p",
        "q",
        "r",
        "s",
        "t",
        "u",
        "v",
        "w",
        "x",
        "y",
        "z"
    ];

    //uint arrays
    //uint16[][8] TIERS;

    //address
    address tokenAddress;
    address _owner;

    constructor() ERC721("Mad Cow NFT", "MC") {
        _owner = msg.sender;

    }



    /*
  __  __ _     _   _             ___             _   _             
 |  \/  (_)_ _| |_(_)_ _  __ _  | __|  _ _ _  __| |_(_)___ _ _  ___
 | |\/| | | ' \  _| | ' \/ _` | | _| || | ' \/ _|  _| / _ \ ' \(_-<
 |_|  |_|_|_||_\__|_|_||_\__, | |_| \_,_|_||_\__|\__|_\___/_||_/__/
                         |___/                                     
   */
    /**
     * @dev Mint internal, this is to avoid code duplication.
     */
    function mintInternal() internal {
        uint256 _totalSupply = totalSupply();
        require(_totalSupply < MAX_SUPPLY);
        require(!Library.isContract(msg.sender));

        uint256 thisTokenId = _totalSupply;
        tokenIdToHash[thisTokenId] = inithash();
        hashToMinted[tokenIdToHash[thisTokenId]] = true;

        _mint(msg.sender, thisTokenId);
    }

    /**
     * @dev Mints new tokens.
     */
    function mintNfToken(uint256 _times) public payable {
        uint256 _price;

        if (block.timestamp < publicSaleDate)
          {_price = PRESALE_PRICE;}
        else
          {_price = PRICE;}
        
        require((_times > 0 && _times <= 30));
        require(msg.value >= _price);

        // Do some validations depending on which step of the sale we are in
        block.timestamp < publicSaleDate ? presaleValidations() : require(msg.value >= _price);

        for(uint256 i=0; i< _times; i++){
            mintInternal();
        }
    }

    function presaleValidations() internal view {
        require(isWhitelisted(msg.sender), "user is not whitelisted");
    }

    //PUBLIC VIEWS
    function isWhitelisted(address _user) public view returns (bool) {
        return whitelistedAddresses[_user];
    }

    function inithash() internal returns (string memory) {
        // Initialized by all zeros.
        string memory currentHash;

        // Character among five
        uint rand_char = uint(keccak256(abi.encodePacked(block.timestamp, msg.sender, SEED_NONCE))) % 5;
        SEED_NONCE++;
        uint rand_eye = uint(keccak256(abi.encodePacked(msg.sender, block.timestamp, SEED_NONCE))) % 7;
        SEED_NONCE++;
        uint rand_mouth = uint(keccak256(abi.encodePacked(SEED_NONCE, block.timestamp, msg.sender))) % 5;
        SEED_NONCE++;
                
        currentHash = string(abi.encodePacked(Library.toString(rand_char), Library.toString(rand_eye), Library.toString(rand_mouth)));

        return currentHash;
    }

    function UpgradeAccessory(uint16 _t, string memory _c) public {
        // _c == 0: 
        require(ownerOf(_t) == msg.sender);
        require(compare(_c,"0")||compare(_c,"1")||compare(_c,"2")||compare(_c,"3"));

        string memory currentHash = _tokenIdToHash(_t);
        require(length(currentHash) == 3);

        IToken(tokenAddress).burnFrom(msg.sender, 2000000000000000000);
        tokenIdToHash[_t] = concat(currentHash, _c);
    }


    function UpgradeHat(uint16 _t, string memory _c) public {
        // _c == 0: 
        require(ownerOf(_t) == msg.sender);
        require(compare(_c,"0")||compare(_c,"1")||compare(_c,"2")||compare(_c,"3")||compare(_c,"4")||compare(_c,"5"));

        string memory currentHash = _tokenIdToHash(_t);
        require(length(currentHash) == 4);

        IToken(tokenAddress).burnFrom(msg.sender, 2000000000000000000);
        tokenIdToHash[_t] = concat(currentHash, _c);
    }

    function UpgradeNeck(uint16 _t, string memory _c) public {
        // _c == 0: 
        require(ownerOf(_t) == msg.sender);
        require(compare(_c,"0")||compare(_c,"1")||compare(_c,"2")||compare(_c,"3"));

        string memory currentHash = _tokenIdToHash(_t);
        require(length(currentHash) == 5);

        IToken(tokenAddress).burnFrom(msg.sender, 2000000000000000000);
        tokenIdToHash[_t] = concat(currentHash, _c);
    }

    function length(string memory str) internal pure returns(uint) {return bytes(str).length;}

    function concat(string memory a, string memory b) internal pure returns(string memory) 
    {
        return string(abi.encodePacked(a, b));
        }

    function compare(string memory a, string memory b) internal pure returns(bool)
    {return keccak256(abi.encodePacked(a))==keccak256(abi.encodePacked(b));}

    /*
 ____     ___   ____  ___        _____  __ __  ____     __ ______  ____  ___   ____   _____
|    \   /  _] /    ||   \      |     ||  |  ||    \   /  ]      ||    |/   \ |    \ / ___/
|  D  ) /  [_ |  o  ||    \     |   __||  |  ||  _  | /  /|      | |  ||     ||  _  (   \_ 
|    / |    _]|     ||  D  |    |  |_  |  |  ||  |  |/  / |_|  |_| |  ||  O  ||  |  |\__  |
|    \ |   [_ |  _  ||     |    |   _] |  :  ||  |  /   \_  |  |   |  ||     ||  |  |/  \ |
|  .  \|     ||  |  ||     |    |  |   |     ||  |  \     | |  |   |  ||     ||  |  |\    |
|__|\_||_____||__|__||_____|    |__|    \__,_||__|__|\____| |__|  |____|\___/ |__|__| \___|
                                                                                           
*/

    /**
     * @dev Helper function to reduce pixel size within contract
     */
    function letterToNumber(string memory _inputLetter)
        internal
        view
        returns (uint8)
    {
        for (uint8 i = 0; i < LETTERS.length; i++) {
            if (
                keccak256(abi.encodePacked((LETTERS[i]))) == keccak256(abi.encodePacked((_inputLetter)))
            ) return (i);
        }
        revert();
    }

    /**
     * @dev Hash to SVG function
     */
    function hashToSVG(string memory _hash)
        public
        view
        returns (string memory)
    {
        string memory svgString;
        bool[24][24] memory placedPixels;

        uint256 len = length(_hash);

        for (uint8 i = 0; i < len; i++) {
            // for loop over trait categories

            uint8 thisTraitIndex = Library.parseInt(
                Library.substring(_hash, i, i + 1)
            );

            for (
                uint16 j = 0;
                j < traitTypes[i][thisTraitIndex].pixelCount;
                j++
            ) {
                // for loop over pixel categories
                string memory thisPixel = Library.substring(
                    traitTypes[i][thisTraitIndex].pixels,
                    j * 4,
                    j * 4 + 4
                );

                uint8 x = letterToNumber(
                    Library.substring(thisPixel, 0, 1)
                );
                uint8 y = letterToNumber(
                    Library.substring(thisPixel, 1, 2)
                );

                svgString = string(
                    abi.encodePacked(
                        svgString,
                        "<rect class='c",
                        Library.substring(thisPixel, 2, 4),
                        "' x='",
                        x.toString(),
                        "' y='",
                        y.toString(),
                        "'/>"
                    )
                );
            
                placedPixels[x][y] = true;
            }    

        }
        svgString = string(
            abi.encodePacked(
                '<svg id="mouse-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet" viewBox="0 0 24 24"><rect class="bg" x="0" y="0" /><image x="0" y="0" width="24" height="24" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAejSURBVEhLVZUJVJTXFcd/M2wDDMsgw6ogKKCyuIJKsKW4xbphjJpIUqu1mmriUmPS5BhFW2xPmpjUpZq6EEGMxg3LZqqoKK4gMjgCyjIMssiwD8wAA8zXma+nPaf3nHe+9+53v//9f/e+93+SzKhEocQ0iMcnK/ER/FgVPJ6moSIcImNprHuC1bzC4lAV62lNvcQ3Z7M4rjon+n2DjUh6pJzef5j++N+KvpraLvF5dOMInGWjkM4tOs4UezvS1qUTujwEjdyG7TkNNFRqqTGNxU0ZwMlzTzD5mkjK+JATumxe/fCdOHxl/lyrNxI55+dM2rcDd30hC0d0kBbtTffjp7R3tiFRPcwVrAyROqN63Ix/lD+25mbGeXrzutfAQsVbIqPsnlP/Yzt3z2bKt17GJaCX0mnviKz/y9hIBy/6DLhXObDnVhM2HfIZyVMUMtITP2DR7iTSbzcyP8Ydk9SEKkfDqsNbCIwNR2ETwI2yMvqkI8nLHSTK44WY+I1VS9gwz5PCskY0La28kigIchrkX40afj2qHyq0rwThVoM4rPNFvzsk1JdphIrmJuEvH/5BEAaHhS5BEJ6pe8R5c1OD0NNp9fQKn36WItyvLxfaXrcIyRdrhO5XN4Q/rTsslD8pFTos708dyBEkzZ16wcrkzQ8ucCk1EUPZIzKfu/Go1pLdYjGTR7NteTBuQ2Z+ut1MxtMWdi7xJetRHa5yOYunypHLnOk1tKLAiSYnGVdTDrLh0C7adc78J0FzKfeqZYzy68SrxUx7UBBKUwP2XuPEJIKLxadVUNThSEf3DeYqzUgHHHEZqSRjw3YxZtZX/8B3pDPeng7ES5aLvo9Vl5DaunajagxGbRpB0gU/VJ99x8FbVXxxr48nzzXiGNIMo6usQffjLkKDQrlWZCRwykQGff3ZmHGGjugF1LwspvBmAa3FJYzZsFJMEChokaRdvS/8YoySdgdbyoqrmTxvDo0ljUyLtKPc8ts3al+zImo2nb3PMQw70d3dTF9PCErjTSLGR3AnVYVZdYN5Z/cyUKmzxJlQyO15UVAhJpGq69TYejiS/+A+0bHjMKub2HfpGkuSs/AecmOsupNxStDVdNN1r5KUdC0ybTFT3/glOaXdeLmbRaDW52p235Jg0OqRuI0kcOl49lUPICnU6ITUY3coNtuxf6U3KSfVGDolOCsEDv1xPlFmPetO9/NRXBfOFS089hjDjIQQaotfEOAdgae/gDbrHj3T/Qj3U1BTbsDT1uLrcOVhhqUHtQ9VbP4omoKUeIq0Sj65n83eiizyj76HbU8vf1XJ6a0v4UbvWIbC3Ul56MRQvwKdXsqzYTPWHrb5ykVwqxn0LtiN9+ZRvZaAdZuRTpocTm5WLd/+sw3Z7eNYdclqJy6/xE3qwOrQYXE93QNGK8eRtW0E/l4G4mJjGKitZMexUvH9w6f9dNfDpyd+IjO/iKSwQXHH2ezetDT5qVYgxs+LOt+J2EWG45E4m5b8H/Gbsxh9cwnRU6OJ9Hck5kiXpXl5lsZ34TOkw87Fk2C9gZBZ8bxKycAwKwRfcye+eRpGRfsQaNkc0mvPWoidqeDkhVzejbRnRcJYkA2SIY1issIOV+UETlvEzip+Fxf0MaHLzHQfD8qEAExVWpF96faDuKhvYV79e443hOH2+WLK3QNYdkCNpNrYLmSfy2bVm7M5fy1flOzw6TNAKSeivJF12a9EkP27wjEX54sy3izxwqzJ49sLClqNxcQuW8KEj7dS/tXfWOj6nKIhFyqyi1iwaa1FIC26Ipe7sGrn39m/fzXVlgM2P94X9f07RMRF83VuIZLOdhSjVzHNS/d/EqGwtMcqDSZtKTn6cGostU/cksg0JyNVde1cPX8FmwXvbEwetB/CdexUJvj10qyRoHAZYG66wJOqUt6dNxvJkJzIcE+6Bust21KBjaydk6eMGL18mOwlobz6BXb3tBxq8+F82l1kXjH82ehAju0YbGZX9CUn3O1hhaOB64+7+M3lxzjNnMWXoypYGjcJN2GA6LRqknyc8e93xMGoZt5l2LctFof336c+PZulTYFM3LqGu+fvsHPTr9jbb6DtRTW1GwKRWut7JUhH3fbDZNTHMy54vVjziqA4jrQpWJkvp/rtCZw4X8v6l65MfL2A7yeZsSt/LcbJvj7MlYocgs+c5eypJJZZ2pcZJ8P5VinRW+4jOfOzDaJcN5S20P84U/zovbYC9ql6mNOsZ02VkeQv1rCn2g5pZRlrWo1oLCiHQiz379xlYrzVUo/8wFV1JrXT4tlqZ6K6ScmXE+2RWoHlsYFi0FSXYc6kqvn84jP6q+pE8LKD61kzBlyOphHWHSWC7wgaZuKBm+iXrMAm7yJjrl/hZa6M8YFLcfQf5uERDYMFNVzIzEPaf+oAb7kv4vTmTEo6zcQG1FHiEE9ehSua79cQ4WGmslAnEvgmoZE7WTd5mX+esNYE0lxXM03ymoWpTZYLoY8k7SNC1+bTb5GJmSNMCAY/JHV9JrFE7b0tIkhMppTQB368nG8Q1xm9DzgWNoc7hWUkWA6fqqqA9JVjWXu9A+9HT0g/8ja+/mZ0tRomFPoRP1XJ89MFtOmrkIwO5t+iDIxt6FEnDgAAAABJRU5ErkJggg=="/>',
                svgString,
                "<style>rect.bg{width:24px;height:24px;fill:#FFCBB6}rect{width:1px;height:1px;}#mouse-svg{shape-rendering:crispedges;}@keyframes colorChange {0%{fill:#FFFF00}25%{fill:#FF0000}50%{fill: #00FF00}75%{fill: #00FFFF}100%{fill: #FF00FF}}.c00{animation: colorChange 1.5s infinite;}.c01{fill:#ffffff}.c02{fill:#D7D7D7}.c03{fill:#A6FFFF}.c04{fill:#D4FFFE}.c05{fill:#95A1B9}.c06{fill:#BECAE2}.c07{fill:#5A777F}.c08{fill:#8298A5}.c09{fill:#0B384B}.c10{fill:#10516D}.c11{fill:#D8D8D8}.c12{fill:#F5F5F5}.c13{fill:#454B3D}.c14{fill:#9C958D}.c15{fill:#FA00FF}.c16{fill:#C600C7}.c17{fill:#2b2d2f}.c18{fill:#465FFF}.c19{fill:#FFCBB6}.c20{fill:#4085A3}.c21{fill:#91B0DC}.c22{fill:#777777}.c23{fill:#848484}.c24{fill:#ABABAB}.c25{fill:#BABABA}.c26{fill:#C7C7C7}.c27{fill:#cc0000}.c28{fill:#cc0000}.c29{fill:#DB520E}.c30{fill:#EC5810}.c31{fill:#FF6213}.c32{fill:#FE732E}.c33{fill:#FF8754}.c34{fill:#F28250}.c35{fill:#FF8754}.c36{fill:#FF9772}.c37{fill:#FFCBB6}.c38{fill:#FFFFFF}.c39{fill:#459594}.c40{fill:#F77B0B}.c41{fill:#26FFFF}.c42{fill:#180F02}.c43{fill:#19212B}.c44{fill:#4B69FB}.c45{fill:#FFCBB6}.c46{fill:#185ACC}.c47{fill:#031D3C}.c48{fill:#0B2C53}.c49{fill:#1D487B}.c50{fill:#465FFF}.c51{fill:#465FFF}.c52{fill:#003EF5}.c53{fill:#242424}.c54{fill:#4A4A4A}.c55{fill:#676767}.c56{fill:#0673F0}.c57{fill:#0E67FC}.c58{fill:#0E50FE}.c59{fill:#1C2BFB}.c60{fill:#2F1F14}.c61{fill:#377FB0}.c62{fill:#0E528F}.c63{fill:#710193}.c64{fill:#377FB0}.c65{fill:#2a2a2a}.c66{fill:#161616}.c67{fill:#ffd9c2}.c68{fill:#ffc3a0}.c69{fill:#743d2b}.c70{fill:#3c1917}.c71{fill:#FF3E33}.c72{fill:#FF3E33}.c73{fill:#ffd700}.c74{fill:#50c878}.c75{fill:#fed0fc}.c76{fill:#000000}.c77{fill:#6d3200}.c78{fill:#eec07b}.c79{fill:#FFCC99}.c80{fill:#bb7777}.c81{animation: colorChange 1s infinite;}.c82{fill:#808080}</style></svg>"
            )
        );

        return svgString;
    }

    /**
     * @dev Hash to metadata function
     */
    function hashToMetadata(string memory _hash)
        public
        view
        returns (string memory)
    {
        string memory metadataString;

        uint256 len = length(_hash);

        for (uint8 i = 0; i < len; i++) {
            uint8 thisTraitIndex = Library.parseInt(
                Library.substring(_hash, i, i + 1)
            );

            metadataString = string(
                abi.encodePacked(
                    metadataString,
                    '{"trait_type":"',
                    traitTypes[i][thisTraitIndex].traitType,
                    '","value":"',
                    traitTypes[i][thisTraitIndex].traitName,
                    '"}'
                )
            );

            if (i != len-1){
                metadataString = string(abi.encodePacked(metadataString, ","));}
        }

        return string(abi.encodePacked("[", metadataString, "]"));
    }

    /**
     * @dev Returns the SVG and metadata for a token Id
     * @param _tokenId The tokenId to return the SVG and metadata for.
     */
    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(_exists(_tokenId));

        string memory tokenHash = _tokenIdToHash(_tokenId);

        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Library.encode(
                        bytes(
                            string(
                                abi.encodePacked(
                                    '{"name": "Mad Cow #',
                                    Library.toString(_tokenId),
                                    '", "description": "Mad Cow NFT consists of 6,969 unique crazy metaverse cows. It introduces the upgrading traits system for the first time in the NFT space. All the metadata and images are generated and stored 100% on-chain. No IPFS, no API.", "image": "data:image/svg+xml;base64,',
                                    Library.encode(
                                        bytes(hashToSVG(tokenHash))
                                    ),
                                    '","attributes":',
                                    hashToMetadata(tokenHash),
                                    "}"
                                )
                            )
                        )
                    )
                )
            );
    }

    /**
     * @dev Returns a hash for a given tokenId
     * @param _tokenId The tokenId to return the hash for.
     */
    function _tokenIdToHash(uint256 _tokenId)
        public
        view
        returns (string memory)
    {
        string memory tokenHash = tokenIdToHash[_tokenId];
        return tokenHash;
    }

    /**
     * @dev Returns the wallet of a given wallet. Mainly for ease for frontend devs.
     * @param _wallet The wallet to get the tokens of.
     */
    function walletOfOwner(address _wallet)
        public
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_wallet);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_wallet, i);
        }
        return tokensId;
    }

    /*

  ___   __    __  ____     ___  ____       _____  __ __  ____     __ ______  ____  ___   ____   _____
 /   \ |  |__|  ||    \   /  _]|    \     |     ||  |  ||    \   /  ]      ||    |/   \ |    \ / ___/
|     ||  |  |  ||  _  | /  [_ |  D  )    |   __||  |  ||  _  | /  /|      | |  ||     ||  _  (   \_ 
|  O  ||  |  |  ||  |  ||    _]|    /     |  |_  |  |  ||  |  |/  / |_|  |_| |  ||  O  ||  |  |\__  |
|     ||  `  '  ||  |  ||   [_ |    \     |   _] |  :  ||  |  /   \_  |  |   |  ||     ||  |  |/  \ |
|     | \      / |  |  ||     ||  .  \    |  |   |     ||  |  \     | |  |   |  ||     ||  |  |\    |
 \___/   \_/\_/  |__|__||_____||__|\_|    |__|    \__,_||__|__|\____| |__|  |____|\___/ |__|__| \___|
                                                                                                     


    */

    /**
     * @dev Clears the traits.
     */
    function clearTraits() public onlyOwner {
        for (uint256 i = 0; i < 9; i++) {
            delete traitTypes[i];
        }
    }

    /**
     * @dev Add a trait type
     * @param _traitTypeIndex The trait type index
     * @param traits Array of traits to add
     */

    function addTraitType(uint256 _traitTypeIndex, Trait[] memory traits)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < traits.length; i++) {
            traitTypes[_traitTypeIndex].push(
                Trait(
                    traits[i].traitName,
                    traits[i].traitType,
                    traits[i].pixels,
                    traits[i].pixelCount
                )
            );
        }

        return;
    }

    /**
     * @dev Sets the ERC20 token address
     * @param _tokenAddress The token address
     */

    function setTokenAddress(address _tokenAddress) public onlyOwner {
        tokenAddress = _tokenAddress;
    }

    /**
     * @dev Transfers ownership
     * @param _newOwner The new owner
     */
    function transferOwnership(address _newOwner) public onlyOwner {
        _owner = _newOwner;
    }

    /**
     * @dev Withdraw ETH to owner
     */
    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;

        payable(msg.sender).transfer(balance);
    }   

    function whitelistUsers(address[] memory addresses) public onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            whitelistedAddresses[addresses[i]] = true;
        }
    }


    /**
     * @dev Modifier to only allow owner to call functions
     */
    modifier onlyOwner() {
        require(_owner == msg.sender);
        _;
    }
}
//pragma solidity ^0.8.0;
function withdraw() public onlyOwner {
    uint256 balance = address(this).balance;

    payable(msg.sender).transfer(balance);
}   

/**
 * @dev Withdraw ETH to owner
 */