// Contract: PunkBlocks
// Target function: function svgFromNames(
// SPDX-License-Identifier: MIT
// Author: tycoon.eth, thanks to @geraldb & @samwilsn on Github for inspiration!
// Version: v0.1.3
// Note: The MIT license is for the source code only. Images registered through
// this contract retain all of their owner's rights. This contract
// is a non-profit "library" project and intended to archive & preserve punk
// images, so that they can become widely accessible for decentralized
// applications, including marketplaces, wallets, galleries, etc.
pragma solidity ^0.8.19;
/**

 ███████████                        █████
░░███░░░░░███                      ░░███
 ░███    ░███ █████ ████ ████████   ░███ █████
 ░██████████ ░░███ ░███ ░░███░░███  ░███░░███
 ░███░░░░░░   ░███ ░███  ░███ ░███  ░██████░
 ░███         ░███ ░███  ░███ ░███  ░███░░███
 █████        ░░████████ ████ █████ ████ █████
░░░░░          ░░░░░░░░ ░░░░ ░░░░░ ░░░░ ░░░░░



 ███████████  ████                    █████
░░███░░░░░███░░███                   ░░███
 ░███    ░███ ░███   ██████   ██████  ░███ █████  █████
 ░██████████  ░███  ███░░███ ███░░███ ░███░░███  ███░░
 ░███░░░░░███ ░███ ░███ ░███░███ ░░░  ░██████░  ░░█████
 ░███    ░███ ░███ ░███ ░███░███  ███ ░███░░███  ░░░░███
 ███████████  █████░░██████ ░░██████  ████ █████ ██████
░░░░░░░░░░░  ░░░░░  ░░░░░░   ░░░░░░  ░░░░ ░░░░░ ░░░░░░

            A Registry of 24x24 png images

This contract:

1. Stores all the classic traits of the CryptoPunks in
individual png files, 100% on-chain. These are then used as
blocks to construct CryptoPunk images. Outputted as SVGs.

2. Any of the 10,000 "classic" CryptoPunks can be generated
by supplying desired arguments to a function, such as
the id of a punk, or a list of the traits.

3. An unlimited number of new punk images can be generated from
the existing classic set of traits, or even from new traits!

4. New traits (blocks) can be added to the contract by
registering them with the `registerBlock` function.

Further documentation:
https://github.com/0xTycoon/punk-blocks

*/

//import "hardhat/console.sol";

