// Contract: PooperRenderer
// Target function: function renderSVG(uint256 tokenId) external view returns(string memory)
load(byte(28, input))),
                        shr(6, or(
                            and(m, mload(byte(29, input))),
                            shr(6, or(
                                and(m, mload(byte(30, input))),
                                shr(6, mload(byte(31, input)))
                            ))
                        ))
                    ))

                    ptr := add(ptr, 3)

                    if iszero(lt(data, end)) { break }
                }

                // Allocate the memory for the string.
                // Add 32 + 31 and mask with `not(31)` to round the
                // free memory pointer up the next multiple of 32.
                mstore(0x40, and(add(add(result, decodedLength), 63), not(31)))

                // Restore the zero slot.
                mstore(0x60, 0)
            }
        }
    }
}

// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

interface IRenderer {
    function renderAttributes(uint256 tokenId) external view returns(string memory);
    function renderSVG(uint256 tokenId) external view returns(string memory);
}

contract ImageScripterSSTORE2 {

    mapping(bytes32 => address) internal _imageScriptPtr;

    // hub stuff
    mapping(bytes32 => address) _id2ElementsPtr; // this is powerful. use this.

    mapping(bytes32 => address) _id2StringPtr; // this is powerful. use this.

    mapping(bytes32 => address) _id2StylePtr; // this is powerful. use this.
    // hub stuff
}

struct Element {
    bytes32 name; // for attributes tie {id <--> name}
    bool groupingOrPath; // grouping means d decodes to Elements[] iterate
    bytes d; 
    // data or 
    //   - array of ids bytes32[]: id -> ptr(Elements) // during construction
    //   or 
    //   - array of ptrs address[]: ptr -> Elements
}

struct Elements {
    bytes32 id; 
    bytes32[] fills; // vary
    Element[] elements; // vary
    string class; // may be longer thant 32
}

struct ImageScript {
    //<style>

    address motionPtr; // ptr -> string

    address[] styleIterPtrs; // M: ptr -> Elements  //M: ptr -> string[]
    //</style>

    address[] elementIterPtrs; // M: ptr -> Elements
}

    
// for efficiency
struct ElementssFormatted {
    bytes32[] ids;
    bytes[] compressedBE; // compressed byte Elements
}

