// Contract: PunkBlocks
// Target function: function svgFromPunkID(
██░░███ ███░░███ ░███░░███  ███░░
 ░███░░░░░███ ░███ ░███ ░███░███ ░░░  ░██████░  ░░█████
 ░███    ░███ ░███ ░███ ░███░███  ███ ░███░░███  ░░░░███
 ███████████  █████░░██████ ░░██████  ████ █████ ██████
░░░░░░░░░░░  ░░░░░  ░░░░░░   ░░░░░░  ░░░░ ░░░░░ ░░░░░░

            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;
    }

    /**
    * @dev svgFromNames returns the svg data as a string
    * @param _attributeNames a list of attribute names, eg "Male 1", "Goat"
    *   must have at least 1 layer 0 attribute (eg. Male, Female, Alien, Ape, Zombie)
    *   e.g. ["Male 1","Goat"]
    *   Where "Male 1" is a layer 0 attribute, that decides what version of
    *   image to use for the higher
    *   layers (dataMale or dataFemale)
    * @param _size the width and height of generated svg, eg. 24
    * @param _orderID which order config to use when rendering, 0 is the default
    */
    function svgFromNames(
        string[] memory _attributeNames,
        uint16 _x,
        uint16 _y,
        uint16 _size,
        uint32 _orderID) external view returns (string memory){
        bool isLarge;
        bytes32[] memory layerKeys = new bytes32[](13);
        for (uint16 i = 0; i < _attributeNames.length; i++) {
            bytes32 hash = keccak256(
                abi.encodePacked(_attributeNames[i]));
            uint256 fo = blocksInfo[hash];
            if (fo == 0) {
                break;
            }
            (Layer l, uint16 sL,) = _unpackInfo(fo);
            layerKeys[uint256(orderConfig[_orderID][l])] = hash;
            if (l == Layer.Base) {
                // base later
                if (sL > 0) {
                    isLarge = true;
                }
            }
        }
        return _svg(layerKeys, _x, _y, _size, isLarge);
    }

    /**
    * @dev svgFromKeys returns the svg data as a string
    * @param _attributeKeys a list of attribute names that have been hashed,
    *    eg keccak256("Male 1"), keccak256("Goat")
    *    must have at least 1 layer 0 attribute (eg. keccak256("Male 1")) which
    *    decides what version of image to use for the higher layers
    *    (dataMale or dataFemale)
    *    e.g. ["0x9039da071f773e85254cbd0f99efa70230c4c11d63fce84323db9eca8e8ef283",
    *    "0xd5de5c20969a9e22f93842ca4d65bac0c0387225cee45a944a14f03f9221fd4a"]
    * @param _size the width and height of generated svg, eg. 24
    * @param _orderID which order config to use when rendering, 0 is the default
    */
    function svgFromKeys(
        bytes32[] memory _attributeKeys,
        uint16 _x,
        uint16 _y,
        uint16 _size,
        uint32 _orderID) external view returns (string memory) {
        bool isLarge;
        bytes32[] memory layerKeys = new bytes32[](13);
        for (uint16 i = 0; i < _attributeKeys.length; i++) {
            uint256 fo = blocksInfo[_attributeKeys[i]];
            if (fo == 0) {
                break;
            }
            (Layer l, uint16 sL,) = _unpackInfo(fo);
            layerKeys[uint256(orderConfig[_orderID][l])] = _attributeKeys[i];
            if (l == Layer.Base) {
                // base later
                if (sL > 0) {
                    isLarge = true;
                }
            }
        }
        return _svg(layerKeys, _x, _y, _size, isLarge);
    }

    /**
    * @dev svgFromIDs returns the svg data as a string
    *   e.g. [9,55,99]
    *   One of the elements must be must be a layer 0 block.
    *   This element decides what version of image to use for the higher layers
    *   (dataMale or dataFemale)
    * @param _ids uint256 ids of an attribute, by it's index of creation
    * @param _size the width and height of generated svg, eg. 24
    * @param _orderID which order config to use when rendering, 0 is the default
    */
    function svgFromIDs(
        uint32[] calldata _ids,
        uint16 _x,
        uint16 _y,
        uint16 _size,
        uint32 _orderID) external view returns (string memory) {
        bool isLarge;
        bytes32[] memory layerKeys = new bytes32[](13);
        for (uint16 i = 0; i < _ids.length; i++) {
            bytes32 hash = index[_ids[i]];
            uint256 fo = blocksInfo[hash];
            if (fo == 0) {
                break;
            }
            (Layer l, uint16 sL,) = _unpackInfo(fo);
            layerKeys[uint256(orderConfig[_orderID][l])] = hash;
            if (l == Layer.Base) {
                // base later
                if (sL > 0) {
                    isLarge = true;
                }
            }
        }
        return _svg(layerKeys, _x, _y, _size, isLarge);
    }