contract PunkBlocks {
    using DynamicBufferLib for DynamicBufferLib.DynamicBuffer;
    address admin;
    // Layer is in the order of rendering
    enum Layer {
        Base,      //0 Base is the face. Determines if m or f version will be used to render the remaining layers
        Mouth,     //1 (Hot Lipstick, Smile, Buck Teeth, ...)
        Cheeks,    //2 (Rosy Cheeks)
        Blemish,   //3 (Mole, Spots)
        Eyes,      //4 (Clown Eyes Green, Green Eye Shadow, ...)
        Neck,      //5 (Choker, Silver Chain, Gold Chain)
        Beard,     //6 (Big Beard, Front Beard, Goat, ...)
        Ears,      //7 (Earring)
        HeadTop1,  //8 (Purple Hair, Shaved Head, Beanie, Fedora,Hoodie)
        HeadTop2,  //9 eg. sometimes an additional hat over hair
        Eyewear,   //10 (VR, 3D Glass, Eye Mask, Regular Shades, Welding Glasses, ...)
        MouthProp, //11 (Medical Mask, Cigarette, ...)
        Nose       //12 (Clown Nose)
    }
    struct Block {
        Layer layer; // 13 possible layers
        bytes blockL;// male version of this attribute
        bytes blockS;// female version of this attribute
    }
    mapping(bytes32 => bytes) public blockS;      // small attributes as png
    mapping(bytes32 => bytes) public blockL;      // large attributes as png
    mapping(bytes32 => uint256) public blocksInfo;// byte 0: layer, byte 1-2: blockL.length, byte 3-5: blockS.length
    mapping (uint32 => mapping(Layer => uint16)) public orderConfig; // layer => seq
    uint32 public nextConfigId;
    uint32 public nextId;                    // next id to use when adding a block
    mapping(uint32 => bytes32) public index; // index of each block by its sequence
    mapping(uint256 => Layer) public blockToLayer;
    event NewBlock(address, uint32, string);

    uint256 constant private bit1byte  =  0xFF;         // bit mask 1
    uint256 constant private bit2byte  =  0xFFFF00;     // bit mask 2
    uint256 constant private bit3byte  =  0xFFFF000000; // bit mask 3

    /**
    * @dev getBlocks returns a sequential list of blocks in a single call
    * @param _fromID is which id to begin from
    * @param _count how many items to retrieve.
    * @return Block[] list of blocks, uint256 next id
    */
    function getBlocks(
        uint _fromID,
        uint _count) external view returns(Block[] memory, uint32) {
        Block[] memory ret = new Block[](_count);
        while (_count != 0) {
            bytes32 i = index[uint32(_fromID + _count - 1)];
            uint256 info = blocksInfo[i];
            if (info > 0) {
                (Layer l,,) = _unpackInfo(info);
                ret[_count-1].blockS = blockS[i];
                ret[_count-1].blockL = blockL[i];
                ret[_count-1].layer = l;
            }
            _count--;
        }
        return (ret, nextId);
    }

    /**
    * registerOrderConfig
    */
    function registerOrderConfig(
        Layer[] calldata _order
    ) external {
        mapping(Layer => uint16) storage c = orderConfig[nextConfigId];
        for (uint16 i = 0; i < _order.length; i++) {
            require(c[Layer(i)] == 0, "storage must be empty");
            c[Layer(i)] = uint16(i);
        }
        nextConfigId++;
    }

    function _packInfo(uint8 _layer, uint16 _l, uint16 _s) pure internal returns (uint256) {
        uint256 scratch;
        scratch = uint256(_layer);
        scratch = (uint256(_l) << 8) | scratch; // 16 bit uint, m length
        scratch = (uint256(_s) << 24) | scratch;// 16 bit uint, f length
        return scratch;
    }

    /**
    * _unpackInfo extracts block information
    */
    function _unpackInfo(uint256 _info) pure internal returns(Layer, uint16, uint16) {
        Layer layer = Layer(uint8(_info));
        uint16 l = uint16((_info & bit2byte) >> 8);
        uint16 s = uint16((_info & bit3byte) >> 24);
        return (layer, l, s);
    }

    /**
    * get info about a block
    */
    function info(bytes32 _id) view public returns(Layer, uint16, uint16) {
        uint256 info = blocksInfo[_id];
        if (info == 0) {
            return (Layer.Base, 0, 0);
        }
        return _unpackInfo(info);
    }

    /**
    * @dev registerBlock allows anybody to add a new block to the contract.
    *   Either _dataL or _dataF, or both, must contain a byte stream of a png file.
    *   It's best if the png is using an 'index palette' and the lowest bit depth possible,
    *   while keeping the highest compression setting.
    * @param _dataL png data for the larger male version, 24x24
    * @param _dataS png data for the smaller female version, 24x24
    * @param _layer 0 to 12, corresponding to the Layer enum type.
    * @param _name the name of the trait, Camel Case. e.g. "Luxurious Beard"
    */
    function registerBlock(
        bytes calldata _dataL,
        bytes calldata _dataS,
        uint8 _layer,
        string memory _name) external
    {
        bytes32 key = keccak256(abi.encodePacked(_name));
        uint256 info = blocksInfo[key];
        require (info == 0, "slot taken");
        require (_layer < 13, "invalid _layer");
        require (_dataL.length + _dataS.length > 0, "no data");
        require (_dataL.length <= type(uint16).max, "L too big");
        require (_dataS.length <= type(uint16).max, "S too big");
        if (_layer==0 && _dataL.length > 0 && _dataS.length > 0) {
            revert("layer0 cannot have both versions");
        }
        if (_dataL.length > 0) {
            require (_validatePng(_dataL), "invalid L png");
            blockL[key] = _dataL;
        }
        if (_dataS.length > 0) {
            require (_validatePng(_dataS), "invalid S png");
            blockS[key] = _dataS;
        }
        blocksInfo[key] = _packInfo(_layer, uint16(_dataL.length), uint16(_dataS.length));
        index[nextId] = key;
        unchecked{nextId++;}
        emit NewBlock(msg.sender, nextId, _name);
    }

    /**
    * @dev Just a limited png validation. Only verifies that the png is 24x24 and has a png structure,
    *   but doesn't validate any checksums or any other validation
    */
    function _validatePng(bytes calldata _data) pure internal returns (bool) {
    unchecked {
        if (_data.length < 8) {
            return false;
        }
        bytes memory pngHeader = bytes("\x89PNG\r\n\x1a\n"); // first 8 bytes
        uint pos;
        while (pos < 8) {
            if (_data[pos] != pngHeader[pos]) {
                return false;
            }
            pos++;
        }
        int32 chunkLen;
        while (true) {
            // next 4 bytes represent a big-endian int32, the chunk length
            chunkLen = int32(uint32(uint8(_data[pos+3]))
            | uint32(uint8(_data[pos+2]))<<8
            | uint32(uint8(_data[pos+1]))<<16
                | uint32(uint8(_data[pos]))<<24);
            pos += 4;
            if (
                _data[pos] == bytes1("I") &&
                _data[pos+1] == bytes1("H") &&
                _data[pos+2] == bytes1("D") &&
                _data[pos+3] == bytes1("R")) { // IHDR
                if (24 != int32(uint32(uint8(_data[pos+7]))
                | uint32(uint8(_data[pos+6]))<<8
                | uint32(uint8(_data[pos+5]))<<16
                    | uint32(uint8(_data[pos+4]))<<24)) { // width needs to be 24
                    return false;
                }
                if (24 != int32(uint32(uint8(_data[pos+11]))
                | uint32(uint8(_data[pos+10]))<<8
                | uint32(uint8(_data[pos+9]))<<16
                    | uint32(uint8(_data[pos+8]))<<24)) { // height needs to be 24
                    return false;
                }
            } else if (
                _data[pos] == bytes1("P") &&
                _data[pos+1] == bytes1("L") &&
                _data[pos+2] == bytes1("T") &&
                _data[pos+3] == bytes1("E")) {  // PLTE
            } else if (
                _data[pos] == bytes1("t") &&
                _data[pos+1] == bytes1("R") &&
                _data[pos+2] == bytes1("N") &&
                _data[pos+3] == bytes1("S")) {  // tRNS
            } else if (
                _data[pos] == bytes1("I") &&
                _data[pos+1] == bytes1("D") &&
                _data[pos+2] == bytes1("A") &&
                _data[pos+3] == bytes1("T")) {  // IDAT
            } else if (
                _data[pos] == bytes1("I") &&
                _data[pos+1] == bytes1("E") &&
                _data[pos+2] == bytes1("N") &&
                _data[pos+3] == bytes1("D")) {  // IEND
                return true;                    // png is valid (without checking the CRC)
            } else {
                return false;
            }
            pos += 4 + uint(int(chunkLen)) + 4; // skip the payload, ignore CRC
        }
    } //unchecked
        return true;
    }