// Contract: PooperRenderer
// Target function: function _read(address ptr) private view returns(bytes memory)
not owner.");
    }

    function _onlyHasCallerRole() private view {
        require(_hasCallerRole[msg.sender], "msg.sender !_hasCallerRole.");
    }

    function _onlyLinkedContract() private view {
        require(bytes(abi.encodePacked(_contractLink[msg.sender])).length > 0, "caller not linked.");
    }

    function grantCallerRole(address account) external {
        _onlyOwner();
        _grantCallerRole(account);
    }

    function _grantCallerRole(address account) private {
        _hasCallerRole[account] = true;
    }

    function linkContract(address contract_, bytes32 imageScriptPtrName) external {
        _onlyOwner();
        _linkContract(contract_, imageScriptPtrName);
    }
    
    function _linkContract(address contract_, bytes32 imageScriptPtrName) private {
        _contractLink[contract_] = imageScriptPtrName;
    }

    string constant SVG_HEADER = ""
"    <svg version=\"1.1\""
"     baseProfile=\"full\""
"     viewBox=\"0 0 100 100\""
"     xmlns=\"http://www.w3.org/2000/svg\""
"     >"
     "";

    string constant SVG_FOOTER = "</svg>";

    function _getSeed(uint256 tokenId) private view returns(uint256) {
        address caller = msg.sender;
        return uint256(keccak256(abi.encodePacked(tokenId, caller)));
    }

    function getMaxDistinctTokens() external returns(uint256) {
        _onlyHasCallerRole();
        _onlyLinkedContract();
        address caller = msg.sender;
        return _getMaxDistinctTokens(_contractLink[msg.sender]);
    }

    function getMaxDistinctTokens(bytes32 name) external returns(uint256) {
        _onlyOwner();
        return _getMaxDistinctTokens(name); 
    }

    function _getMaxDistinctTokens(bytes32 name) private returns(uint256 tally) {
        ImageScript memory script = abi.decode(_read(_imageScriptPtr[name]), (ImageScript));
        uint256 elementIterPtrsLength = script.styleIterPtrs.length;
        Elements memory elements;
        tally = 1;
        for (uint256 i; i < elementIterPtrsLength; ++i) {
            elements = abi.decode(_read(script.styleIterPtrs[i]), (Elements));
            tally *= _getMaxDistinctTokens(elements);
        }
        elementIterPtrsLength = script.elementIterPtrs.length;
        for (uint256 i; i < elementIterPtrsLength; ++i) {
            elements = abi.decode(_read(script.elementIterPtrs[i]), (Elements));
            tally *= _getMaxDistinctTokens(elements);
        }
    }

    function _getMaxDistinctTokens(Elements memory e) private returns(uint256 tally) {
        Element memory element;
        uint256 elementsLength = e.elements.length;
        tally = elementsLength; // this is the most crucial line in this count
        for (uint256 i; i < elementsLength; ++i) {
            element = e.elements[i];
            tally *= (e.fills.length > 0) ? e.fills.length : 1;

            tally *= (element.groupingOrPath) ? _getMaxDistinctTokens(element) : 1;
        }
    }

    function _getMaxDistinctTokens(Element memory e) private returns(uint256) {
        if (e.groupingOrPath) {
            address[] memory toGroupPtrs = abi.decode(e.d, (address[]));
            
            uint256 toGroupPtrsLength = toGroupPtrs.length;
            Elements memory elts;
            uint256 tally = 1;
            for (uint256 i; i < toGroupPtrsLength; ++i) {
                elts = abi.decode(_read(toGroupPtrs[i]), (Elements));
                tally *= _getMaxDistinctTokens(elts);
            }
            return tally;
        }
        revert("_getMaxDistinctTokens: only for groupings.");
    }

    function renderAttributes(uint256 tokenId) external view returns(string memory) {
        _onlyHasCallerRole();
        _onlyLinkedContract();
        
        ImageScript memory script = abi.decode(_read(_imageScriptPtr[_contractLink[msg.sender]]), (ImageScript));

        uint256 seed = _getSeed(tokenId);
        return _getAttributes(script, seed); // "csv", so whomever ingests may need to ignore last comma
    }

    // encoding is done by caller
    function renderSVG(uint256 tokenId) external view returns(string memory) {
        _onlyHasCallerRole();
        _onlyLinkedContract();
        return string(_renderSVG(tokenId)); 
    }

    function _renderSVG(uint256 tokenId) private view returns(bytes memory) {

        ImageScript memory script = abi.decode(_read(_imageScriptPtr[_contractLink[msg.sender]]), (ImageScript));

        uint256 seed = _getSeed(tokenId);

        return abi.encodePacked(
            SVG_HEADER,
            _getStyles(script, seed),
            _getPaths(script, seed),
            SVG_FOOTER 
        );
    }

    function _getStyles(ImageScript memory script, uint256 seed) private view returns(string memory) {
        require(script.motionPtr != address(0), "motionPtr not set.");
        string memory style;
        uint256 styleIterPtrsLength = script.styleIterPtrs.length;
        Elements memory e;
        Element[] memory elts;
        for (uint256 i; i < styleIterPtrsLength; ++i) {
            e = abi.decode(_read(script.styleIterPtrs[i]), (Elements));
            elts = e.elements;
            style = string(abi.encodePacked(style, _choose(elts, seed, e.id, "element").d));
        }
        return string(abi.encodePacked("<style>",
                      _read(script.motionPtr),
                      style, 
                      "</style>"));    
    }

    function _getPaths(ImageScript memory script, uint256 seed) private view returns(string memory ret) {
        uint256 elementIterPtrsLength = script.elementIterPtrs.length;
        for (uint256 i; i < elementIterPtrsLength; ++i) {
            Elements memory elements = abi.decode(_read(script.elementIterPtrs[i]), (Elements));
            ret = string(abi.encodePacked(ret, _toString(elements, seed)));
        }
    }

    function _toString(Elements memory e, uint256 seed) private view returns(string memory ret) {
      
        Element memory element = _choose(e.elements, seed, e.id, "element");
        bool groupingOrPath = element.groupingOrPath;
        (string memory prefix, 
         string memory prePostfix, 
         string memory postfix) = (groupingOrPath) ? ("<g ", ">", "</g>") : ("<path ", "", "/>"); 
        return string(abi.encodePacked(
            prefix,
            "id=\"", _toString(e.id), 
            "\" class=\"", e.class,
            "\" fill=\"", _toString(_choose(e.fills, seed, e.id, "fill")), "\" ",
            prePostfix,
            _toString(element, seed),
            postfix
        ));
    }

    // can this be more efficient??
    function _toString(bytes32 b) private pure returns(string memory ret) {
        if (b == 0x0) return "";
        ret = string(abi.encodePacked(b)); 
        unchecked{
        for (uint256 i = 1; i < 32; ++i) {
            if (bytes(ret)[i] == 0x0) {
                assembly {
                    mstore(ret, i)
                } 
                break;
            }
        } 
        }//uc
    }

    function _toString(Element memory e, uint256 seed) private view returns(string memory) {
        if (e.groupingOrPath) {
            address[] memory toGroupPtrs = abi.decode(e.d, (address[]));
            
            string memory ret;
            uint256 toGroupPtrsLength = toGroupPtrs.length;
            Elements memory elts;
            for (uint256 i; i < toGroupPtrsLength; ++i) {
                elts = abi.decode(_read(toGroupPtrs[i]), (Elements));
                ret = string(abi.encodePacked(ret, _toString(elts, seed))); 
            }
            return ret;
        }
        return string(abi.encodePacked("d=\"", e.d, "\" "));
    }

    // opensea compatibility
    // note: using _toString() for bytes32 elements is CRUCIAL for decoding in the browser
    function _traitTypeString(bytes32 traitType, bytes32 value) private pure returns(string memory) {
        return string(abi.encodePacked(
          "{\"trait_type\": \"", 
          _toString(traitType), 
          "\", \"value\": \"", 
          _toString(value),"\"}"
        )); 
    }
    
    function _traitTypeString(string memory traitType, bytes32 value) private pure returns(string memory) {
        return string(abi.encodePacked(
            "{\"trait_type\":\"", traitType, 
            "\",\"value\":\"", _toString(value), "\"}"
        )); 
    }

    function _toAttributeString(Elements memory e, uint256 seed) private view returns(string memory ret) {
        Element memory element = _choose(e.elements, seed, e.id, "element");
        if (element.name != 0x0)
            ret = string(abi.encodePacked(_traitTypeString(e.id, element.name), ","));
        if (e.fills.length > 1) 
            ret = string(abi.encodePacked(
                            ret,
                            _traitTypeString(
                            string(abi.encodePacked(_toString(e.id), "-fill")), 
                            _choose(e.fills, seed, e.id, "fill")), 
                            ","));

        bool groupingOrPath = element.groupingOrPath;
        if (groupingOrPath) 
            ret = string(abi.encodePacked(ret, _toAttributeString(element, seed)));

    }

    function _toAttributeString(Element memory e, uint256 seed) private view returns(string memory ret) {
        if (e.groupingOrPath) {
            address[] memory toGroupPtrs = abi.decode(e.d, (address[]));
            
            uint256 toGroupPtrsLength = toGroupPtrs.length;
            Elements memory elts;
            for (uint256 i; i < toGroupPtrsLength; ++i) {
                elts = abi.decode(_read(toGroupPtrs[i]), (Elements));
                ret = string(abi.encodePacked(ret, _toAttributeString(elts, seed))); 
            }
            return ret;
        }
        revert("_toAttributeString: only for groupings.");
    }

    function _getAttributes(ImageScript memory script, uint256 seed) private view returns(string memory ret) {
        uint256 elementIterPtrsLength = script.styleIterPtrs.length;
        Elements memory elements;
        for (uint256 i; i < elementIterPtrsLength; ++i) {
            elements = abi.decode(_read(script.styleIterPtrs[i]), (Elements));
            ret = string(abi.encodePacked(ret, _toAttributeString(elements, seed)));
        }
        elementIterPtrsLength = script.elementIterPtrs.length;
        for (uint256 i; i < elementIterPtrsLength; ++i) {
            elements = abi.decode(_read(script.elementIterPtrs[i]), (Elements));
            ret = string(abi.encodePacked(ret, _toAttributeString(elements, seed)));
        }
    }

    function _choose(Element[] memory e, uint256 seed, bytes32 id, string memory name) private pure returns(Element memory) {
        seed = uint256(keccak256(abi.encode(seed, id, name))); // not packed :)
        return e[seed % e.length]; // e should NEVER have length 0
    }

    function _choose(bytes32[] memory e, uint256 seed, bytes32 id, string memory name) private pure returns(bytes32) {
        if (e.length == 0) return ""; // this is used for "fill" and things that can be "null"
        seed = uint256(keccak256(abi.encode(seed, id, name))); // not packed :)
        return e[seed % e.length];
    }

    // hub stuff

    function _setImageScript(bytes memory compressedImageScript, bytes32 name) private {
        _imageScriptPtr[name] = SSTORE2.write(compressedImageScript);
    }

    // motion
    function setMotion(bytes calldata compressedBStyle, bytes32 id) external {
        _onlyOwner();
        _id2StringPtr[id] = SSTORE2.write(compressedBStyle);
    }

    function getStringPtr(bytes32 id) external view returns(address) {
        _onlyOwner();
        return _getStringPtr(id);
    }

    function _getStringPtr(bytes32 id) private view returns(address) {
        return _id2StringPtr[id];
    }

    // style
    function formatStyle(Elements[] memory eltss, bytes32[] memory ids) external returns(bytes memory) {
        _onlyOwner();
        uint256 length = eltss.length;
        require(length == ids.length, "wrong lens.");
        bytes[] memory preRet = new bytes[](length);
        for (uint256 i; i < length; ++i) {
            preRet[i] = abi.encode(ids[i], Compression.compressZeros(abi.encode(eltss[i])));
        }
        return abi.encode(preRet);
    }
    
    function setStyle(bytes calldata formatted) external {
        _onlyOwner();
        bytes[] memory bArr = abi.decode(formatted, (bytes[]));
        uint256 length = bArr.length;
        bytes32 id;
        bytes memory compressedBStyle;
        for (uint256 i; i < length; ++i) {
            (id, compressedBStyle) = abi.decode(bArr[i], (bytes32, bytes)); 
            _setStyle(compressedBStyle, id);
        }
    }

    function _setStyle(bytes memory compressedBStyle, bytes32 id) private {
        _id2StylePtr[id] = SSTORE2.write(compressedBStyle);
    }

    function getStylePtr(bytes32 id) external view returns(address) {
        _onlyOwner();
        return _getStylePtr(id);
    }
    
    function _getStylePtr(bytes32 id) private view returns(address) {
        return _id2StylePtr[id];
    }

    // elements
    function formatElementss(bytes calldata bbE) external view returns(ElementssFormatted memory ret) {
        _onlyOwner();
        bytes[] memory bE = abi.decode(bbE, (bytes[]));
        return _formatElementss(bE);
    }

    function _formatElementss(bytes[] memory bE) private view returns(ElementssFormatted memory ret) {
        // using bytes for ease on js layer
        uint256 len = bE.length;
        bytes32[] memory ids = new bytes32[](len); 
        Elements memory e;
        bytes[] memory compressedBEltss = new bytes[](len);
        for (uint256 i; i < len; ++i) {
            e = abi.decode(bE[i], (Elements));
            require(e.id != 0x0, "elements need id!!");
            ids[i] = e.id;
            compressedBEltss[i] = Compression.compressZeros(abi.encode(_format(e)));
        }
        ret.ids = ids;
        ret.compressedBE = compressedBEltss;
    }

    function _format(Elements memory e) private view returns(Elements memory ret) {
        Element[] memory elts = e.elements; 
        uint256 eltsLength = elts.length;
        Element[] memory retElts = new Element[](eltsLength); 
        for (uint256 i; i < eltsLength; ++i) {
            retElts[i] = _format(elts[i]);
        }
        ret.id = e.id;
        ret.fills = e.fills;
        ret.elements = retElts;
        ret.class = e.class;
    }

    function _format(Element memory e) private view returns(Element memory ret) {
        bytes memory retData;
        if (e.groupingOrPath) {
            bytes32[] memory ids = abi.decode(e.d, (bytes32[])); 
            uint256 idsLength = ids.length;
            address[] memory retPtrs = new address[](idsLength);
            for (uint256 i; i < idsLength; ++i) {
                retPtrs[i] = _getElementsPtr(ids[i]);
            }
            retData = abi.encode(retPtrs);
        } else {
            retData = e.d;
        }
        ret.name = e.name;
        ret.groupingOrPath = e.groupingOrPath;
        ret.d = retData;
    }

    function setElementss(ElementssFormatted calldata ef) external {
        _onlyOwner();
        // note this is unchecked where the mapping can be overwritten
        uint256 eLength = ef.ids.length;
        bytes[] memory compressedBE = ef.compressedBE;
        unchecked{
        for (uint256 i; i < eLength; ++i) {
            _id2ElementsPtr[ef.ids[i]] = SSTORE2.write(compressedBE[i]);
        }
        }//uc
    }

    function getElementsPtr(bytes32 id) external view returns(address) {
        _onlyOwner();
        return _getElementsPtr(id);
    } 
    
    function _getElementsPtr(bytes32 id) private view returns(address) {
        return _id2ElementsPtr[id];
    } 

    // callStatic!!
    function buildImageScript(bytes32 motion, bytes32[] memory styles, bytes32[] memory ids) external view returns(bytes memory) {
        _onlyOwner();
        ImageScript memory _is;
        _is.motionPtr = _getStringPtr(motion);
        
        _is.styleIterPtrs = new address[](styles.length);
        for (uint256 i; i < styles.length; ++i) {
            _is.styleIterPtrs[i] = _getStylePtr(styles[i]); 
        }
        _is.elementIterPtrs = new address[](ids.length);
        for (uint256 i; i < ids.length; ++i) {
            _is.elementIterPtrs[i] = _getElementsPtr(ids[i]); 
        }
        return Compression.compressZeros(abi.encode(_is));
    }

    function setImageScript(bytes memory compressedImageScript/*ImageScript memory _is*/, bytes32 name, address caller) external {
        _onlyOwner();
        _setImageScript(compressedImageScript, name);
        _linkContract(caller, name);
        _grantCallerRole(caller);
    }