// Contract: CryptoSVG
// Target function: function _getTokenToBeMinted(uint16 _totalMintedTokens) private returns (uint16)
regular ? _url('12') : filterType == FilterType.pixel ? _url('14') : '',
                    '" cx="',
                    calOffset(location),
                    '" cy="300" r="150" />'
                )
            );
    }

    function animateEllipse(int256 location, Ellipase memory ellipse) internal pure returns (string memory) {
        uint256 scale = calScale(location);
        return
            generateEllipse(
                Ellipase(
                    ellipse.cx,
                    ellipse.cy,
                    (ellipse.rx * scale) / 100,
                    (ellipse.ry * scale) / 100,
                    ellipse.fill,
                    ellipse.filter,
                    '1'
                )
            );
    }

    function animateCircle(int256 location, Circle memory circle) internal pure returns (string memory) {
        return
            generateCircle(
                Circle(
                    circle.cx,
                    circle.cy,
                    (circle.r * calScale(location)) / 100,
                    circle.fill,
                    circle.filter,
                    circle.opacity
                )
            );
    }

    function generatePlant(
        string memory color,
        int256 location,
        FType memory fType
    ) internal pure returns (string memory) {
        string memory k150 = '150';
        string memory k300 = '300';
        string memory k1 = '1';
        string memory k0 = '0';
        string memory kEmpty = '';
        uint256 scale = calScale(location);
        FilterType filterType = fType.filterType;
        string[54] memory parts;
        parts[0] = '<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600"><defs>';

        parts[1] = generateFilterBlur(k1, '-0.1', '-0.1', '2', k1);

        parts[2] = generateFilterBlur('2', '-0.1', '-0.1', '2', '10');

        parts[3] = generateFilterBlur('3', '-0.2', '-0.2', '3', '21');

        parts[4] = generateFilterBlur('4', k0, k0, '2', '91');

        parts[5] = generateFilterBlur('5', '-100', '-100', k300, '7');

        parts[6] = generateFilterBlur('6', '-100', '-100', k300, '21');

        parts[7] = generateFilterBlur('7', '-100', '-100', k300, '6');

        parts[8] = generateFilterBulence(
            '9',
            FilterBulence(k0, k0, k1, 'fractalNoise', fType.baseFrequency, fType.numOctaves)
        );

        parts[9] = generateFilterBulence(
            '10',
            FilterBulence(k0, k0, k1, 'turbulence', fType.baseFrequency, fType.numOctaves)
        );

        parts[10] = generateFilteUnregular('11', color);

        parts[11] = generateFilteUnregular('12', '#000000');

        parts[12] = generateFilterPixel('14');

        parts[13] = generateFilterTexture('15', color, fType.surfaceScale);

        parts[14] = '</defs>';

        parts[15] = generateRadialGradient(
            'r1',
            RadialGradient(k150, k300, Strings.toString((80 * scale) / 100), false, kEmpty, kEmpty, false, true)
        );

        parts[16] = generatesStop(k0, k0, color);

        parts[17] = generatesStop(k0, '0.89', color);

        parts[18] = generatesStop('0.99', '0.9', color);

        parts[19] = generatesStop(k0, k1, color);

        parts[20] = '</radialGradient>';

        parts[21] = generateRadialGradient(
            'r2',
            RadialGradient('230', k300, Strings.toString((30 * scale) / 100), false, kEmpty, kEmpty, false, true)
        );

        parts[22] = generatesStop(k0, k0, color);

        parts[23] = generatesStop(k0, '0.89', color);

        parts[24] = generatesStop('0.99', '0.9', color);

        parts[25] = generatesStop(k0, k1, color);

        parts[26] = '</radialGradient>';

        parts[27] = generateRadialGradient('r3', RadialGradient('0.3', '0.3', '0.4', true, '0.5', '0.5', true, false));

        parts[28] = generatesStop(k1, k0, '#000000');

        parts[29] = generatesStop(k1, k1, color);

        parts[30] = '</radialGradient>';

        parts[31] = generateLinearGradient('l1', k0, k0, k1);

        parts[32] = generatesStop(k1, k0, color);

        parts[33] = generatesStop(k1, k1, '#000000');

        parts[34] = '</linearGradient>';

        parts[35] = '<rect x="0" y="0"  height="600" width="600" />';

        parts[36] = string(
            abi.encodePacked(
                '<g transform="translate(300, 300)" filter="',
                filterType == FilterType.unregular ? _url('11') : kEmpty,
                '">'
            )
        );

        parts[37] = filterType == FilterType.linearGradient
            ? generateCircle(Circle(k0, k0, 150, _url('l1'), _url(k1), k1))
            : filterType == FilterType.radialGradient
            ? generateCircle(Circle(k0, k0, 150, _url('r3'), _url(k1), k1))
            : filterType == FilterType.unregular || filterType == FilterType.pixel
            ? kEmpty
            : generateCircle(Circle(k0, k0, 150, color, _url(k1), k1));

        parts[38] = filterType == FilterType.linearGradient
            ? generateCircle(Circle(k0, k0, 150, _url('l1'), _url('2'), k1))
            : filterType == FilterType.radialGradient
            ? generateCircle(Circle(k0, k0, 150, _url('r3'), _url('2'), k1))
            : filterType == FilterType.unregular || filterType == FilterType.pixel
            ? kEmpty
            : generateCircle(Circle(k0, k0, 150, color, _url('2'), k1));

        parts[39] = filterType == FilterType.unregular
            ? generateCircle(Circle(k0, k0, 150, color, _url('11'), k1))
            : filterType == FilterType.blur
            ? generateCircle(Circle(k0, k0, 150, color, _url('3'), k1))
            : filterType == FilterType.turbulence
            ? generateCircle(Circle(k0, k0, 150, color, _url('10'), k1))
            : filterType == FilterType.pixel
            ? generateCircle(Circle(k0, k0, 150, color, _url('14'), k1))
            : filterType == FilterType.texture
            ? generateCircle(Circle(k0, k0, 150, color, _url('15'), k1))
            : filterType == FilterType.linearGradient
            ? generateCircle(Circle(k0, k0, 150, _url('l1'), kEmpty, k1))
            : filterType == FilterType.radialGradient
            ? generateCircle(Circle(k0, k0, 150, _url('r3'), kEmpty, k1))
            : generateCircle(Circle(k0, k0, 150, color, _url('9'), k1));

        parts[40] = '</g>';

        parts[41] = animateMoon(location, filterType);

        parts[42] = string(
            abi.encodePacked(
                '<g filter="',
                filterType == FilterType.unregular ? _url('11') : filterType == FilterType.pixel ? _url('14') : kEmpty,
                '">'
            )
        );

        parts[43] = animateCircle(location, Circle(k150, k300, 100, _url('r1'), '', '0.25'));

        parts[44] = animateCircle(location, Circle('225', k300, 50, _url('r2'), '', '0.25'));

        parts[45] = animateCircle(location, Circle('110', k300, 30, color, '', '0.15'));

        parts[46] = animateCircle(location, Circle('278', k300, 50, color, '', '0.1'));

        parts[47] = '</g>';

        parts[48] = string(abi.encodePacked('<g filter="', filterType == FilterType.pixel ? _url('14') : kEmpty, '">'));

        parts[49] = animateEllipse(location, Ellipase(k150, k300, 15, 35, color, _url('5'), '1'));

        parts[50] = animateEllipse(location, Ellipase(k150, k300, 25, 35, color, _url('6'), '1'));

        parts[51] = animateEllipse(location, Ellipase(k150, k300, 10, 30, color, _url('7'), '1'));

        parts[52] = '</g>';

        parts[53] = '</svg>';

        string memory output = kEmpty;
        uint256 step = 8;
        for (uint256 j; j < 6; j++) {
            output = string(
                abi.encodePacked(
                    output,
                    parts[j * step],
                    parts[j * step + 1],
                    parts[j * step + 2],
                    parts[j * step + 3],
                    parts[j * step + 4],
                    parts[j * step + 5],
                    parts[j * step + 6],
                    parts[j * step + 7]
                )
            );
        }
        output = string(abi.encodePacked(output, parts[48], parts[49], parts[50], parts[51], parts[52], parts[53]));
        return output;
    }

    function parseFilterType(FilterType ft) internal pure returns (string memory) {
        if (ft == FilterType.blur) {
            return 'blur';
        } else if (ft == FilterType.texture) {
            return 'texture';
        } else if (ft == FilterType.linearGradient) {
            return 'linear';
        } else if (ft == FilterType.radialGradient) {
            return 'radial';
        } else if (ft == FilterType.fractalNoise) {
            return 'fractal';
        } else if (ft == FilterType.turbulence) {
            return 'turbulence';
        } else if (ft == FilterType.pixel) {
            return 'pixel';
        } else if (ft == FilterType.unregular) {
            return 'unregular';
        }
        return '';
    }

    function randomWithTokenId(string memory prefix, uint256 tokenId) internal pure returns (uint256) {
        return CryptoSVGLibrary.random(string(abi.encodePacked(prefix, Strings.toString(tokenId))));
    }

    function getColor(uint256 tokenId) public pure returns (string memory) {
        uint256 rand = randomWithTokenId('color', tokenId);
        return string(abi.encodePacked('#', CryptoSVGLibrary.substring(Strings.toHexString(rand), 2, 8)));
    }

    function getType(uint256 tokenId) public pure returns (FType memory) {
        uint256 rand = randomWithTokenId('type', tokenId);
        uint256 rand1 = randomWithTokenId('baseFrequency', tokenId);
        uint256 rand2 = randomWithTokenId('numOctaves', tokenId);
        uint256 rand3 = randomWithTokenId('surfaceScale', tokenId);
        uint256 num = (rand % 100) + 1;
        uint256 num1 = (rand1 % 5) + 1;
        uint256 num2 = (rand2 % 5) + 1;
        uint256 num3 = (rand3 % 3) + 1;
        FilterType ft = FilterType.blur;
        if (num <= 25) {
            ft = FilterType.blur;
        } else if (num <= 45) {
            ft = FilterType.texture;
        } else if (num <= 60) {
            ft = FilterType.linearGradient;
        } else if (num <= 75) {
            ft = FilterType.radialGradient;
        } else if (num <= 85) {
            ft = FilterType.turbulence;
        } else if (num <= 95) {
            ft = FilterType.fractalNoise;
        } else if (num <= 98) {
            ft = FilterType.unregular;
        } else {
            ft = FilterType.pixel;
        }
        return
            FType(
                ft,
                string(abi.encodePacked('0.0', Strings.toString(num1))),
                Strings.toString(num2),
                Strings.toString(num3)
            );
    }

    function getStartingLocation(uint256 tokenId) public pure returns (uint256) {
        return randomWithTokenId('start', tokenId) % 901;
    }

    function getSpeed(uint256 tokenId) public pure returns (uint256, uint256) {
        uint256 rand = randomWithTokenId('speed', tokenId);
        uint256 speed = 1;
        uint256 num = (randomWithTokenId('level', tokenId) % 100) + 1;
        uint256 level = 6;
        if (num <= 25) {
            speed = (rand % (999 - 512)) + 513;
            level = 6;
        } else if (num <= 50) {
            speed = (rand % (512 - 256)) + 257;
            level = 5;
        } else if (num <= 75) {
            speed = (rand % (256 - 128)) + 129;
            level = 4;
        } else if (num <= 85) {
            speed = (rand % (128 - 64)) + 65;
            level = 3;
        } else if (num <= 95) {
            speed = (rand % (64 - 16)) + 17;
            level = 2;
        } else if (num <= 98) {
            speed = (rand % 16) + 1;
            level = 1;
        } else {
            speed = (rand % 9000) + 1000;
            level = 0;
        }
        return (speed, level);
    }

    function getPeriod(uint256 tokenId) public pure returns (uint256) {
        (uint256 speed, ) = getSpeed(tokenId);
        return _getPeriod(speed);
    }

    function _getPeriod(uint256 speed) internal pure returns (uint256) {
        return 900 * speed;
    }

    function packAttribute(
        string memory color,
        FType memory ft,
        uint256 level,
        uint256 period,
        uint256 startingLocation
    ) internal pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    '{"color": "',
                    string(
                        abi.encodePacked(
                            color,
                            '","type":"',
                            parseFilterType(ft.filterType),
                            '","speedLevel":"',
                            Strings.toString(level),
                            '","period":"',
                            string(abi.encodePacked(Strings.toString(period), ' block')),
                            '","startingLocation":"',
                            Strings.toString(startingLocation)
                        )
                    ),
                    '"}'
                )
            );
    }

    function _tokenURI(
        uint256 tokenId,
        FType memory ft,
        string memory color,
        uint256 location,
        uint256 startingLocation,
        uint256 speed,
        uint256 level
    ) internal pure returns (string memory) {
        int256 _location = moonLocation(location, startingLocation, speed);
        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "SVG #',
                        Strings.toString(tokenId),
                        '", "description": "", "image": "data:image/svg+xml;base64,',
                        Base64.encode(bytes(generatePlant(color, _location, ft))),
                        '","attributes": ',
                        packAttribute(color, ft, level, _getPeriod(speed), startingLocation),
                        '}'
                    )
                )
            )
        );
        return string(abi.encodePacked('data:application/json;base64,', json));
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        (uint256 speed, uint256 level) = getSpeed(tokenId);
        return
            _tokenURI(
                tokenId,
                getType(tokenId),
                getColor(tokenId),
                block.number,
                getStartingLocation(tokenId),
                speed,
                level
            );
    }

    // Private and Internal functions