contract DecentralistsDescriptor is Ownable, IDescriptor {
  // SVG Types
  bytes32 private constant SVG_TYPE_BASE = 'BASE'; // 0x4241534500000000000000000000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_NECKLACE_MALE = 'NECKLACE_MALE'; // 	0x4e45434b4c4143455f4d414c4500000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_NECKLACE_FEMALE = 'NECKLACE_FEMALE'; // 	0x4e45434b4c4143455f46454d414c450000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_FACIAL_MALE = 'FACIAL_MALE'; // 	0x46414349414c5f4d414c45000000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_FACIAL_FEMALE = 'FACIAL_FEMALE'; // 	0x46414349414c5f46454d414c4500000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_EARRING_MALE = 'EARRING_MALE'; // 	0x45415252494e475f4d414c450000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_EARRING_FEMALE = 'EARRING_FEMALE'; // 	0x45415252494e475f46454d414c45000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_HEAD_MALE = 'HEAD_MALE'; // 	0x484541445f4d414c450000000000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_HEAD_FEMALE = 'HEAD_FEMALE'; // 	0x484541445f46454d414c45000000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_GLASSES_MALE = 'GLASSES_MALE'; // 	0x474c41535345535f4d414c450000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_GLASSES_FEMALE = 'GLASSES_FEMALE'; // 	0x474c41535345535f46454d414c45000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_LIPSTICK_MALE = 'LIPSTICK_MALE'; // 	0x4c4950535449434b5f4d414c4500000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_LIPSTICK_FEMALE = 'LIPSTICK_FEMALE'; // 	0x4c4950535449434b5f46454d414c450000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_SMOKING_MALE = 'SMOKING_MALE'; // 	0x534d4f4b494e475f4d414c450000000000000000000000000000000000000000
  bytes32 private constant SVG_TYPE_SMOKING_FEMALE = 'SMOKING_FEMALE'; // 	0x534d4f4b494e475f46454d414c45000000000000000000000000000000000000

  // Set of traits
  string[] private TRAIT_BASE = [
    'Human Male Black',
    'Human Female Black',
    'Human Male Dark',
    'Human Female Dark',
    'Human Male Pale',
    'Human Female Pale',
    'Human Male White',
    'Human Female White',
    'Vampire Male',
    'Vampire Female',
    'Metahuman Male',
    'Metahuman Female',
    'Ape Male'
  ];
  string[] private TRAIT_NECKLACE = ['None', 'Diamond', 'Golden', 'Silver'];
  string[] private TRAIT_FACIAL_MALE = [
    'None',
    'Chivo Black',
    'Chivo Blonde',
    'Chivo Brown',
    'Chivo Gray',
    'Chivo Red',
    'Chivo White',
    'Long Black',
    'Long Blonde',
    'Long Brown',
    'Long Gray',
    'Long Red',
    'Long White',
    'Regular Black',
    'Regular Blonde',
    'Regular Brown',
    'Regular Gray',
    'Regular Red',
    'Regular White',
    'Sideburns Black',
    'Sideburns Blonde',
    'Sideburns Brown',
    'Sideburns Gray',
    'Sideburns Red',
    'Sideburns White'
  ];
  string[] private TRAIT_FACIAL_FEMALE = ['None'];
  string[] private TRAIT_EARRING = ['None', 'Cross', 'Diamond', 'Golden', 'Silver'];
  string[] private TRAIT_HEAD_MALE = [
    'None',
    'Afro',
    'CapUp Green',
    'CapUp Red',
    'Kangaroo Black',
    'CapBack Blue',
    'CapBack Orange',
    'Conspiracist',
    'Cop',
    'CapFront Purple',
    'CapFront Red',
    'Hat Black',
    'Long Black',
    'Long Blonde',
    'Long Brown',
    'Long Gray',
    'Long Red',
    'Long White',
    'Punky Black',
    'Punky Blonde',
    'Punky Brown',
    'Punky Gray',
    'Punky Purple',
    'Punky Red',
    'Punky White',
    'Short Black',
    'Short Blonde',
    'Short Brown',
    'Short Gray',
    'Short Red',
    'Short White',
    'Trapper',
    'Wool Blue',
    'Wool Green',
    'Wool Red'
  ];
  string[] private TRAIT_HEAD_FEMALE = [
    'None',
    'Afro',
    'CapUp Green',
    'CapUp Red',
    'Kangaroo Black',
    'CapBack Blue',
    'CapBack Orange',
    'Conspiracist',
    'Cop',
    'CapFront Purple',
    'CapFront Red',
    'Hat Black',
    'Long Black',
    'Long Blonde',
    'Long Brown',
    'Long Gray',
    'Long Red',
    'Long White',
    'Punky Black',
    'Punky Blonde',
    'Punky Brown',
    'Punky Gray',
    'Punky Purple',
    'Punky Red',
    'Punky White',
    'Short Black',
    'Short Blonde',
    'Short Brown',
    'Short Gray',
    'Short Red',
    'Short White',
    'Straight Black',
    'Straight Blonde',
    'Straight Brown',
    'Straight Gray',
    'Straight Orange',
    'Straight Platinum',
    'Straight Purple',
    'Straight Red',
    'Straight White',
    'Trapper',
    'Wool Blue',
    'Wool Green',
    'Wool Red'
  ];
  string[] private TRAIT_GLASSES = ['None', 'Beetle', 'Nerd', 'Patch', 'Pilot', 'Surf', 'VR'];
  string[] private TRAIT_LIPSTICK_MALE = ['None'];
  string[] private TRAIT_LIPSTICK_FEMALE = ['None', 'Green', 'Orange', 'Pink', 'Purple', 'Red'];
  string[] private TRAIT_SMOKING = ['None', 'Cigar', 'Cigarette', 'E-Cigarette'];

  // Store of SVG layers
  mapping(bytes32 => LibSvg.SvgLayer[]) private svgLayers;

  // TokenId Resolver, used to get corresponding tokenId of a set of traits
  ITokenIdResolver public tokenIdResolver;

  /**
   * @dev Constructor
   * @param tokenIdResolver_ address of the contract resolver of token ids
   */
  constructor(address tokenIdResolver_) {
    tokenIdResolver = ITokenIdResolver(tokenIdResolver_);
  }

  /**
   * @notice Returns the amount of stored SVGs of a given type
   * @param svgType type of SVG
   * @return amount of stored SVGs
   */
  function getSizeOfSvgType(bytes32 svgType) external view returns (uint256) {
    return svgLayers[svgType].length;
  }

  /**
   * @notice Returns a SVG of a given type and id
   * @param svgType type of SVG
   * @param id id of SVG
   * @return SVG
   */
  function getSvg(bytes32 svgType, uint256 id) public view returns (bytes memory) {
    return LibSvg._getSvg(svgLayers[svgType], id);
  }

  /**
   * @notice Store a set of SVGs with the given types and sizes
   * @dev Only callable by owner
   * @param svgs set of SVGs
   * @param typesAndSizes array of types and sizes of the SVGs
   */
  function storeSvg(string calldata svgs, LibSvg.SvgTypeAndSizes[] calldata typesAndSizes)
    external
    onlyOwner
  {
    LibSvg._storeSvg(svgLayers, svgs, typesAndSizes);
  }

  /**
   * @notice Update a set of SVGs with the given types, ids and sizes
   * @dev Only callable by owner
   * @param svgs set of SVGs
   * @param typesAndIdsAndSizes array of types, ids and sizes of the SVGs
   */
  function updateSvg(
    string calldata svgs,
    LibSvg.SvgTypeAndIdsAndSizes[] calldata typesAndIdsAndSizes
  ) external onlyOwner {
    LibSvg._updateSvg(svgLayers, svgs, typesAndIdsAndSizes);
  }

  /**
   * @notice Returns the Uniform Resource Identifier (URI) given a set of traits
   * @param traits set of traits
   * @return token uri
   */
  function tokenURI(uint256[8] calldata traits) external view override returns (string memory) {
    uint256 tokenId = tokenIdResolver.getTokenId(traits);
    string memory traitsString = _buildAttributes(
      _traitsToAttributesString(traits),
      _getBreedAndSexString(traits[0])
    );
    string memory svg = _buildSvg(traits);
    return _buildTokenURI(tokenId, traitsString, svg);
  }

  /**
   * @notice Returns a base64 SVG given a set of traits
   * @param traits set of traits
   * @return SVG in base64 format
   */
  function _buildSvg(uint256[8] calldata traits) internal view returns (string memory) {
    bytes memory linesRendered;

    if (traits[0] % 2 == 0) {
      linesRendered = abi.encodePacked(
        getSvg(SVG_TYPE_BASE, traits[0]),
        getSvg(SVG_TYPE_NECKLACE_MALE, traits[1]),
        getSvg(SVG_TYPE_FACIAL_MALE, traits[2]),
        getSvg(SVG_TYPE_HEAD_MALE, traits[4]),
        getSvg(SVG_TYPE_EARRING_MALE, traits[3]),
        getSvg(SVG_TYPE_GLASSES_MALE, traits[5]),
        getSvg(SVG_TYPE_LIPSTICK_MALE, traits[6]),
        getSvg(SVG_TYPE_SMOKING_MALE, traits[7])
      );
    } else {
      linesRendered = abi.encodePacked(
        getSvg(SVG_TYPE_BASE, traits[0]),
        getSvg(SVG_TYPE_NECKLACE_FEMALE, traits[1]),
        getSvg(SVG_TYPE_FACIAL_FEMALE, traits[2]),
        getSvg(SVG_TYPE_HEAD_FEMALE, traits[4]),
        getSvg(SVG_TYPE_EARRING_FEMALE, traits[3]),
        getSvg(SVG_TYPE_GLASSES_FEMALE, traits[5]),
        getSvg(SVG_TYPE_LIPSTICK_FEMALE, traits[6]),
        getSvg(SVG_TYPE_SMOKING_FEMALE, traits[7])
      );
    }

    return
      Base64.encode(
        abi.encodePacked(
          '<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350" viewBox="0 -0.5 24 24" shape-rendering="crispEdges">',
          string(linesRendered),
          '</svg>'
        )
      );
  }

  /**
   * @notice Returns a stringify json of attributes given a set of attributes
   * @param attributes string array of attributes
   * @param breedAndSex breed and sex string
   * @return stringify json of attributes
   */
  function _buildAttributes(string[] memory attributes, string memory breedAndSex)
    internal
    pure
    returns (string memory)
  {
    string memory firstPart = string(
      abi.encodePacked(
        '"attributes":[{"trait_type":"Tier","value":"',
        attributes[0],
        '"},{"trait_type":"Sex","value":"',
        attributes[1],
        '"},{"trait_type":"Base","value":"',
        attributes[2],
        '"},{"trait_type":"Necklace","value":"',
        attributes[3],
        breedAndSex,
        '"},{"trait_type":"Facial","value":"',
        attributes[4],
        breedAndSex,
        '"},{"trait_type":"Earring","value":"',
        attributes[5]
      )
    );
    string memory secondPart = string(
      abi.encodePacked(
        breedAndSex,
        '"},{"trait_type":"Head","value":"',
        attributes[6],
        breedAndSex,
        '"},{"trait_type":"Glasses","value":"',
        attributes[7],
        breedAndSex,
        '"},{"trait_type":"Lipstick","value":"',
        attributes[8],
        breedAndSex,
        '"},{"trait_type":"Smoking","value":"',
        attributes[9],
        breedAndSex,
        '"}]'
      )
    );

    return string(abi.encodePacked(firstPart, secondPart));
  }

  /**
   * @notice Returns the token uri
   * @param tokenId id of the token
   * @param traits string array of traits
   * @param imageSVG SVG
   * @return token uri in base64
   */
  function _buildTokenURI(
    uint256 tokenId,
    string memory traits,
    string memory imageSVG
  ) internal pure returns (string memory) {
    return
      string(
        abi.encodePacked(
          'data:application/json;base64,',
          Base64.encode(
            bytes(
              abi.encodePacked(
                '{"name":"Decentralist #',
                Strings.toString(tokenId),
                '","description":"Decentralists is the collection for those who believe in the revolutionary power of crypto technology. Each one represents a customizable and unique combination stored 100% in the Ethereum blockchain.",',
                traits,
                ',"background_color":"12223B","image":"',
                'data:image/svg+xml;base64,',
                imageSVG,
                '"}'
              )
            )
          )
        )
      );
  }

  /**
   * @notice Returns the array of attributes in string for a given set of traits
   * @param traits set of traits
   * @return string array of attributes
   */
  function _traitsToAttributesString(uint256[8] calldata traits)
    internal
    view
    returns (string[] memory)
  {
    string[] memory traitsToString = new string[](10);
    traitsToString[0] = traits[0] < 8 ? 'Standard' : 'Premium';
    traitsToString[1] = traits[0] % 2 == 0 ? 'Male' : 'Female';
    if (traits[0] % 2 == 0) {
      traitsToString[2] = TRAIT_BASE[traits[0]];
      traitsToString[3] = TRAIT_NECKLACE[traits[1]];
      traitsToString[4] = TRAIT_FACIAL_MALE[traits[2]];
      traitsToString[5] = TRAIT_EARRING[traits[3]];
      traitsToString[6] = TRAIT_HEAD_MALE[traits[4]];
      traitsToString[7] = TRAIT_GLASSES[traits[5]];
      traitsToString[8] = TRAIT_LIPSTICK_MALE[traits[6]];
      traitsToString[9] = TRAIT_SMOKING[traits[7]];
    } else {
      traitsToString[2] = TRAIT_BASE[traits[0]];
      traitsToString[3] = TRAIT_NECKLACE[traits[1]];
      traitsToString[4] = TRAIT_FACIAL_FEMALE[traits[2]];
      traitsToString[5] = TRAIT_EARRING[traits[3]];
      traitsToString[6] = TRAIT_HEAD_FEMALE[traits[4]];
      traitsToString[7] = TRAIT_GLASSES[traits[5]];
      traitsToString[8] = TRAIT_LIPSTICK_FEMALE[traits[6]];
      traitsToString[9] = TRAIT_SMOKING[traits[7]];
    }
    return traitsToString;
  }

  /**
   * @notice Returns an string indicating the breed and sex given the base trait
   * @param base base trait
   * @return breed and sex string
   */
  function _getBreedAndSexString(uint256 base) internal pure returns (string memory) {
    string memory breed;
    if (base / 2 < 4) {
      breed = ' (Human ';
    } else if (base / 2 == 4) {
      breed = ' (Vampire ';
    } else if (base / 2 == 5) {
      breed = ' (Metahuman ';
    } else {
      breed = ' (Ape ';
    }
    return string(abi.encodePacked(breed, base % 2 == 0 ? 'Male)' : 'Female)'));
  }
}
function tokenURI(uint256[8] calldata traits) external view override returns (string memory) {
  uint256 tokenId = tokenIdResolver.getTokenId(traits);
  string memory traitsString = _buildAttributes(
    _traitsToAttributesString(traits),
    _getBreedAndSexString(traits[0])
  );
  string memory svg = _buildSvg(traits);
  return _buildTokenURI(tokenId, traitsString, svg);
}

/**
 * @notice Returns the Uniform Resource Identifier (URI) given a set of traits
 * @param traits set of traits
 * @return token uri
 */