library GraveyardLibrary {
    string internal constant TABLE =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


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


    /**
     * @dev Converts a digit from 0 - 10000 into its corresponding rarity based on the given rarity tier.
     * @param _randinput The input from 0 - 10000 to use for rarity gen.
     * @param _rarityTier The tier to use.
     */
    function rarityGen(uint256 _randinput, uint8 _rarityTier, uint16[][8] storage TIERS)
        external
        view
        returns (string memory)
    {
        uint16 currentLowerBound = 0;
        for (uint8 i = 0; i < TIERS[_rarityTier].length; i++) {
            uint16 thisPercentage = TIERS[_rarityTier][i];
            if (
                _randinput >= currentLowerBound &&
                _randinput < currentLowerBound + thisPercentage
            ) return toString(i);
            currentLowerBound = currentLowerBound + thisPercentage;
        }

        revert();
    }


    /**
     * @dev Returns the current miece cost of mint.
     */
    function currentMieceCost(uint256 _totalSupply) public pure returns (uint256) {

        if (_totalSupply <= 1000) 
            return 15 ether; // 15 miece

        if (_totalSupply > 1000 && _totalSupply <= 2111)
            return 25 ether; // 25 miece

        if (_totalSupply > 2111 && _totalSupply <= 3222)
            return 35 ether; // 35 miece

        if (_totalSupply > 3222 && _totalSupply <= 4333)
            return 45 ether; // 45 miece

        if (_totalSupply > 4333 && _totalSupply <= 5444)
            return 55 ether; // 55 miece

        revert();
    }


    /**
     * @dev Returns the current cost of fastening up a digging
     */
    function currentFastenCost() public pure returns (uint256) {
        return 10 ether;  // 10 miece
    }

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


    /**
     * @dev Returns a hash for a given tokenId
     * @param tokenHash The tokenId to return the hash for.
     * @param yielded The tokenId to return the hash for.
     */
    function _tokenIdToHash(string memory tokenHash, bool yielded)
        external
        pure
        returns (string memory)
    {
        // string memory tokenHash = tokenIdToHash[_tokenId];

        // If this is grave has been dug override the previous hash
        // FIXME: we could just use the burn address instead and remove the yielded flag
        // if (ownerOf(_tokenId) == 0x000000000000000000000000000000000000dEaD) {

        if ( yielded ) {            
            tokenHash = string(
                abi.encodePacked(
                    "1",
                    GraveyardLibrary.substring(tokenHash, 1, 9)
                )
            );

        }

        return tokenHash;
    }


    /**
     * @dev Hash to metadata function
     */
    function hashToMetadata(string memory _hash, mapping(uint256 => Trait[]) storage traitTypes)
        public
        view
        returns (string memory)
    {
        string memory metadataString;

        for (uint8 i = 0; i < 9; i++) { //9
            uint8 thisTraitIndex = GraveyardLibrary.parseInt(
                GraveyardLibrary.substring(_hash, i, i + 1)
            );

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

            if (i != 8)
                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 tokenURIData(uint256 _tokenId, string memory tokenHash, mapping(uint256 => Trait[]) storage traitTypes, string[] memory LETTERS, uint256 diggingStarted)
        external
        view        
        returns (string memory)
    {
        
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    encode(
                        bytes(
                            string(
                                abi.encodePacked(
                                    '{"name": "cMyGravez #',
                                    toString(_tokenId),
                                    // toString(diggingStarted),      // FIXME: !!!!                               
                                    '", "description": "cMyKatzGraves is a collection of 5,444 unique graves, where burned cats rests in peace. All the metadata and images stored 100% on-chain.", "image": "data:image/svg+xml;base64,',
                                    encode(
                                        bytes(hashToSVG(tokenHash,traitTypes,LETTERS,diggingStarted))
                                    ),
                                    '","attributes":',
                                    hashToMetadata(tokenHash,traitTypes),
                                    "}"
                                )
                            )
                        )
                    )
                )
            );
    }


    /**
     * @dev Hash to SVG function
     */
    function hashToSVG(string memory _hash, mapping(uint256 => Trait[]) storage traitTypes, string[] memory LETTERS, uint256 diggingStarted)
        public
        view
        returns (string memory)
    {
        string memory svgString;
        bool[24][24] memory placedPixels;

        for (uint8 i = 0; i < 9; i++) { 
            uint8 thisTraitIndex = GraveyardLibrary.parseInt(
                GraveyardLibrary.substring(_hash, i, i + 1)
            );
            
            // Hide the zombie arm if the digging is not started
            if ( i == 1 && diggingStarted == 0 ) {
                continue;
            }
            for (
                uint16 j = 0;
                j < traitTypes[i][thisTraitIndex].pixelCount; // <
                j++
            ) {
                string memory thisPixel = GraveyardLibrary.substring(
                    traitTypes[i][thisTraitIndex].pixels,
                    j * 4,
                    j * 4 + 4
                );

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

                if (placedPixels[x][y]) continue;

                svgString = string(
                    abi.encodePacked(
                        svgString,
                        "<rect class='c",
                        GraveyardLibrary.substring(thisPixel, 2, 4),
                        "' x='",
                        toString(x),
                        "' y='",
                        toString(y),
                        "'/>"
                    )
                );

                placedPixels[x][y] = true;
            }
        }

        svgString = string(
            abi.encodePacked(
                '<svg id="g" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 24 24" style="background-color:#2A2A2A"> ',
                svgString,
               // "<style>rect{width:1px;height:1px;} #mouse-svg{shape-rendering: crispedges;} .c00{fill:#000000}.c01{fill:#B1ADAC}.c02{fill:#D7D7D7}.c03{fill:#FFA6A6}.c04{fill:#FFD4D5}.c05{fill:#B9AD95}.c06{fill:#E2D6BE}.c07{fill:#7F625A}.c08{fill:#A58F82}.c09{fill:#4B1E0B}.c10{fill:#6D2C10}.c11{fill:#D8D8D8}.c12{fill:#F5F5F5}.c13{fill:#433D4B}.c14{fill:#8D949C}.c15{fill:#05FF00}.c16{fill:#01C700}.c17{fill:#0B8F08}.c18{fill:#421C13}.c19{fill:#6B392A}.c20{fill:#A35E40}.c21{fill:#DCBD91}.c22{fill:#777777}.c23{fill:#848484}.c24{fill:#ABABAB}.c25{fill:#BABABA}.c26{fill:#C7C7C7}.c27{fill:#EAEAEA}.c28{fill:#0C76AA}.c29{fill:#0E97DB}.c30{fill:#10A4EC}.c31{fill:#13B0FF}.c32{fill:#2EB9FE}.c33{fill:#54CCFF}.c34{fill:#50C0F2}.c35{fill:#54CCFF}.c36{fill:#72DAFF}.c37{fill:#B6EAFF}.c38{fill:#FFFFFF}.c39{fill:#954546}.c40{fill:#0B87F7}.c41{fill:#FF2626}.c42{fill:#180F02}.c43{fill:#2B2319}.c44{fill:#FBDD4B}.c45{fill:#F5B923}.c46{fill:#CC8A18}.c47{fill:#3C2203}.c48{fill:#53320B}.c49{fill:#7B501D}.c50{fill:#FFE646}.c51{fill:#FFD627}.c52{fill:#F5B700}.c53{fill:#242424}.c54{fill:#4A4A4A}.c55{fill:#676767}.c56{fill:#F08306}.c57{fill:#FCA30E}.c58{fill:#FEBC0E}.c59{fill:#FBEC1C}.c60{fill:#14242F}.c61{fill:#B06837}.c62{fill:#8F4B0E}.c63{fill:#D88227}.c64{fill:#B06837}</style></svg>"
                "<style>rect{width:1px;height:1px;}#g{shape-rendering: crispedges;}.c00{fill:#3b0346}.c01{fill:#9500b4}.c02{fill:#496707}.c03{fill:#a58d9c}.c04{fill:#859e4a}.c05{fill:#778d45}.c06{fill:#6f8342}.c07{fill:#ff0043}.c08{fill:#f6767b}.c09{fill:#c74249}.c10{fill:#aa343a}.c11{fill:#ffffff}.c12{fill:#000000}.c13{fill:#bfb23e}.c14{fill:#e9cf00}.c15{fill:#00b300}.c16{fill:#009a1a}.c17{fill:#00791a}.c18{fill:#ff2d00}.c19{fill:#e57600}.c20{fill:#f8d9d9}.c21{fill:#ddc600}.c22{fill:#4f4f4f}.c23{fill:#535353}.c24{fill:#565656}.c25{fill:#444444}.c26{fill:#46503f}.c27{fill:#363d30}.c28{fill:#72775c}.c29{fill:#474939}.c30{fill:#5c614a}.c31{fill:#833f41}.c32{fill:#a09300}.c33{fill:#938700}.c34{fill:#877c00}.c35{fill:#5b231d}.c36{fill:#733e39}.c37{fill:#97575a}.c38{fill:#c66703}.c39{fill:#a75c19}.c40{fill:#5a2e04}.c41{fill:#26331d}.c42{fill:#314522}.c43{fill:#3b2d08}.c44{fill:#47360c}.c45{fill:#002845}.c46{fill:#00538a}.c47{fill:#382e25}.c48{fill:#473c32}.c49{fill:#f6efe9}.c50{fill:#695841}.c51{fill:#705d43}.c52{fill:#786142}.c53{fill:#6a4945}.c54{fill:#724946}.c55{fill:#794a45}.c56{fill:#0f0f0f}.c57{fill:#181818}.c58{fill:#8a8a8a}.c59{fill:#445763}.c60{fill:#475c6a}.c61{fill:#465f70}.c62{fill:#ededed}</style></svg>"
            )
        );

        return svgString;
    }    


    function encode(bytes memory data) public pure returns (string memory) {
        if (data.length == 0) return "";

        // load the table into memory
        string memory table = TABLE;

        // multiply by 4/3 rounded up
        uint256 encodedLen = 4 * ((data.length + 2) / 3);

        // add some extra buffer at the end required for the writing
        string memory result = new string(encodedLen + 32);

        assembly {
            // set the actual output length
            mstore(result, encodedLen)

            // prepare the lookup table
            let tablePtr := add(table, 1)

            // input ptr
            let dataPtr := data
            let endPtr := add(dataPtr, mload(data))

            // result ptr, jump over length
            let resultPtr := add(result, 32)

            // run over the input, 3 bytes at a time
            for {

            } lt(dataPtr, endPtr) {

            } {
                dataPtr := add(dataPtr, 3)

                // read 3 bytes
                let input := mload(dataPtr)

                // write 4 characters
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(input, 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
            }

            // padding with '='
            switch mod(mload(data), 3)
            case 1 {
                mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
            }
            case 2 {
                mstore(sub(resultPtr, 1), shl(248, 0x3d))
            }
        }

        return result;
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        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 parseInt(string memory _a)
        internal
        pure
        returns (uint8 _parsedInt)
    {
        bytes memory bresult = bytes(_a);
        uint8 mint = 0;
        for (uint8 i = 0; i < bresult.length; i++) {
            if (
                (uint8(uint8(bresult[i])) >= 48) &&
                (uint8(uint8(bresult[i])) <= 57)
            ) {
                mint *= 10;
                mint += uint8(bresult[i]) - 48;
            }
        }
        return mint;
    }

    function substring(
        string memory str,
        uint256 startIndex,
        uint256 endIndex
    ) internal pure returns (string memory) {
        bytes memory strBytes = bytes(str);
        bytes memory result = new bytes(endIndex - startIndex);
        for (uint256 i = startIndex; i < endIndex; i++) {
            result[i - startIndex] = strBytes[i];
        }
        return string(result);
    }

    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }
}
// 
function toString(uint256 value) internal pure returns (string memory) {
    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);
}

/**
 * @dev Converts a `uint256` to its ASCII `string` decimal representation.
 */