contract EthTerrestrialsV2Descriptor {
   using Strings for uint256;
   using InflateLib for bytes;

   /// @notice Storage entry for a trait type (category)
   struct TraitType {
      string name; //the name of the category (i.e., 'Clothes')
      uint16[] rarity; //the rarity table is mapped to each Trait in a TraitType and should be provided as an array of desired occurrences out of 1000 samples. E.g.: [500,200,200,100,100]. 0 rarity indicates that the trait will not occur naturally.
   }
   mapping(uint8 => TraitType) public traitTypes;
   /*   Note: Certain layers have only one trait - left in code for consistency/future projects
    *    0 position will be null for certain traits
    *   Trait Types:
    *   0: Background
    *   1: Hoodie Back
    *   2: Head Color
    *   3: Head Outline
    *   4: Ears
    *   5: Mouths
    *   6: Eyes
    *   7: Head Accessories
    *   8: Apparel
    *   9: Front
    */

   mapping(uint8 => bool) hiddentrait; //No need to display metadta for certain layers.

   /// @notice Storage entry for a single trait/custom token
   struct Trait {
      address imageStore; //SSTORE2 storage location for SVG image data, compressed using DEFLATE (python zlib). Header (first 2 bytes) and checksum (last 4 bytes) truncated.
      uint96 imagelen; //The length of the uncomressed image date (required for decompression).
      string name; //the name of the trait (i.e. "grey hoodie") or custom image.
   }

   /// @notice Storage entry for non-allowed trait combinations and rearranged traits
   struct TraitDescriptor {
      uint8 traitType;
      uint8 trait;
   }

   /// @notice Key: keccak hash of trait type and trait index for which there are non-allowed trait combinations
   mapping(bytes32 => TraitDescriptor[]) public traitExclusions;

   /// @notice Key: keccak hash of trait type and trait index that should be placed below any traits in list
   mapping(bytes32 => TraitDescriptor[]) public traitRearrangement;

   /// @notice A mapping of traits in the format traits[traitType][trait number]. The ordering of trait types corresponds to traitTypes (above).
   mapping(uint8 => mapping(uint8 => Trait)) public traits;

   /// @notice A mapping of custom tokenIds that contain custom tokens.
   mapping(uint256 => Trait) public customs;

   /// @notice Permanently seals the metadata in the contract from being modified by deployer.
   bool public contractsealed;

   string private SVGOpenTag =
      '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1100 1100" style="enable-background:new 0 0 1100 1100" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink">';

   address private deployer;

   Terraforms public terraforms = Terraforms(0x4E1f41613c9084FdB9E34E11fAE9412427480e56);

   modifier onlyDeployerWhileUnsealed() {
      require(!contractsealed && msg.sender == deployer, "Not authorized or locked");
      _;
   }

   constructor() public {
      deployer = msg.sender;
   }

   /*
.___  ___.  _______ .___________.    ___       _______       ___   .___________.    ___      
|   \/   | |   ____||           |   /   \     |       \     /   \  |           |   /   \     
|  \  /  | |  |__   `---|  |----`  /  ^  \    |  .--.  |   /  ^  \ `---|  |----`  /  ^  \    
|  |\/|  | |   __|      |  |      /  /_\  \   |  |  |  |  /  /_\  \    |  |      /  /_\  \   
|  |  |  | |  |____     |  |     /  _____  \  |  '--'  | /  _____  \   |  |     /  _____  \  
|__|  |__| |_______|    |__|    /__/     \__\ |_______/ /__/     \__\  |__|    /__/     \__\ 
*/

   /// @notice Returns an ERC721 standard tokenURI
   /// @param tokenId, the desired tokenId to display
   /// @param rawSeed, the raw seed code generated by the seeder contract following mint
   /// @param tokenType, the type of token (one of one or common)
   /// @return output, a base64 encoded JSON string containing the tokenURI (metadata and image)
   function generateTokenURI(
      uint256 tokenId,
      uint256 rawSeed,
      uint256 tokenType
   ) external view returns (string memory) {
      string memory name = string(abi.encodePacked("EtherTerrestrial #", tokenId.toString()));
      string memory description = "EtherTerrestrials are inter-dimensional Extra-Terrestrials who came to Earth's internet to infuse consciousness into all other pixelated Lifeforms. They can be encountered in the form of on-chain characters as interpreted by the existential explorer Kye."; //need to write
      string memory traits_json;
      string memory image;

      if (tokenType == 1) {
         //these tokens do not use a seed
         image = getSvgCustomToken(tokenId);
         traits_json = viewTraitsJSONCustom(tokenId);
      } else if (rawSeed == 0) {
         //if the seed is zero but not a one of one, the token must be unrevealed. Unrevealed image stored at custom index 69.
         image = getSvgCustomToken(69);
         description = "Unrevealed EtherTerrestrial - Refresh Soon";
         traits_json = "[]";
      } else {
         uint8[10] memory seed = processRawSeed(rawSeed);
         image = getSvgFromSeed(seed);
         traits_json = viewTraitsJSON(seed);
      }
      string memory json = Base64.encode(
         bytes(
            string(
               abi.encodePacked(
                  '{"name": "',
                  name,
                  '", "description": "',
                  description,
                  '", "attributes":',
                  traits_json,
                  ',"image": "',
                  "data:image/svg+xml;base64,",
                  Base64.encode(bytes(image)),
                  '"}'
               )
            )
         )
      );

      string memory output = string(abi.encodePacked("data:application/json;base64,", json));
      return output;
   }

   /// @notice Converts a raw seed code into a list of traits
   /// @param rawSeed, the raw seed code generated by the seeder contract following mint
   /// @return seed, a uint8 array containing the finally assigned trait codes for each trait type
   /// @dev For generative tokens, raw seeds are handled according to the following workflow:
   /// 	1. processRawSeed(): perform keccak operation over the raw seed and assign a rarity seed to each trait
   /// 	2. generateSeedFromRarityTables(): runs the results of processRawSeed over rarity tables to determine the traits
   ///   3. enforceRequiredCombinations(): ensures that certain traits are matched with, or prohibited from being matched with certain others.
   function processRawSeed(uint256 rawSeed) public view returns (uint8[10] memory) {
      uint16[10] memory initialSeed;

      for (uint8 i = 0; i < 10; i++) {
         initialSeed[i] = uint16(uint256(keccak256(abi.encodePacked(rawSeed, i))) % 1000);
      }
      uint8[10] memory seed = generateSeedFromRarityTables(initialSeed);
      seed = enforceRequiredCombinations(seed);
      return seed;
   }

   /// @notice Converts an initial rarity seed to a list of traits
   /// @param initialSeed, the initial rarity seed for a given token
   /// @return seed, a uint8 array containing the trait code for each traitType
   function generateSeedFromRarityTables(uint16[10] memory initialSeed) internal view returns (uint8[10] memory) {
      uint8[10] memory seed;
      for (uint8 traitType; traitType < 10; traitType++) {
         uint16[] memory rarityTable = traitTypes[traitType].rarity;
         uint16 upperBound;
         for (uint8 index; index < rarityTable.length; index++) {
            upperBound += rarityTable[index];
            if (initialSeed[traitType] < upperBound) {
               seed[traitType] = index;
               break;
            }
         }
      }
      return seed;
   }

   /// @notice Alters the seed to remove combinations that the artist has determined conflict with one another
   /// @param seed, the post-rarity allocated traits for a token
   /// @return the modified seed
   function enforceRequiredCombinations(uint8[10] memory seed) public view returns (uint8[10] memory) {
      for (uint8 i; i < seed.length; i++) {
         TraitDescriptor[] memory exclusions = traitExclusions[keccak256(abi.encodePacked(i, seed[i]))];
         // check to see if seed contains any trait combinations that are not allowed
         for (uint8 j = 0; j < exclusions.length; j++) {
            // seed has a trait combination that is not allowed, remove combination
            if (seed[exclusions[j].traitType] == exclusions[j].trait) {
               seed[exclusions[j].traitType] = 0;
            }
         }
      }

      // Exclude all of a type for some traits
      if (seed[6] == 6) {
         seed[7] = 0;
      } else if (seed[8] == 1) {
         seed[7] = 0;
      } else if (seed[8] == 7) {
         seed[7] = 0;
      } else if (seed[8] == 8) {
         seed[7] = 0;
      } else if (seed[8] == 9) {
         seed[7] = 0;
      }

      //Add hoodie back if hoodie
      if (seed[8] == 3 || seed[8] == 4 || seed[8] == 6) {
         seed[1] = 1;
      }

      return seed;
   }

   /// @notice Generates an ERC721 standard metadata JSON string for generative tokens
   /// @param seed, the finally allocated traits for a token
   /// @return json, a JSON metadata string
   function viewTraitsJSON(uint8[10] memory seed) public view returns (string memory) {
      string[] memory jsonItems = new string[](11);

      for (uint8 traitType; traitType < 10; traitType++) {
         if (!hiddentrait[traitType]) {
            uint8 traitCode = seed[traitType];
            string memory traitName = traits[traitType][traitCode].name;
            jsonItems[traitType] = string(
               abi.encodePacked(
                  traitType == 0 ? '[{"trait_type":"' : ',{"trait_type":"',
                  traitTypes[traitType].name,
                  '","value":"',
                  traitName,
                  '"}'
               )
            );
         }
      }
      jsonItems[10] = ',{"trait_type":"Life Form","value":"Tripped"}]';

      string memory json;
      for (uint256 i = 0; i < 11; i++) {
         json = string(abi.encodePacked(json, jsonItems[i]));
      }
      return json;
   }

   /// @notice Generates an ERC721 standard metadata JSON string for custom tokens
   /// @param tokenId, the desired tokenId
   /// @return a JSON metadata string
   function viewTraitsJSONCustom(uint256 tokenId) public view returns (string memory) {
      return
         string(
            abi.encodePacked(
               '[{"trait_type":"Cosmic Being","value":"',
               customs[tokenId].name,
               '"},{"trait_type":"Life Form","value":"Tripped"}]'
            )
         );
   }

   /// @notice Generates an unencoded SVG image for a given seed
   /// @param seed, the finally allocated traits for a token
   /// @return an SVG string
   function getSvgFromSeed(uint8[10] memory seed) public view returns (string memory) {
      string[10] memory SVG;

      for (uint8 traitType; traitType < 10; traitType++) {
         uint8 traitCode = seed[traitType];
         // uint8.max means no rearrangement
         uint8 rearrangementIndex = type(uint8).max;
         TraitDescriptor[] memory rearrangements = traitRearrangement[keccak256(abi.encodePacked(traitType, traitCode))];
         //  check to see if this trait has any rearrangements
         for (uint8 i; i < rearrangements.length; i++) {
            // rearrangement found for these trait combinations
            if (seed[rearrangements[i].traitType] == rearrangements[i].trait) {
               rearrangementIndex = rearrangements[i].traitType;
            }
         }
         string memory image = traits[traitType][traitCode].imagelen != 0 ? getTraitSVG(traitType, traitCode) : "";

         if (rearrangementIndex != type(uint8).max) {
            SVG[traitType] = SVG[rearrangementIndex];
            SVG[rearrangementIndex] = image;
         } else {
            SVG[traitType] = image;
         }
      }
      string memory SVGStr;
      for (uint8 i; i < SVG.length; i++) {
         SVGStr = string(abi.encodePacked(SVGStr, SVG[i]));
      }
      return string(abi.encodePacked(SVGOpenTag, SVGStr, "</svg>"));
   }

   /// @notice Returns the unencoded SVG image for a given seed
   /// @param tokenId, the tokenId of the custom token
   /// @return an SVG string
   /// @dev token 104's background is an onchain composition of Terraform #3640
   function getSvgCustomToken(uint256 tokenId) public view returns (string memory) {
      return
         string(
            abi.encodePacked(
               SVGOpenTag,
               tokenId == 104
                  ? '<defs><clipPath id="clipPath3"><circle cx="550" cy="550" r="500" /></clipPath></defs><g style="clip-path: url(#clipPath3);"><rect x="0" y="0" width="3000" height="3000" style="stroke: none; fill:none; clip-path: url(#clipPath3);" /><g xmlns="http://www.w3.org/2000/svg" transform="scale(1.51,1) translate(-178)">'
                  : "",
               tokenId == 104 ? terraforms.tokenSVG(3640) : "",
               tokenId == 104 ? "</g></g>" : "",
               decompress(SSTORE2.read(customs[tokenId].imageStore), customs[tokenId].imagelen),
               "</svg>"
            )
         );
   }

   function getTraitSVG(uint8 traitType, uint8 traitCode) public view returns (string memory) {
      return decompress(SSTORE2.read(traits[traitType][traitCode].imageStore), traits[traitType][traitCode].imagelen);
   }

   function decompress(bytes memory input, uint256 len) public pure returns (string memory) {
      (, bytes memory decompressed) = InflateLib.puff(input, len);
      return string(decompressed);
   }

   /*
 _______   _______ .______    __        ______   ____    ____  _______ .______      
|       \ |   ____||   _  \  |  |      /  __  \  \   \  /   / |   ____||   _  \     
|  .--.  ||  |__   |  |_)  | |  |     |  |  |  |  \   \/   /  |  |__   |  |_)  |    
|  |  |  ||   __|  |   ___/  |  |     |  |  |  |   \_    _/   |   __|  |      /     
|  '--'  ||  |____ |  |      |  `----.|  `--'  |     |  |     |  |____ |  |\  \----.
|_______/ |_______|| _|      |_______| \______/      |__|     |_______|| _| `._____|
                                                                                                                                                
*/

   /// @notice Establishes the 10 traitTypes
   function setTraitTypes(TraitType[] memory _traitTypes) external onlyDeployerWhileUnsealed {
      for (uint8 i; i < 10; i++) {
         traitTypes[i] = _traitTypes[i];
      }
   }

   /// @notice Establishes traits for a single traitType
   function setTraits(
      uint8 _traitType,
      Trait[] memory _traits,
      uint8[] memory _traitNumber,
      bytes[] memory _images
   ) external onlyDeployerWhileUnsealed {
      require(_traits.length == _traitNumber.length && _traitNumber.length == _images.length, "Length Mismatch");
      for (uint8 i; i < _traits.length; i++) {
         _traits[i].imageStore = SSTORE2.write(_images[i]);
         traits[_traitType][_traitNumber[i]] = _traits[i];
      }
   }

   function setHiddenTraits(uint8[] memory _traitTypes, bool _hidden) external onlyDeployerWhileUnsealed {
      for (uint8 i; i < _traitTypes.length; i++) hiddentrait[_traitTypes[i]] = _hidden;
   }

   /// @notice Establishes the custom token art
   function setCustom(
      uint256[] memory _tokenIds,
      Trait[] memory _oneOfOnes,
      bytes[] memory _images
   ) external onlyDeployerWhileUnsealed {
      require(_tokenIds.length == _oneOfOnes.length);
      for (uint256 i; i < _oneOfOnes.length; i++) {
         _oneOfOnes[i].imageStore = SSTORE2.write(_images[i]);
         customs[_tokenIds[i]] = _oneOfOnes[i];
      }
   }

   /// @notice Set a trait exclusion rule
   /// @param traitType, the trait type index
   /// @param trait, the trait index
   /// @param exclusions, all unallowed combinations
   function setTraitExclusions(
      uint8 traitType,
      uint8 trait,
      TraitDescriptor[] memory exclusions
   ) external onlyDeployerWhileUnsealed {
      bytes32 traitHash = keccak256(abi.encodePacked(traitType, trait));
      delete traitExclusions[traitHash];
      for (uint8 i = 0; i < exclusions.length; i++) {
         traitExclusions[traitHash].push(exclusions[i]);
      }
   }

   /// @notice Set a trait rearrangement rule
   /// @param traitType, the trait type index
   /// @param trait, the trait index
   /// @param rearrangements, all traits that should be placed above base trait
   function setTraitRearrangements(
      uint8 traitType,
      uint8 trait,
      TraitDescriptor[] memory rearrangements
   ) external onlyDeployerWhileUnsealed {
      bytes32 traitHash = keccak256(abi.encodePacked(traitType, trait));
      delete traitRearrangement[traitHash];
      for (uint8 i = 0; i < rearrangements.length; i++) {
         traitRearrangement[traitHash].push(rearrangements[i]);
      }
   }

   /// @notice IRREVERSIBLY SEALS THE CONTRACT FROM BEING MODIFIED
   function sealContract() external onlyDeployerWhileUnsealed {
      contractsealed = true;
   }
}
function setTraitExclusions(
   uint8 traitType,
   uint8 trait,
   TraitDescriptor[] memory exclusions
) external onlyDeployerWhileUnsealed {
   bytes32 traitHash = keccak256(abi.encodePacked(traitType, trait));
   delete traitExclusions[traitHash];
   for (uint8 i = 0; i < exclusions.length; i++) {
      traitExclusions[traitHash].push(exclusions[i]);
   }
}

/// @notice Set a trait exclusion rule
/// @param traitType, the trait type index
/// @param trait, the trait index
/// @param exclusions, all unallowed combinations