// Contract: MESHTT1
// Target function: function getRandBase(uint256 tokenId) private view returns (bytes20)
        }
        return string(result);
    }    
    
    function compareStrings(string memory a, string memory b) internal pure returns (bool) {
        return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
    }
    
    // nice solution: https://ethereum.stackexchange.com/questions/56319/how-to-convert-bytes32-to-string
    function toShortString(bytes32 _data) internal pure returns (string memory) {
      bytes memory _bytesContainer = new bytes(32);
      uint256 _charCount = 0;
      // loop through every element in bytes32
      for (uint256 _bytesCounter = 0; _bytesCounter < 32; _bytesCounter++) {
        bytes1 _char = bytes1(bytes32(uint256(_data) * 2 ** (8 * _bytesCounter)));
        if (_char != 0) {
          _bytesContainer[_charCount] = _char;
          _charCount++;
        }
      }
    
      bytes memory _bytesContainerTrimmed = new bytes(_charCount);
    
      for (uint256 _charCounter = 0; _charCounter < _charCount; _charCounter++) {
        _bytesContainerTrimmed[_charCounter] = _bytesContainer[_charCounter];
      }
    
      return string(_bytesContainerTrimmed);
    }    
    
}

contract externalNft {
    function balanceOf(address owner) external view returns (uint256 balance) {}
}

