abstract contract ERC721URIStorage is ERC721 {
    using Strings for uint256;

    // Optional mapping for token URIs
    mapping(uint256 => string) private _coords;
    mapping(uint256 => uint256) private _noObjects;
    mapping(uint256 => uint256) private _noColors;
    mapping(uint256 => uint[]) private _colors;

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */


    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }


    
    
    function concatenate(string memory a,string memory b) public pure returns (string memory){
        return string(abi.encodePacked(a,'',b));
    }

    function drawPoly(uint256 x1, uint256 y1, uint256 x2, uint256 y2, uint256 x3, uint256 y3, uint256 x4, uint256 y4, string memory color) private pure returns (string memory) {

        string memory poly;

        poly = 
                string(
                    abi.encodePacked(
                        '<polygon fill="', color, '" stroke="', color, '" stroke-width=".25"'
                        ' points="'
                    )
                );
        
        poly = 
                string(
                    abi.encodePacked(
                        poly,
                        uint2str(x1),',',
                        uint2str(y1),',',
                        uint2str(x2),',',
                        uint2str(y2),',',
                        uint2str(x3),',',
                        uint2str(y3),',',
                        uint2str(x4),',',
                        uint2str(y4),
                        '" />'
                    )
                );
        
        return poly;
    }

    function drawRect(uint256 x, uint256 y, uint256 w, uint256 h, string memory color) private pure returns (string memory) {
        string memory poly;

        poly = 
                string(
                    abi.encodePacked(
                        '<polygon fill="', color, '" stroke="', color, '" stroke-width=".25"'
                        ' points="'
                    )
                );
        
        poly = 
                string(
                    abi.encodePacked(
                        poly,
                        uint2str(x),',',
                        uint2str(y),',',
                        uint2str(x+w),',',
                        uint2str(y),',',
                        uint2str(x+w),',',
                        uint2str(y+h),',',
                        uint2str(x),',',
                        uint2str(y+h),
                        '" />'
                    )
                ); 
        return poly;
    }


    function makeGradient(string memory color1, string memory color2, uint256 isVertical, uint256 index, uint256 side) private pure returns (string memory) {
        return(string(
                    abi.encodePacked(
                        '<defs><linearGradient id="',
                        string(
                    abi.encodePacked(
                        'g',
                        uint2str(index),
                        '-',
                        uint2str(side)
                    )),'"',
                        ' gradientTransform="rotate(',
                        uint2str(((side + isVertical) % 2) * 90),
                        ')">',
                        '<stop offset="0%" stop-color="',
                        color1,
                        '" />',
                        '<stop offset="100%" stop-color="',
                        color2,
                        '" />',
                        "</linearGradient></defs>"
                    )
                ));
    }

    function makeGradientName(uint x, uint y) private pure returns (string memory) {
        return(string(
                    abi.encodePacked(
                        'url(#g',
                        uint2str(x),
                        '-',
                        uint2str(y),
                        ')'
                    )));
    }

    function st2num(string memory numString) public pure returns(uint) {
        uint  val=0;
        bytes   memory stringBytes = bytes(numString);
        for (uint  i =  0; i<stringBytes.length; i++) {
            uint exp = stringBytes.length - i;
            bytes1 ival = stringBytes[i];
            uint8 uval = uint8(ival);
           uint jval = uval - uint(0x30);
   
           val +=  (uint(jval) * (10**(exp-1))); 
        }
      return val;
    }

    function getSlice(uint256 begin, uint256 end, string memory text) public pure returns (string memory) {
        bytes memory a = new bytes(end-begin+1);
        for(uint i=0;i<=end-begin;i++){
            a[i] = bytes(text)[i+begin-1];
        }
        return string(a);    
    }

    function string2Arr(string memory coordsString) public view virtual returns
    (uint256[2][] memory) {
        
        uint len = bytes(coordsString).length;
        string memory tempValue = "";
        uint delimCounter = 0;
        uint commaCounter = 0;
        uint i;

        for (i=0; i < len; i++){
            if ((bytes(coordsString)[i]) == bytes(coordsString)[0]){
                commaCounter++;
            }
        }

        uint[] memory commaArray = new uint[](commaCounter);
        uint256[2][] memory coordzArray = new uint256[2][](32);
        //coordzArray[0][0] = 2;
        //coordzArray[0][1] = 1;

        uint z = 0;
        
        for (i=0; i < len; i++){
            if ((bytes(coordsString)[i]) == bytes(coordsString)[0]){
                commaArray[z] = i;
                z++;
            }
        }

        for (uint y=0; delimCounter <= commaCounter-5; y = y + 2){
            tempValue = getSlice(commaArray[delimCounter] + 2, commaArray[delimCounter+1], coordsString);
            delimCounter++;
            coordzArray[y][0] = st2num(tempValue);

            tempValue = getSlice(commaArray[delimCounter] + 2, commaArray[delimCounter+1], coordsString);
            delimCounter++;
            coordzArray[y][1] = st2num(tempValue);

            tempValue = getSlice(commaArray[delimCounter] + 2, commaArray[delimCounter+1], coordsString);
            delimCounter++;
            coordzArray[y+1][0] = st2num(tempValue);

            tempValue = getSlice(commaArray[delimCounter] + 2, commaArray[delimCounter+1], coordsString);
            delimCounter++;
            coordzArray[y+1][1] = st2num(tempValue);
        }
        return (coordzArray);
    }

    struct SlotInfo {
        string[8] colorString;
        int newX;
        uint newY;
        uint newW;
        uint newH;
        string polys;
        string poly;
        string grady;
    }

    function makeArt(string memory coordiesIn, uint256 numberOfObjects, uint[] memory colorsArray) public view virtual returns (string memory){
        SlotInfo memory slot;
        uint256 step;

        uint256[2][] memory coordies = string2Arr(coordiesIn);

        uint256 ifTwoObject = 1;
        uint256 loopCounter = 0;
        slot.colorString[0] = "Red";
        slot.colorString[1] = "Magenta";
        slot.colorString[2] = "Lime";
        slot.colorString[3] = "Yellow";
        slot.colorString[4] = "Blue";
        slot.colorString[5] = "Cyan";
        slot.colorString[6] = "Orangered";
        slot.colorString[7] = "Purple";
        if (numberOfObjects == 2) {
            ifTwoObject = 0;
        }
        slot.polys = '<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg"><polygon fill="black" points="0,0 500,0 500,500 0,500" />';
        for (step = 0; loopCounter < numberOfObjects - 1; step = step + 2){ 
            slot.poly = drawRect(coordies[step][0], coordies[step][1], coordies[step+1][0], coordies[step+1][1], slot.colorString[colorsArray[step/2]]);
            slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            slot.poly = drawRect(coordies[step+2][0], coordies[step+2][1], coordies[step+3][0], coordies[step+3][1], slot.colorString[colorsArray[step/2+1]]);
            slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            if (coordies[step][1] < coordies[step+2][1]) {
                slot.grady = makeGradient(slot.colorString[colorsArray[step/2]], slot.colorString[colorsArray[step/2+1]], 1, step, 0);
                slot.polys = string(abi.encodePacked(slot.polys, slot.grady));
                slot.poly = drawPoly(coordies[step][0], coordies[step][1], coordies[step][0] + coordies[step+1][0], coordies[step][1], coordies[step+2][0] + coordies[step+3][0], coordies[step+2][1], coordies[step+2][0], coordies[step+2][1], makeGradientName(step, 0));
                slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            } 
            if (coordies[step][0] + coordies[step+1][0] > coordies[step+2][0] + coordies[step+3][0]) {
                slot.grady = makeGradient(slot.colorString[colorsArray[step/2+1]], slot.colorString[colorsArray[step/2]], 1, step, 1);
                slot.polys = string(abi.encodePacked(slot.polys, slot.grady));    
                slot.poly = drawPoly(coordies[step][0] + coordies[step+1][0], coordies[step][1], coordies[step+2][0] + coordies[step+3][0], coordies[step+2][1], coordies[step+2][0] + coordies[step+3][0], coordies[step+2][1] + coordies[step+3][1], coordies[step][0] + coordies[step+1][0], coordies[step][1] + coordies[step+1][1], makeGradientName(step, 1));
                slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            }
            if (coordies[step][1] + coordies[step+1][1] > coordies[step+2][1] + coordies[step+3][1]) {
                slot.grady = makeGradient(slot.colorString[colorsArray[step/2+1]], slot.colorString[colorsArray[step/2]], 1, step, 2);
                slot.polys = string(abi.encodePacked(slot.polys, slot.grady));    
                slot.poly = drawPoly(coordies[step][0], coordies[step][1] + coordies[step+1][1], coordies[step][0] + coordies[step+1][0], coordies[step][1] + coordies[step+1][1], coordies[step+2][0] + coordies[step+3][0], coordies[step+2][1] + coordies[step+3][1], coordies[step+2][0], coordies[step+2][1] + coordies[step+3][1], makeGradientName(step, 2));
                slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            }
            if (coordies[step][0] < coordies[step+2][0]) {
            slot.grady = makeGradient(slot.colorString[colorsArray[step/2]], slot.colorString[colorsArray[step/2+1]], 1, step, 3);
                slot.polys = string(abi.encodePacked(slot.polys, slot.grady));
                slot.poly = drawPoly(coordies[step][0], coordies[step][1], coordies[step+2][0], coordies[step+2][1], coordies[step+2][0], coordies[step+2][1] + coordies[step+3][1], coordies[step][0], coordies[step][1] + coordies[step+1][1], makeGradientName(step, 3));
                slot.polys = string(abi.encodePacked(slot.polys, slot.poly));
            }
            loopCounter++;    
        }
        return string(abi.encodePacked(slot.polys, "</svg>"));
    }



    struct JsonInfo {
        string[8] colorString;
        string json;
        string artData;
        string attributes;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        // If there is no base URI, return the token URI.
        JsonInfo memory jsonSlot;
        jsonSlot.colorString[0] = "Red";
        jsonSlot.colorString[1] = "Magenta";
        jsonSlot.colorString[2] = "Lime";
        jsonSlot.colorString[3] = "Yellow";
        jsonSlot.colorString[4] = "Blue";
        jsonSlot.colorString[5] = "Cyan";
        jsonSlot.colorString[6] = "Orangered";
        jsonSlot.colorString[7] = "Purple";
        jsonSlot.attributes = "";
        for (uint i=0; i < _noColors[tokenId]; i++){
            jsonSlot.attributes = concatenate(jsonSlot.attributes, ',{"Trait_type":"Color_');
            jsonSlot.attributes = concatenate(jsonSlot.attributes, uint2str(i+1));
            jsonSlot.attributes = concatenate(jsonSlot.attributes, '","value":"');
            jsonSlot.attributes = concatenate(jsonSlot.attributes, (jsonSlot.colorString[_colors[tokenId][i]]));
            jsonSlot.attributes = concatenate(jsonSlot.attributes, '"}');
        }
        jsonSlot.artData = makeArt(_coords[tokenId], _noObjects[tokenId], _colors[tokenId]);
        jsonSlot.json = string(abi.encodePacked(
            '{"description":"Unhuman Compositions is a collection of 777 generative NFTs by Damjanski - each generated when you take a photo. Each NFT is fully stored and rendered on chain.","external_url":"http://unhuman.xyz"',',',
            '"name": "Unhuman Composition ',
            uint2str(tokenId),'",',
            '"image": "data:image/svg+xml;base64,'));
        jsonSlot.json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        jsonSlot.json,
                        Base64.encode(bytes(jsonSlot.artData)),'",',
                        '"attributes":[',
                        '{"Trait_type":"Property_1","value":"',
                        uint2str(_noObjects[tokenId]),
                        ' Objects"},',
                        '{"Trait_type":"Property_2","value":"',
                        uint2str(_noColors[tokenId]),
                        ' Colors"}',
                        jsonSlot.attributes,
                        ']}'
                    )
                )
            )
        );
        return string(abi.encodePacked(_baseURI(), jsonSlot.json));
    }

    /**
     * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */

    function _setCoords(uint256 tokenId, string memory _coordsPassed) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _coords[tokenId] = _coordsPassed;
    }

    function _setNoObjects(uint256 tokenId, uint256 _noObjectsPassed) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _noObjects[tokenId] = _noObjectsPassed;
    }

    function _setNoColors(uint256 tokenId, uint256 _noColorsPassed) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _noColors[tokenId] = _noColorsPassed;
    }

    function _setColors(uint256 tokenId, uint256[] memory _colorsPassed) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _colors[tokenId] = _colorsPassed;
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual override {
        super._burn(tokenId);
    }
}
/**
 * @dev ERC721 token with storage based token URI management.
 */
function _burn(uint256 tokenId) internal virtual override {
    super._burn(tokenId);
}

/**
 * @dev Destroys `tokenId`.
 * The approval is cleared when the token is burned.
 *
 * Requirements:
 *
 * - `tokenId` must exist.
 *
 * Emits a {Transfer} event.
 */