library Compression {

    struct Segment {
        uint256 start;
        uint256 end;
    }

    uint256 constant FLAG_IS_NOT_COMPRESSED = 0x1;
    uint256 constant FLAG_IS_COMPRESSED = 0x2;

    uint256 constant FLAG_ZERO_MASK = 0x0f;
    uint256 constant FLAG_ZERO = 0x03;
    uint256 constant FLAG_NONZERO = 0x04;

    uint256 constant FLAG_LENGTH_MASK = 0xf0;
    uint256 constant FLAG_LENGTH_UINT8 = 0x10;
    uint256 constant FLAG_LENGTH_UINT16 = 0x20;
    uint256 constant FLAG_LENGTH_UINT32 = 0x30;
    uint256 constant FLAG_LENGTH_UINT64 = 0x40;

    // just compresses all zeros 
    function compressZeros(bytes memory input) internal view returns(bytes memory ret) {
        uint256 inputLength = input.length;
        ret = new bytes(inputLength*3); // overdo it
        uint256 retIdx;

        uint256 idx;

        // first find consecutive zeros
        
        uint256 _byte;
        bool inZeroSegment;
        Segment[] memory zeroSegments = new Segment[](inputLength);

        // collect all zero segments
        for (uint256 i; i < inputLength; ++i) {
            _byte = uint256(uint8(input[i]));

            if (_byte == 0) {
                if (!inZeroSegment) { // start of zero segment
                    zeroSegments[idx].start = i;
                    inZeroSegment = true;
                }
            } else {
                if (inZeroSegment) { // is end of zero segment
                    zeroSegments[idx].end = i;
                    inZeroSegment = false;
                    ++idx;
                }
            }
        }
        
        assembly {
            mstore(zeroSegments, idx)
        }

        // we now know all the zero segments
        Segment memory zs;
        uint256 start;
        uint256 end;
        uint256 length;

        for (uint256 i; i < idx; ++i) {
            zs = zeroSegments[i]; 
            end = zs.start;
            ret[retIdx] = bytes1(uint8(FLAG_NONZERO));
            length = end-start;

            retIdx = _setLength(ret, retIdx, length);
            for (uint256 j = start; j < end; ++j) {
                ret[retIdx++] = input[j];
            }
            start = zs.end;

            // zeros
            ret[retIdx] = bytes1(uint8(FLAG_ZERO));
            length = zs.end-zs.start;
            retIdx = _setLength(ret, retIdx, length);
        }
        
        ret[retIdx] = bytes1(uint8(FLAG_NONZERO));
        length = inputLength-zs.end;
        retIdx = _setLength(ret, retIdx, length);
        for (uint256 i = zs.end; i < inputLength; ++i) {
            ret[retIdx++] = input[i];
        }
        if (retIdx < input.length) { // compression was favorable
            ret[retIdx++] = bytes1(uint8(FLAG_IS_COMPRESSED));
            assembly {
                mstore(ret, retIdx)
            }
        } else { // compression was NOT an improvement
            ret = new bytes(inputLength+1);
            assembly {
                mstore(ret, 0)
            }
            _append(ret, input);
            assembly {
                mstore(ret, add(inputLength, 1))
            }
            ret[inputLength] = bytes1(uint8(FLAG_IS_NOT_COMPRESSED));
        }
    } 

    function decompressZeros(bytes memory input) internal pure returns(bytes memory ret) {
        // assumes input is output of `compressZeros`
        uint256 inputLength = input.length;
        require(inputLength > 0, "invalid input len.");
        uint256 isCompressed = uint256(uint8(input[inputLength-1]));
        assembly {
            mstore(input, sub(mload(input), 1))
        }
        if (isCompressed == FLAG_IS_NOT_COMPRESSED) return input;
        --inputLength;

        uint256 totalLength;
        uint256 length;
        uint256 idx;
        uint256 flag;
        
        while (idx < inputLength) {
            flag = uint256(uint8(input[idx])) & FLAG_ZERO_MASK;
            (length, idx) = _getLength(input, idx);
            if (length == 0) {
                continue;
            }
            totalLength += length; 
            if (flag == FLAG_NONZERO){
                idx += length;
            }
        }
        idx = 0;
        ret = new bytes(totalLength);
        uint256 retIdx;
        while (idx < inputLength) {
            flag = uint256(uint8(input[idx])) & FLAG_ZERO_MASK;
            (length, idx) = _getLength(input, idx);
            if (length == 0) {
                continue;
            }
            if (flag == FLAG_ZERO) {
                retIdx += length;
                continue;
            } else if (flag == FLAG_NONZERO){
                for (uint256 i; i < length; ++i) {
                    ret[retIdx++] = input[i+idx]; 
                }
                idx += length; 
            } else {
                revert("should not happen :(");
            }
        }
    }
    
    function _setLength(bytes memory arr, uint256 arrIdx, uint256 length) private pure returns(uint256) {
        uint256 bound;
        if (length < type(uint8).max) {
            arr[arrIdx++] |= bytes1(uint8(FLAG_LENGTH_UINT8));
            bound = 1;
        } else if (length < type(uint16).max) {
            arr[arrIdx++] |= bytes1(uint8(FLAG_LENGTH_UINT16));
            bound = 2;
        } else if (length < type(uint32).max) { 
            arr[arrIdx++] |= bytes1(uint8(FLAG_LENGTH_UINT32));
            bound = 4;
        } else if (length < type(uint64).max) { 
            arr[arrIdx++] |= bytes1(uint8(FLAG_LENGTH_UINT64));
            bound = 8;
        } else {
            revert("_setLength:unsupportedLength");
        }
        for (uint256 i; i < bound; ++i) {
            arr[arrIdx++] = bytes1(uint8(length >> (8*i)));
        }
        return arrIdx;
    }

    function _getLength(bytes memory arr, uint256 arrIdx) private pure returns(uint256, uint256) {
        uint256 flagLength = uint256(uint8(arr[arrIdx++])) & FLAG_LENGTH_MASK; 
        uint256 bound;
        if (flagLength == FLAG_LENGTH_UINT8) {
            bound = 1;
        } else if (flagLength == FLAG_LENGTH_UINT16) {
            bound = 2;
        } else if (flagLength == FLAG_LENGTH_UINT32) { 
            bound = 4;
        } else if (flagLength == FLAG_LENGTH_UINT64) { 
            bound = 8;
        } else {
            revert("_getLength:unsupportedLength");
        }
        uint256 length;
        for (uint256 i; i < bound; ++i) {
            length |= uint256(uint8(arr[arrIdx++])) << (8*i);
        }
        return (length, arrIdx);
    }
    
    // cheaper than bytes concat :)
    function _append(bytes memory dst, bytes memory src) private view {
      
        assembly {
            // resize

            let priorLength := mload(dst)
            
            mstore(dst, add(priorLength, mload(src)))
        
            // copy    

            pop(
                staticcall(
                  gas(), 4, 
                  add(src, 32), // src data start
                  mload(src), // src length 
                  add(dst, add(32, priorLength)), // dst write ptr
                  mload(dst)
                ) 
            )
        }
    }
}

contract PooperRenderer is IRenderer, ImageScripterSSTORE2, Ownable {

    mapping(address => bool) internal _hasCallerRole;
    mapping(address => bytes32) internal _contractLink;

    constructor() {
        _hasCallerRole[owner()] = true;
    }

    function _onlyOwner() internal view {
        require(msg.sender == owner(), "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
    }