/**
 * @title "The Mesh" by Takens Theorem contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
contract MESHTT1 is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    uint256 private MAX_SUPPLY = 72;
    function totalSupply() external view returns (uint256) {
        return MAX_SUPPLY;
    }
    string[] private romans = ["n","i","ii","iii","iv","v","vi","vii","viii","ix"];
    uint256 public lastTokenMsg = 1;         
    uint256 public lastMsgHeight = block.number;        
    uint256 public curCharity = 0;
    uint256[] private theDims = [2,36,3,24,4,18,8,8,9,9,12,12,6,6];
    string[] private theCols = ["#FF0000","#FF8B00","#E8FF00","#5DFF00","#00FF2E","#00FFB9",
                                "#00B9FF","#002EFF","#5D00FF","#E800FF","#FF008B"];
    string private svg_start = "<?xml version='1.0' encoding='UTF-8'?><svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='750' width='750' viewBox='0 0 4000 4000'><rect x='0' y='0' fill='#000000' width='4000' height='4000' />";

    /************************************************
    
    NODES = functions, information about individual tokens 
    
    */
    struct Node {
        uint256 tokenMsg;
        uint256 desiredCharity; // limited to 0-7
        uint256 totMsgs;
    }
    function addNode(uint256 tokenId, uint256 charityId) private {
        Node memory node = Node(tokenId % 10, charityId, 0);
        nodes[tokenId] = node;
    }
    mapping (uint256 => Node) private nodes; // indexed 1, ..., n
    function viewNode(uint256 tokenId) external view returns (Node memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        return(nodes[tokenId]);
    }
    function makePost(uint256 tokenId, uint256 msgContent) external payable {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not owner nor approved");
        require(msgContent < 10,"ERROR: message must be from 0 to 9");
        require(msg.value >= 1000000000000000 && msg.value <= 50000000000000000, 
            "ERROR: range 0.001 - 0.05 ETH required to post");

        nodes[tokenId].tokenMsg = msgContent;
        nodes[tokenId].totMsgs++;
        lastTokenMsg = tokenId;
        lastMsgHeight = block.number;
        payable(charities[curCharity].addr).transfer(msg.value); // send to charity
    }    

    /************************************************
    
    CHARITY = posting requires small donation; "The Mesh" votes on which 
    
    */
    struct Charity {
        address addr;
        string name;
        uint256 votes;
    }    
    function addCharity(address addr, string memory name) private {
        Charity memory charity = Charity(addr, name, 0);        
        charities.push(charity);
    }
    Charity[] private charities; // indexed 0, ..., n - 1 
    function viewCharity(uint256 i) external view returns (Charity memory) {
        require(i < charities.length,"ERROR: out of bounds");
        return(charities[i]);
    }
    function updateCharity() external {
        uint256 curMax = 0;
        uint256 curCharityId;
        for (uint256 i = 0; i < charities.length; i++) {
            if (charities[i].votes > curMax) {
                curMax = charities[i].votes;
                curCharityId = i;
            }
        }
        curCharity = curCharityId;
    }
    function voteCharity(uint256 tokenId, uint256 charityId) external {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not owner nor approved");
        require(charityId < charities.length,"ERROR: charity ID must be 0 - 7");

        charities[nodes[tokenId].desiredCharity].votes--;
        nodes[tokenId].desiredCharity = charityId;
        charities[charityId].votes++;        
    }    

    /************************************************
    
    SVG drawing functions (line, element, text)
    
    */
    function drawLn(uint256 x1,uint256 y1,uint256 x2,uint256 y2,string memory stroke) private pure returns (string memory) {
        return string(abi.encodePacked(
            "<line x1='",Strings.toString(x1),
            "' y1='",Strings.toString(y1),
            "' x2='",Strings.toString(x2),
            "' y2='",Strings.toString(y2),
            "' stroke='",stroke,
            "' stroke-width='15pt' />")
        );
    }

    function drawEl(uint256 typ, uint256 x, uint256 y, uint256 sz, string memory stroke, string memory fill) private pure returns (string memory) {
        string memory output = '';
        if (typ==0) {
            output = string(abi.encodePacked(
                "<circle cx='",Strings.toString(x),
                "' cy='",Strings.toString(y),
                "' r='",Strings.toString(sz),
                "' "
            ));  
        } else {
            string memory sign_x = sz > x ? // control rects at edge
                string(abi.encodePacked('-',Strings.toString(sz - x))) :
                string(abi.encodePacked(Strings.toString(x - sz)));
            string memory sign_y = sz > y ? 
                string(abi.encodePacked('-',Strings.toString(sz - y))) :
                string(abi.encodePacked(Strings.toString(y - sz)));
            output = string(abi.encodePacked(
                "<rect x='",sign_x,
                "' y='",sign_y,
                "' width='",Strings.toString(2*sz),
                "' height='",Strings.toString(2*sz),
                "' "
            ));  
        }        
        return(string(abi.encodePacked(output,
            "fill='",fill,"' stroke='",stroke,"' stroke-width='5pt' />")));
    }

    function drawTxt(string memory txt, uint256 x, uint256 y, string memory stroke, string memory fill) private view returns (string memory) {
        return string(abi.encodePacked(
            "<text x='",Strings.toString(x),
            "' y='",Strings.toString(y),
            "' font-size='40pt' fill='",fill,
            c[1],stroke,"'>",
            txt,"</text>"
        ));
    }

    function xl(uint256 i, uint256 tokenId) private view returns (uint256) {
        uint256 ix = (lastMsgHeight + vl(1,tokenId)) % theDims.length;
        return((i-1) % theDims[ix] * 
            4000/theDims[ix] + 
            4000/theDims[ix]/2);
    }
    function yl(uint256 i, uint256 tokenId) private view returns (uint256) {
        uint256 ix = (lastMsgHeight + vl(1,tokenId)) % theDims.length;
        return((i - 1) / theDims[ix] * 
            4000/(MAX_SUPPLY/theDims[ix]) + 
            4000/(MAX_SUPPLY/theDims[ix])/2);
    }    

    /************************************************
    
    functions needed for graph
    
    */
    function getNeighbors(uint256 tokenId) public view returns (uint256, uint256, uint256[] memory, uint256[] memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        (uint256 totToks, uint256 totProjs, uint256[] memory refOwn) = getNftBalance(ownerOf(tokenId));
        uint256[] memory neighbors = new uint256[](_tokenIds.current());
        uint256 ix = 0;
        for (uint i = 1; i <= _tokenIds.current(); i++) {
            (,, uint256[] memory compOwn) = getNftBalance(ownerOf(i));
            for (uint j = 0; j < compOwn.length; j++) {
                if (compOwn[j]>0 && refOwn[j]>0) {
                    neighbors[ix] = i;
                    ix++;
                    break;
                }
            }
        }
        return(totToks, totProjs, refOwn, neighbors);
    } 

    function getNftBalance(address addr) public view returns (uint256, uint256, uint256[] memory) { 
        uint256[] memory projCounts = new uint256[](5);

        // mainnet
        projCounts[0] = externalNft(0x06012c8cf97BEaD5deAe237070F9587f8E7A266d).balanceOf(addr);  // CryptoKitties
        projCounts[1] = externalNft(0xFBeef911Dc5821886e1dda71586d90eD28174B7d).balanceOf(addr);  // KnownOrigin
        projCounts[2] = externalNft(0x79986aF15539de2db9A5086382daEdA917A9CF0C).balanceOf(addr);  // Cryptovoxels
        projCounts[3] = externalNft(0xF3E778F839934fC819cFA1040AabaCeCBA01e049).balanceOf(addr);  // Avastars 
        projCounts[4] = externalNft(0xa7d8d9ef8D8Ce8992Df33D8b8CF4Aebabd5bD270).balanceOf(addr);  // Art Blocks

        uint256 totToks = 0;
        uint256 totProjs = 0;
        for (uint256 i = 0; i < projCounts.length; i++){
            totToks = totToks + projCounts[i];
            if (projCounts[i] > 0) {
                totProjs = totProjs + 1;    
            }
        }

        return(totToks, totProjs, projCounts);
    }

    string ncp = 'WAIT';
    string[] private c = ['', ''];
    function setStr(string memory val) external onlyOwner {
        require(bytes(ncp).length > 0, 'ERROR: Already configured');
        if (bytes(c[0]).length == 0) {
            c[0] = val;
        } else if (bytes(c[1]).length == 0) {
            c[1] = val;
        } else if (bytes(ncp).length > 0) {
            ncp = val;        
        }
    }   

    /************************************************
    
    general functions for The Mesh
    
    */
    function renderDescription(uint256 tokenId) private view returns (string memory) {
        return string(abi.encodePacked("",
            "The crypto panopticon reveals.\\n\\n",
            "Last refreshed at block ", Strings.toString(block.number), "\\n\\n",
            c[0],
            Strings.toAsciiString(ownerOf(tokenId)))); 
    }

    function mintNFT_n(uint256 n) public onlyOwner {
        require(_tokenIds.current() < MAX_SUPPLY,'ERROR: minting complete');
        for (uint i = 0; i < n; i++) {
            if (_tokenIds.current() == MAX_SUPPLY) { // avoid accidental overring
                break;
            }
            _tokenIds.increment();
            uint256 newItemId = _tokenIds.current();

            uint assignCharity = (block.number + i) % charities.length;
            charities[assignCharity].votes++;
            addNode(newItemId, assignCharity);

            _mint(msg.sender, newItemId);
        }
    }

    // raw svg
    function reveal(uint256 tokenId) public view returns (string memory) {
        require(bytes(ncp).length == 0, "ERROR: Misconfigured");
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        
        (uint256 totToks, uint256 totProjs,, uint256[] memory neighbors) = getNeighbors(tokenId);        
        
        string memory els = '';
        string memory theCol;        

        for (uint i = 1; i <= MAX_SUPPLY; i++) {
            theCol = string(abi.encodePacked(theCols[vl(i % (2*totProjs+1), tokenId) % theCols.length],"66"));
            els = string(abi.encodePacked(els,
                drawEl(
                    (lastMsgHeight + vl(2,tokenId)) % 2,
                    xl(i,tokenId),
                    yl(i,tokenId),
                    50+(totToks+1)*30 > 700 ? 700 : 50+(totToks+1)*30,
                    "#00000000",theCol
                )
            ));    
        }

        for (uint i = 0; i < neighbors.length; i++) {
            if (neighbors[i]>0 && neighbors[i]!=tokenId) {
                theCol = nodes[neighbors[i]].tokenMsg==nodes[tokenId].tokenMsg ? "#ffffffff" : "#ffffff44";
                
                els = string(abi.encodePacked(els,
                    drawEl(
                        (lastMsgHeight + vl(2,tokenId)) % 2,
                        xl(neighbors[i],tokenId),
                        yl(neighbors[i],tokenId),
                        50,"#00000000",theCol
                    ),
                    drawLn(
                        xl(tokenId,tokenId),
                        yl(tokenId,tokenId),
                        xl(neighbors[i],tokenId),
                        yl(neighbors[i],tokenId),
                        theCol
                    )                    
                ));    
            } else if (neighbors[i]==0) {
                break;
            }
        }
        
        els = string(abi.encodePacked(els,
            drawEl(
                (lastMsgHeight + vl(2,tokenId)) % 2,
                xl(tokenId,tokenId),yl(tokenId,tokenId),
                50+(totToks+1)*35 > 800 ? 800 : 50+(totToks+1)*35,
                "#00000000","#ffffff77"
            )                   
        ));
        els = string(abi.encodePacked(els,
            drawEl(
                (lastMsgHeight + vl(2,tokenId)) % 2,
                xl(tokenId,tokenId),yl(tokenId,tokenId),
                60,
                "#00000000","#000000"
            )                   
        ));
        els = string(abi.encodePacked(els,
            drawTxt(
                romans[nodes[tokenId].tokenMsg],xl(tokenId,tokenId),yl(tokenId,tokenId)+13,
                "#ffffff","#ffffff"
            )                   
        ));
        els = string(abi.encodePacked(els,
            drawEl(
                (lastMsgHeight + vl(2,tokenId)) % 2,
                xl(lastTokenMsg,tokenId),yl(lastTokenMsg,tokenId),
                60,
                "#ffffff","#00000000"
            )                   
        ));        
        
        bytes memory _img = abi.encodePacked(svg_start,els,
                 '</svg>'
             );
        return string(_img);
    }

    // pseudorandom value
    function vl(uint256 i, uint256 tokenId) private view returns (uint256) {
        bytes20 baseContent = getRandBase(tokenId);
        return uint256(uint8(baseContent[i]));
    }