// Contract: PooperRenderer
// Target function: function _append(bytes memory dst, bytes memory src) private view
 4 bytes. Optimized for fewer stack operations.
                    mstore8(ptr, mload(and(shr(18, input), 0x3F)))
                    mstore8(add(ptr, 1), mload(and(shr(12, input), 0x3F)))
                    mstore8(add(ptr, 2), mload(and(shr(6, input), 0x3F)))
                    mstore8(add(ptr, 3), mload(and(input, 0x3F)))

                    ptr := add(ptr, 4) // Advance 4 bytes.

                    if iszero(lt(ptr, end)) { break }
                }

                // Allocate the memory for the string.
                // Add 31 and mask with `not(31)` to round the
                // free memory pointer up the next multiple of 32.
                mstore(0x40, and(add(end, 31), not(31)))

                let r := mod(dataLength, 3)

                if iszero(noPadding) {
                    // Offset `ptr` and pad with '='. We can simply write over the end.
                    mstore8(sub(ptr, iszero(iszero(r))), 0x3d) // Pad at `ptr - 1` if `r > 0`.
                    mstore8(sub(ptr, shl(1, eq(r, 1))), 0x3d) // Pad at `ptr - 2` if `r == 1`.
                    // Write the length of the string.
                    mstore(result, encodedLength)
                    break
                }
                // Write the length of the string.
                mstore(result, sub(encodedLength, add(iszero(iszero(r)), eq(r, 1))))
                break
            }
        }
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, false, false)`.
    function encode(bytes memory data) internal pure returns (string memory result) {
        result = encode(data, false, false);
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, fileSafe, false)`.
    function encode(bytes memory data, bool fileSafe)
        internal
        pure
        returns (string memory result)
    {
        result = encode(data, fileSafe, false);
    }

    /// @dev Encodes base64 encoded `data`.
    ///
    /// Supports:
    /// - RFC 4648 (both standard and file-safe mode).
    /// - RFC 3501 (63: ',').
    ///
    /// Does not support:
    /// - Line breaks.
    ///
    /// Note: For performance reasons,
    /// this function will NOT revert on invalid `data` inputs.
    /// Outputs for invalid inputs will simply be undefined behaviour.
    /// It is the user's responsibility to ensure that the `data`
    /// is a valid base64 encoded string.
    function decode(string memory data) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                let end := add(data, dataLength)
                let decodedLength := mul(shr(2, dataLength), 3)

                for {} 1 {} {
                    // If padded.
                    if iszero(and(dataLength, 3)) {
                        let t := xor(mload(end), 0x3d3d)
                        // forgefmt: disable-next-item
                        decodedLength := sub(
                            decodedLength,
                            add(iszero(byte(30, t)), iszero(byte(31, t)))
                        )
                        break
                    }
                    // If non-padded.
                    decodedLength := add(decodedLength, sub(and(dataLength, 3), 1))
                    break
                }
                result := mload(0x40)

                // Write the length of the string.
                mstore(result, decodedLength)

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)

                // Load the table into the scratch space.
                // Constants are optimized for smaller bytecode with zero gas overhead.
                // `m` also doubles as the mask of the upper 6 bits.
                let m := 0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8cc
                mstore(0x5b, m)
                mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064)
                mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4)

                for {} 1 {} {
                    // Read 4 bytes.
                    data := add(data, 4)
                    let input := mload(data)

                    // Write 3 bytes.
                    // forgefmt: disable-next-item
                    mstore(ptr, or(
                        and(m, mload(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);
    }