#include "CMDLMaterials.hpp"
#include "../DNAMP2/CMDLMaterials.hpp"
#include "hecl/Blender/Connection.hpp"

using Stream = hecl::blender::PyOutStream;

namespace DataSpec::DNAMP1 {
using Material = MaterialSet::Material;

void MaterialSet::RegisterMaterialProps(Stream& out) {
  out << "bpy.types.Material.retro_depth_sort = bpy.props.BoolProperty(name='Retro: Transparent Depth Sort')\n"
         "bpy.types.Material.retro_alpha_test = bpy.props.BoolProperty(name='Retro: Punchthrough Alpha')\n"
         "bpy.types.Material.retro_samus_reflection = bpy.props.BoolProperty(name='Retro: Samus Reflection')\n"
         "bpy.types.Material.retro_depth_write = bpy.props.BoolProperty(name='Retro: Depth Write')\n"
         "bpy.types.Material.retro_samus_reflection_persp = bpy.props.BoolProperty(name='Retro: Samus Reflection "
         "Perspective')\n"
         "bpy.types.Material.retro_shadow_occluder = bpy.props.BoolProperty(name='Retro: Shadow Occluder')\n"
         "bpy.types.Material.retro_samus_reflection_indirect = bpy.props.BoolProperty(name='Retro: Samus Reflection "
         "Indirect Texture')\n"
         "bpy.types.Material.retro_lightmapped = bpy.props.BoolProperty(name='Retro: Lightmapped')\n"
         "\n";
}

void Material::AddTexture(Stream& out, GX::TexGenSrc type, int mtxIdx, uint32_t texIdx, bool diffuse) {
  std::string mtxLabel;
  if (mtxIdx == -1)
    mtxLabel = "IDENTITY";
  else
    mtxLabel = fmt::format(FMT_STRING("MTX_{}"), mtxIdx);

  std::string texLabel;
  if (diffuse)
    texLabel = "Diffuse";
  else
    texLabel = "Texture";

  out.format(FMT_STRING("# Texture\n"
                 "tex_node = new_nodetree.nodes.new('ShaderNodeTexImage')\n"
                 "tex_node.label = '{} {}'\n"
                 "texture_nodes.append(tex_node)\n"),
             texLabel, texIdx);

  if (texIdx != 0xff)
    out.format(FMT_STRING("tex_node.image = tex_maps[{}]\n"), texIdx);

  if (type == GX::TG_POS)
    out << "tex_uv_node = new_nodetree.nodes.new('ShaderNodeTexCoord')\n"
           "tex_links.append(new_nodetree.links.new(tex_uv_node.outputs['Window'], tex_node.inputs['Vector']))\n";
  else if (type == GX::TG_NRM)
    out << "tex_uv_node = new_nodetree.nodes.new('ShaderNodeTexCoord')\n"
           "tex_links.append(new_nodetree.links.new(tex_uv_node.outputs['Normal'], tex_node.inputs['Vector']))\n";
  else if (type >= GX::TG_TEX0 && type <= GX::TG_TEX7) {
    uint8_t texIdx = type - GX::TG_TEX0;
    out.format(FMT_STRING("tex_uv_node = new_nodetree.nodes.new('ShaderNodeUVMap')\n"
                   "tex_links.append(new_nodetree.links.new(tex_uv_node.outputs['UV'], tex_node.inputs['Vector']))\n"
                   "tex_uv_node.uv_map = 'UV_{}'\n"),
               texIdx);
  }

  out.format(FMT_STRING("tex_uv_node.label = '{}'\n"), mtxLabel);

  out << "gridder.place_node(tex_uv_node, 0)\n"
         "gridder.place_node(tex_node, 0)\n"
         "tex_uv_node.location[0] -= 120\n"
         "tex_node.location[0] += 120\n"
         "tex_node.location[1] += 176\n"
         "\n";
}

void Material::AddTextureAnim(Stream& out, UVAnimation::Mode type, unsigned idx, const float* vals) {
  switch (type) {
  case UVAnimation::Mode::MvInvNoTranslation:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode0NodeN']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx);
    break;
  case UVAnimation::Mode::MvInv:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode1NodeN']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx);
    break;
  case UVAnimation::Mode::Scroll:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode2Node']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = ({},{},0)\n"
                   "        node.inputs[2].default_value = ({},{},0)\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1], vals[2], vals[3]);
    break;
  case UVAnimation::Mode::Rotation:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode3Node']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = {}\n"
                   "        node.inputs[2].default_value = {}\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1]);
    break;
  case UVAnimation::Mode::HStrip:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode4Node']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = {}\n"
                   "        node.inputs[2].default_value = {}\n"
                   "        node.inputs[3].default_value = {}\n"
                   "        node.inputs[4].default_value = {}\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1], vals[2], vals[3]);
    break;
  case UVAnimation::Mode::VStrip:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode5Node']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = {}\n"
                   "        node.inputs[2].default_value = {}\n"
                   "        node.inputs[3].default_value = {}\n"
                   "        node.inputs[4].default_value = {}\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1], vals[2], vals[3]);
    break;
  case UVAnimation::Mode::Model:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode6NodeN']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx);
    break;
  case UVAnimation::Mode::CylinderEnvironment:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode7NodeN']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = {}\n"
                   "        node.inputs[2].default_value = {}\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1]);
    break;
  case UVAnimation::Mode::Eight:
    out.format(FMT_STRING("for link in list(tex_links):\n"
                   "    if link.from_node.label == 'MTX_{}':\n"
                   "        tex_links.remove(link)\n"
                   "        soc_from = link.from_socket\n"
                   "        soc_to = link.to_socket\n"
                   "        node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
                   "        node.node_tree = bpy.data.node_groups['RetroUVMode8Node']\n"
                   "        node.location[0] = link.from_node.location[0] + 50\n"
                   "        node.location[1] = link.from_node.location[1] - 50\n"
                   "        node.inputs[1].default_value = {}\n"
                   "        node.inputs[2].default_value = {}\n"
                   "        node.inputs[3].default_value = {}\n"
                   "        node.inputs[4].default_value = {}\n"
                   "        node.inputs[5].default_value = {}\n"
                   "        node.inputs[6].default_value = {}\n"
                   "        node.inputs[7].default_value = {}\n"
                   "        node.inputs[8].default_value = {}\n"
                   "        node.inputs[9].default_value = {}\n"
                   "        new_nodetree.links.remove(link)\n"
                   "        new_nodetree.links.new(soc_from, node.inputs[0])\n"
                   "        new_nodetree.links.new(node.outputs[0], soc_to)\n\n"),
               idx, vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7], vals[8]);
    break;
  default:
    break;
  }
}

void Material::AddKcolor(Stream& out, const GX::Color& col, unsigned idx) {
  out.format(FMT_STRING("kcolors[{}] = ({}, {}, {}, {})\n"
                 "kalphas[{}] = {}\n"
                 "\n"),
             idx, (float)col.color[0] / (float)0xff, (float)col.color[1] / (float)0xff,
             (float)col.color[2] / (float)0xff, (float)col.color[3] / (float)0xff, idx,
             (float)col.color[3] / (float)0xff);
}

template <class MAT>
static uint32_t _HashTextureConfig(const MAT& mat) {
  XXH32_state_t xxHash;
  XXH32_reset(&xxHash, 0);
  for (uint32_t i = 0; i < mat.tevStageCount; ++i) {
    const auto& stage = mat.tevStages[i];
    XXH32_update(&xxHash, &stage.ciFlags, sizeof(stage.ciFlags));
    XXH32_update(&xxHash, &stage.aiFlags, sizeof(stage.aiFlags));
    XXH32_update(&xxHash, &stage.ccFlags, sizeof(stage.ccFlags));
    XXH32_update(&xxHash, &stage.acFlags, sizeof(stage.acFlags));
    XXH32_update(&xxHash, &stage.kaInput, sizeof(stage.kaInput));
    XXH32_update(&xxHash, &stage.kcInput, sizeof(stage.kcInput));
    XXH32_update(&xxHash, &stage.rascInput, sizeof(stage.rascInput));
  }
  bool hasInd = mat.flags.samusReflectionIndirectTexture();
  XXH32_update(&xxHash, &hasInd, sizeof(hasInd));
  bool hasLm = mat.flags.lightmap();
  XXH32_update(&xxHash, &hasLm, sizeof(hasLm));
  return XXH32_digest(&xxHash);
}

static const char* ToString(GX::TevColorArg arg) {
  switch (arg) {
  case GX::CC_CPREV:
    return "CC_CPREV";
  case GX::CC_APREV:
    return "CC_APREV";
  case GX::CC_C0:
    return "CC_C0";
  case GX::CC_A0:
    return "CC_A0";
  case GX::CC_C1:
    return "CC_C1";
  case GX::CC_A1:
    return "CC_A1";
  case GX::CC_C2:
    return "CC_C2";
  case GX::CC_A2:
    return "CC_A2";
  case GX::CC_TEXC:
    return "CC_TEXC";
  case GX::CC_TEXA:
    return "CC_TEXA";
  case GX::CC_RASC:
    return "CC_RASC";
  case GX::CC_RASA:
    return "CC_RASA";
  case GX::CC_ONE:
    return "CC_ONE";
  case GX::CC_HALF:
    return "CC_HALF";
  case GX::CC_KONST:
    return "CC_KONST";
  case GX::CC_ZERO:
    return "CC_ZERO";
  default:
    return "UNKNOWN";
  }
}

static const char* ToString(GX::TevAlphaArg arg) {
  switch (arg) {
  case GX::CA_APREV:
    return "CA_APREV";
  case GX::CA_A0:
    return "CA_A0";
  case GX::CA_A1:
    return "CA_A1";
  case GX::CA_A2:
    return "CA_A2";
  case GX::CA_TEXA:
    return "CA_TEXA";
  case GX::CA_RASA:
    return "CA_RASA";
  case GX::CA_KONST:
    return "CA_KONST";
  case GX::CA_ZERO:
    return "CA_ZERO";
  default:
    return "UNKNOWN";
  }
}

static const char* ToString(GX::TevRegID arg) {
  switch (arg) {
  case GX::TEVPREV:
    return "TEVPREV";
  case GX::TEVREG0:
    return "TEVREG0";
  case GX::TEVREG1:
    return "TEVREG1";
  case GX::TEVREG2:
    return "TEVREG2";
  default:
    return "UNKNOWN";
  }
}

template <class MAT>
static void _DescribeTEV(const MAT& mat) {
  for (uint32_t i = 0; i < mat.tevStageCount; ++i) {
    const auto& stage = mat.tevStages[i];
    fmt::print(stderr, FMT_STRING("A:{} B:{} C:{} D:{} -> {} | A:{} B:{} C:{} D:{} -> {}\n"), ToString(stage.colorInA()),
               ToString(stage.colorInB()), ToString(stage.colorInC()), ToString(stage.colorInD()),
               ToString(stage.colorOpOutReg()), ToString(stage.alphaInA()), ToString(stage.alphaInB()),
               ToString(stage.alphaInC()), ToString(stage.alphaInD()), ToString(stage.alphaOpOutReg()));
  }
  bool hasInd = mat.flags.samusReflectionIndirectTexture();
  bool hasLm = mat.flags.lightmap();
  fmt::print(stderr, FMT_STRING("HasIndirect: {} HasLightmap: {}\n"), hasInd, hasLm);
}

struct TexLink {
  const char* shaderInput;
  int texidx;
  bool alpha;
  TexLink(const char* shaderInput, int texidx = -1, bool alpha = false)
  : shaderInput(shaderInput), texidx(texidx), alpha(alpha) {}
};

struct ExtendedSpecularLink {
  int texidx;
  ExtendedSpecularLink(int texidx = -1) : texidx(texidx) {}
};

struct KColLink {
  const char* shaderInput;
  int kcidx;
  bool alpha;
  KColLink(const char* shaderInput, int kcidx = 0, bool alpha = false)
  : shaderInput(shaderInput), kcidx(kcidx), alpha(alpha) {}
};

struct WhiteColorLink {
  const char* shaderInput;
  explicit WhiteColorLink(const char* shaderInput) : shaderInput(shaderInput) {}
};

static void _GenerateRootShader(Stream& out, int) { /* End of shader links */
}

template <typename... Targs>
static void _GenerateRootShader(Stream& out, int tidx, TexLink tex, Targs... args) {
  int texIdx = tex.texidx == -1 ? tidx : tex.texidx;
  out << "texture_nodes[" << texIdx << "].name = '" << tex.shaderInput << "'\n";
  out << "texture_nodes[" << texIdx << "].label = '" << tex.shaderInput << "'\n";
  out << "new_nodetree.links.new(texture_nodes[" << texIdx << "].outputs['" << (tex.alpha ? "Alpha" : "Color")
      << "'], node.inputs['" << tex.shaderInput << "'])\n";
  if (tex.texidx == -1)
    ++tidx;
  _GenerateRootShader(out, tidx, args...);
}

template <typename... Targs>
static void _GenerateRootShader(Stream& out, int tidx, ExtendedSpecularLink tex, Targs... args) {
  int texIdx = tex.texidx == -1 ? tidx : tex.texidx;
  out << "texture_nodes[" << texIdx << "].name = 'Specular'\n";
  out << "texture_nodes[" << texIdx << "].label = 'Specular'\n";
  out << "new_nodetree.links.new(texture_nodes[" << texIdx << "].outputs['Color'], node.inputs['Specular'])\n";
  out << "new_nodetree.links.new(texture_nodes[" << texIdx << "].outputs['Alpha'], node.inputs['ExtendedSpecular'])\n";
  if (tex.texidx == -1)
    ++tidx;
  _GenerateRootShader(out, tidx, args...);
}

template <typename... Targs>
static void _GenerateRootShader(Stream& out, int tidx, KColLink kcol, Targs... args) {
  out << "node.inputs['" << kcol.shaderInput << "'].default_value = " << (kcol.alpha ? "kalphas[" : "kcolors[")
      << kcol.kcidx << "]\n";
  _GenerateRootShader(out, tidx, args...);
}

template <typename... Targs>
static void _GenerateRootShader(Stream& out, int tidx, WhiteColorLink wcol, Targs... args) {
  out << "node.inputs['" << wcol.shaderInput << "'].default_value = (1.0, 1.0, 1.0, 1.0)\n";
  _GenerateRootShader(out, tidx, args...);
}

template <typename... Targs>
static void _GenerateRootShader(Stream& out, const char* type, Targs... args) {
  out << "node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
         "node.name = 'Output'\n"
         "node.node_tree = bpy.data.node_groups['"
      << type
      << "']\n"
         "gridder.place_node(node, 1)\n"
         "new_nodetree.links.new(node.outputs['Surface'], blend_node.inputs['Surface'])\n";
  _GenerateRootShader(out, 0, args...);
}

static TexLink operator"" _tex(const char* str, size_t) { return TexLink(str); }
static TexLink operator"" _texa(const char* str, size_t) { return TexLink(str, -1, true); }
static KColLink operator"" _kcol(const char* str, size_t) { return KColLink(str); }
static KColLink operator"" _kcola(const char* str, size_t) { return KColLink(str, 0, true); }

template <class MAT>
static void _ConstructMaterial(Stream& out, const MAT& material, unsigned groupIdx, unsigned matIdx) {
  unsigned i;

  out.format(FMT_STRING("new_material = bpy.data.materials.new('MAT_{}_{}')\n"), groupIdx, matIdx);
  out << "new_material.use_fake_user = True\n"
         "new_material.use_nodes = True\n"
         "new_material.use_backface_culling = True\n"
         "new_material.blend_method = 'BLEND'\n"
         "new_nodetree = new_material.node_tree\n"
         "for n in new_nodetree.nodes:\n"
         "    new_nodetree.nodes.remove(n)\n"
         "\n"
         "gridder = hecl.Nodegrid(new_nodetree)\n"
         "\n"
         "texture_nodes = []\n"
         "kcolors = {}\n"
         "kalphas = {}\n"
         "tex_links = []\n"
         "\n";

  /* Material Flags */
  out.format(FMT_STRING("new_material.retro_depth_sort = {}\n"
                 "new_material.retro_alpha_test = {}\n"
                 "new_material.retro_samus_reflection = {}\n"
                 "new_material.retro_depth_write = {}\n"
                 "new_material.retro_samus_reflection_persp = {}\n"
                 "new_material.retro_shadow_occluder = {}\n"
                 "new_material.retro_samus_reflection_indirect = {}\n"
                 "new_material.retro_lightmapped = {}\n"
                 "new_material.diffuse_color = (1, 1, 1, {})\n"),
             material.flags.depthSorting() ? "True" : "False", material.flags.alphaTest() ? "True" : "False",
             material.flags.samusReflection() ? "True" : "False", material.flags.depthWrite() ? "True" : "False",
             material.flags.samusReflectionSurfaceEye() ? "True" : "False",
             material.flags.shadowOccluderMesh() ? "True" : "False",
             material.flags.samusReflectionIndirectTexture() ? "True" : "False",
             material.flags.lightmap() ? "True" : "False", material.flags.shadowOccluderMesh() ? "0" : "1");

  /* Texture Indices */
  out << "tex_maps = []\n";
  for (atUint32 idx : material.textureIdxs)
    out.format(FMT_STRING("tex_maps.append(texmap_list[{}])\n"), idx);

  /* KColor entries */
  if (material.flags.konstValuesEnabled()) {
    unsigned i = 0;
    for (const GX::Color& col : material.konstColors)
      Material::AddKcolor(out, col, i++);
  }

  /* Blend factors */
  out << "blend_node = new_nodetree.nodes.new('ShaderNodeGroup')\n"
         "blend_node.name = 'Blend'\n"
         "gridder.place_node(blend_node, 2)\n";
  using BlendFactor = Material::BlendFactor;
  if (material.blendDstFac != BlendFactor::BL_ZERO) {
    if (material.blendDstFac == BlendFactor::BL_ONE)
      out << "blend_node.node_tree = bpy.data.node_groups['HECLAdditiveOutput']\n";
    else
      out << "blend_node.node_tree = bpy.data.node_groups['HECLBlendOutput']\n";
  } else {
    out << "blend_node.node_tree = bpy.data.node_groups['HECLOpaqueOutput']\n"
           "new_material.blend_method = 'OPAQUE'\n";
  }

  /* Add texture maps/tcgs */
  unsigned addedTcgs = 0;
  bool diffuseStage = false;
  for (i = 0; i < material.tevStageCount; ++i) {
    if (material.tevStageTexInfo[i].tcgSlot != 0xff && !(addedTcgs >> material.tevStageTexInfo[i].tcgSlot & 1)) {
      const Material::TexCoordGen& tcg = material.tcgs[material.tevStageTexInfo[i].tcgSlot];
      GX::TexMtx mtx = tcg.mtx();
      int mtxIdx = -1;
      if (mtx >= GX::TEXMTX0 && mtx <= GX::TEXMTX9)
        mtxIdx = (mtx - GX::TEXMTX0) / 3;
      Material::AddTexture(out, tcg.source(), mtxIdx, material.tevStageTexInfo[i].texSlot, diffuseStage);
      addedTcgs |= 1 << material.tevStageTexInfo[i].tcgSlot;
    }
    diffuseStage = material.tevStages[i].colorOpOutReg() == GX::TEVREG0;
  }

  /* Indirect texture node */
  if (material.flags.samusReflectionIndirectTexture())
    Material::AddTexture(out, GX::TexGenSrc::TG_POS, -1, material.indTexSlot[0], false);

  /* Select appropriate root shader and link textures */
  uint32_t hash = _HashTextureConfig(material);
  switch (hash) {
  case 0x03FEE002: /* RetroShader: Diffuse, Emissive, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, "Reflection"_tex);
    break;
  case 0x0473AE40: /* RetroShader: Lightmap, Diffuse, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0x072D2CB3: /* RetroShader: Diffuse, Emissive, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, WhiteColorLink("Specular"),
                        "Reflection"_tex);
    break;
  case 0x07AA75D7: /* RetroShader: Diffuse, Emissive, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, TexLink("Alpha", 0, true));
    break;
  case 0x0879D346: /* RetroShader: KColorDiffuse, Alpha=Texture */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_kcol, "Alpha"_tex);
    break;
  case 0x0DA256BB: /* Lightmap, Diffuse, Specular, Reflection, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "Reflection"_tex,
                        "Alpha"_kcola);
    break;
  case 0x11C41DA4: /* RetroDynamicCharacterShader: Diffuse, DynamicMaskTex, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicCharacterShader", "Diffuse"_tex, "Emissive"_tex, "Specular"_tex,
                        "Reflection"_tex);
    break;
  case 0x1218F83E: /* RetroShader: ObjLightmap, Diffuse, ExtendedSpecular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, ExtendedSpecularLink(), "Reflection"_tex,
                        TexLink("Alpha", 1, true));
    break;
  case 0x129B8578: /* RetroShader: KColorDiffuse, Emissive, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_kcol, "Emissive"_tex, "Alpha"_kcola);
    break;
  case 0x15A00948: /* RetroShader: Diffuse, Emissive, Specular, ExtendedSpecular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, "Specular"_tex, "ExtendedSpecular"_tex,
                        "Reflection"_tex);
    break;
  case 0x15A3E6E5: /* RetroShader: Diffuse, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Alpha"_kcola);
    break;
  case 0x1BEB3E15: /* RetroShader: Diffuse, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, TexLink("Alpha", 0, true));
    break;
  case 0x2261E0EB: /* RetroShader: Diffuse, Emissive, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, "Specular"_tex, "Reflection"_tex);
    break;
  case 0x239C7724: /* RetroDynamicShader: Diffuse*Dynamic, Emissive*Dynamic, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0x240C4C84: /* RetroShader: Lightmap, KColorDiffuse, Specular, Reflection, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_kcol, "Specular"_tex, "Reflection"_tex,
                        "Alpha"_kcola);
    break;
  case 0x2523A379: /* RetroDynamicShader: Emissive*Dynamic, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Emissive"_tex, "Specular"_tex, "Reflection"_tex);
    break;
  case 0x25E85017: /* RetroShader: Lightmap, KColorDiffuse, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_kcol, "Alpha"_kcola);
    break;
  case 0x27FD5C6C: /* RetroShader: ObjLightmap, Diffuse, Specular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "Reflection"_tex,
                        TexLink("Alpha", 1, true));
    break;
  case 0x2AD9F535: /* RetroShader: Emissive, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Emissive"_tex, WhiteColorLink("Specular"), "Reflection"_tex);
    break;
  case 0x2C9F5104: /* RetroShader: Diffuse, Specular, Reflection, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, "Alpha"_kcola);
    break;
  case 0x2D059429: /* RetroShader: Diffuse, Emissive, ExtendedSpecular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, ExtendedSpecularLink(), "Reflection"_tex);
    break;
  case 0x30AC64BB: /* RetroShader: Diffuse, Specular, Reflection, Alpha=KAlpha, IndirectTex */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, "IndirectTex"_tex,
                        "Alpha"_kcola);
    break;
  case 0x39BC4809: /* RetroDynamicShader: ObjLightmap*Dynamic, Diffuse*Dynamic, Emissive*Dynamic, Specular, Reflection,
                      Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, "Specular"_tex,
                        "Reflection"_tex);
    break;
  case 0x3BF97299: /* RetroShader: Lightmap, Diffuse, Specular, Reflection, Alpha=KAlpha, IndirectTex */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "Reflection"_tex,
                        "IndirectTex"_tex, "Alpha"_kcola);
    break;
  case 0x4184FBCA: /* RetroShader: Lightmap, Diffuse, Emissive, DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, TexLink("Alpha", 1, true));
    break;
  case 0x47ECF3ED: /* RetroShader: Diffuse, Specular, Reflection, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, "Emissive"_tex);
    break;
  case 0x4BBDFFA6: /* RetroShader: Diffuse, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0x4D4127A3: /* RetroShader: Lightmap, Diffuse, Specular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "Reflection"_tex,
                        TexLink("Alpha", 1, true));
    break;
  case 0x54A92F25: /* RetroShader: ObjLightmap, KColorDiffuse, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_kcol, "Alpha"_kcola);
    break;
  case 0x58BAA415: /* RetroShader: Lightmap, Diffuse, Emissive, Alpha=1.0 */
    // TODO: Last stage assigns into unused reg2, perhaps for runtime material mod?
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0x54C6204C:
    _GenerateRootShader(out, "RetroShader");
    break;
  case 0x5A62D5F0: /* RetroShader: Lightmap, Diffuse, UnusedExtendedSpecular?, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0x5CB59821: /* RetroShader: Diffuse, UnusedSpecular?, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Alpha"_kcola);
    break;
  case 0x5D0F0069: /* RetroShader: Diffuse, Emissive, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, TexLink("Alpha", 0, true));
    break;
  case 0x5D80E53C: /* RetroShader: Emissive, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Emissive"_tex, "Specular"_tex, "Reflection"_tex);
    break;
  case 0x5F0AB0E9: /* RetroShader: Lightmap, Diffuse, UnusedSpecular?, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0x5F189425: /* RetroShader: Lightmap, Diffuse, UnusedSpecular?, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Alpha"_kcola);
    break;
  case 0x6601D113: /* RetroShader: Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Emissive"_tex);
    break;
  case 0x694287FA: /* RetroShader: Diffuse, Emissive, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, WhiteColorLink("Specular"),
                        "Reflection"_tex);
    break;
  case 0x6D98D689: /* RetroDynamicAlphaShader: Diffuse*Dynamic, Specular, Reflection, Alpha=KAlpha*Dynamic */
    _GenerateRootShader(out, "RetroDynamicAlphaShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, "Alpha"_kcola);
    break;
  case 0x7252CB90: /* RetroShader: Lightmap, Diffuse, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Alpha"_kcola);
    break;
  case 0x72BEDDAC: /* RetroShader: DiffuseMod, Diffuse, Emissive, Specular, Reflection Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "DiffuseMod"_tex, "Diffuse"_tex, "Emissive"_tex, "Specular"_tex,
                        "Reflection"_tex);
    break;
  case 0x76BEA57E: /* RetroShader: Lightmap, Diffuse, Emissive, Specular, Reflection, Alpha=1.0, IndirectTex */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, "Specular"_tex,
                        "Reflection"_tex, "IndirectTex"_tex);
    break;
  case 0x7D6A4487: /* RetroShader: Diffuse, Specular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, TexLink("Alpha", 0, true));
    break;
  case 0x81106196: /* RetroDynamicShader: Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Emissive"_tex);
    break;
  case 0x84319328: /* RetroShader: Reflection, UnusedSpecular?, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", WhiteColorLink("Specular"), "Reflection"_tex);
    break;
  case 0x846215DA: /* RetroShader: Diffuse, Specular, Reflection, Alpha=DiffuseAlpha, IndirectTex */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Specular"_tex, "Reflection"_tex, "IndirectTex"_tex,
                        TexLink("Alpha", 0, true));
    break;
  case 0x8C562AB1: /* RetroShader: Diffuse, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0x8E916C01: /* RetroShader: NULL, all inputs 0 */
    _GenerateRootShader(out, "RetroShader");
    break;
  case 0x957709F8: /* RetroShader: Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Emissive"_tex);
    break;
  case 0x96ABB2D3: /* RetroShader: Lightmap, Diffuse, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0x985A0B67: /* RetroShader: Diffuse, UnusedSpecular?, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, TexLink("Alpha", 0, true));
    break;
  case 0x9B4453A2: /* RetroShader: Diffuse, Emissive, ExtendedSpecular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, ExtendedSpecularLink(), "Reflection"_tex);
    break;
  case 0xA187C630: /* RetroShader: Diffuse, Emissive, UnusedReflection?, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0xB26E9E2E: /* RetroShader: Diffuse, Emissive, Alpha=1.0 */
    // TODO: Last two stages assign into unused reg2, perhaps for runtime material mod?
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0xC0E3FF1F: /* RetroShader: KColorDiffuse, Specular, Reflection, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_kcol, "Specular"_tex, "Reflection"_tex, "Alpha"_kcola);
    break;
  case 0xC138DCFA: /* RetroShader: Diffuse, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0xC3C8B1C8: /* RetroShader: KColorDiffuse, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_kcol, "Alpha"_kcola);
    break;
  case 0xC689C8C6: /* RetroShader: Diffuse, ExtendedSpecular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, ExtendedSpecularLink(), "Reflection"_tex,
                        TexLink("Alpha", 0, true));
    break;
  case 0xC6B18B28: /* RetroShader: Diffuse, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, TexLink("Alpha", 0, true));
    break;
  case 0xCD92D4C5: /* RetroShader: Diffuse, Reflection, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, WhiteColorLink("Specular"), "Reflection"_tex, "Alpha"_kcola);
    break;
  case 0xCE06F3F2: /* RetroShader: Diffuse, Alpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0xD73E7728: /* RetroShader: ObjLightmap, Diffuse, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0xDB8F01AD: /* RetroDynamicShader: Diffuse*Dynamic, Emissive*Dynamic, UnusedSpecular?, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0xE64D1085: /* RetroShader: Lightmap, Diffuse, Emissive, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, "Reflection"_tex,
                        TexLink("Alpha", 1, true));
    break;
  case 0xE6784B10: /* RetroShader: Lightmap, Diffuse, Specular, Reflection, Alpha=DiffuseAlpha, IndirectTex */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "Reflection"_tex,
                        "IndirectTex"_tex, TexLink("Alpha", 1, true));
    break;
  case 0xE68FF182: /* RetroShader: Diffuse, Emissive, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, "Emissive"_tex, "Specular"_tex, "Reflection"_tex);
    break;
  case 0xE92F1340: /* RetroShader: Diffuse, Alpha=DiffuseAlpha*AlphaMod */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_tex, TexLink("Alpha", 0, true), TexLink("AlphaMod", 1, true));
    break;
  case 0xEB4645CF: /* RetroDynamicAlphaShader: Diffuse*Dynamic, Alpha=DiffuseAlpha*Dynamic */
    _GenerateRootShader(out, "RetroDynamicAlphaShader", "Diffuse"_tex, TexLink("Alpha", 0, true));
    break;
  case 0xECEF8D1F: /* RetroDynamicShader: Diffuse*Dynamic, Emissive*Dynamic, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroDynamicShader", "Diffuse"_tex, "Emissive"_tex, "Specular"_tex, "Reflection"_tex);
    break;
  case 0xF1C26570: /* RetroShader: Lightmap, Diffuse, Specular, ExtendedSpecular, Reflection, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Specular"_tex, "ExtendedSpecular"_tex,
                        "Reflection"_tex, TexLink("Alpha", 1, true));
    break;
  case 0xF345C16E: /* RetroShader: Emissive, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Emissive"_tex, "Reflection"_tex);
    break;
  case 0xF4DA0A86: /* RetroShader: KColorDiffuse, Emissive, Alpha=KAlpha */
    _GenerateRootShader(out, "RetroShader", "Diffuse"_kcol, "Emissive"_tex, "Alpha"_kcola); break;
    break;
  case 0xF559DB08: /* RetroShader: Lightmap, Diffuse, Emissive, Specular, Reflection, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, "Specular"_tex,
                        "Reflection"_tex);
    break;
  case 0xF9324367: /* RetroShader: Lightmap, Diffuse, Emissive, Alpha=1.0 */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex);
    break;
  case 0xFC2761B8: /* RetroShader: Lightmap, Diffuse, Alpha=DiffuseAlpha*AlphaMod */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true),
                        TexLink("AlphaMod", 2, true));
    break;
  case 0xFD95D7FD: /* RetroShader: ObjLightmap, Diffuse, Alpha=DiffuseAlpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, TexLink("Alpha", 1, true));
    break;
  case 0xFFF3CEBB: /* RetroShader: Lightmap, Diffuse, Emissive, Alpha */
    _GenerateRootShader(out, "RetroShader", "Lightmap"_tex, "Diffuse"_tex, "Emissive"_tex, TexLink("Alpha", 3, true));
    break;
  default:
    _DescribeTEV(material);
    Log.report(logvisor::Fatal, FMT_STRING("Unable to resolve shader hash {:08X}\n"), hash);
    break;
  }

  /* Has Lightmap? */
  if (material.flags.lightmap()) {
    if (material.tevStageTexInfo[0].texSlot != 0xff)
      out << "new_material.hecl_lightmap = tex_maps[0].name\n"
             "tex_maps[0].use_fake_user = True\n";
  }

  /* Texmtx Animation Section */
  i = 0;
  for (const Material::UVAnimation& anim : material.uvAnims)
    Material::AddTextureAnim(out, anim.mode, i++, anim.vals);
}

void MaterialSet::ConstructMaterial(Stream& out, const MaterialSet::Material& material, unsigned groupIdx,
                                    unsigned matIdx) {
  _ConstructMaterial(out, material, groupIdx, matIdx);
}

MaterialSet::Material::Material(const hecl::blender::Material& mat, std::vector<hecl::ProjectPath>& texPathsOut,
                                int colorCount, bool lightmapUVs, bool matrixSkinning) {
  /* TODO: Rewrite for new shader rep */
  XXH32_state_t xxHash;
  XXH32_reset(&xxHash, 0);

#if 0
  if (gx.m_kcolorCount) {
    flags.setKonstValuesEnabled(true);
    konstCount.push_back(gx.m_kcolorCount);
  }
#endif

  auto search = mat.iprops.find("retro_depth_sort");
  if (search != mat.iprops.end())
    flags.setDepthSorting(search->second != 0);

  search = mat.iprops.find("retro_alpha_test");
  if (search != mat.iprops.end())
    flags.setAlphaTest(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection");
  if (search != mat.iprops.end())
    flags.setSamusReflection(search->second != 0);

  search = mat.iprops.find("retro_depth_write");
  if (search != mat.iprops.end())
    flags.setDepthWrite(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection_persp");
  if (search != mat.iprops.end())
    flags.setSamusReflectionSurfaceEye(search->second != 0);

  search = mat.iprops.find("retro_shadow_occluder");
  if (search != mat.iprops.end())
    flags.setShadowOccluderMesh(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection_indirect");
  if (search != mat.iprops.end())
    flags.setSamusReflectionIndirectTexture(search->second != 0);

  search = mat.iprops.find("retro_lightmapped");
  if (search != mat.iprops.end())
    flags.setLightmap(search->second != 0);

  flags.setLightmapUVArray(lightmapUVs);

#if 0
  atUint16 texFlags = 0;
  atUint16 tcgFlags = 0;
  tevStageTexInfo.reserve(gx.m_tevCount);
  textureIdxs.reserve(gx.m_tevCount);
  for (unsigned i = 0; i < gx.m_tevCount; ++i) {
    const hecl::Backend::GX::TEVStage& stage = gx.m_tevs[i];
    tevStageTexInfo.emplace_back();
    TEVStageTexInfo& texInfo = tevStageTexInfo.back();
    if (stage.m_texGenIdx != -1) {
      texInfo.tcgSlot = stage.m_texGenIdx;
      const hecl::Backend::GX::TexCoordGen& tcg = gx.m_tcgs[stage.m_texGenIdx];
      if (tcg.m_src >= hecl::Backend::GX::TG_TEX0 && tcg.m_src <= hecl::Backend::GX::TG_TEX6)
        tcgFlags |= 1 << (tcg.m_src - hecl::Backend::GX::TG_TEX0);
    }
    if (stage.m_texMapIdx != -1) {
      texInfo.texSlot = textureIdxs.size();
      const hecl::ProjectPath& texPath = texPathsIn.at(stage.m_texMapIdx);
      texFlags |= 1 << i;
      ++textureCount;
      bool found = false;
      for (size_t t = 0; t < texPathsOut.size(); ++t) {
        if (texPath == texPathsOut[t]) {
          found = true;
          textureIdxs.push_back(t);
          break;
        }
      }
      if (!found) {
        textureIdxs.push_back(texPathsOut.size());
        texPathsOut.push_back(texPath);
      }
    }
  }
  flags.setTextureSlots(texFlags);

  XXH32_update(&xxHash, &flags.flags, sizeof(flags.flags));

  vaFlags.setPosition(GX::INDEX16);
  vaFlags.setNormal(GX::INDEX16);

  if (0 < colorCount)
    vaFlags.setColor0(GX::INDEX16);
  if (1 < colorCount)
    vaFlags.setColor1(GX::INDEX16);

  if (tcgFlags & (1 << 0))
    vaFlags.setTex0(GX::INDEX16);
  if (tcgFlags & (1 << 1))
    vaFlags.setTex1(GX::INDEX16);
  if (tcgFlags & (1 << 2))
    vaFlags.setTex2(GX::INDEX16);
  if (tcgFlags & (1 << 3))
    vaFlags.setTex3(GX::INDEX16);
  if (tcgFlags & (1 << 4))
    vaFlags.setTex4(GX::INDEX16);
  if (tcgFlags & (1 << 5))
    vaFlags.setTex5(GX::INDEX16);
  if (tcgFlags & (1 << 6))
    vaFlags.setTex6(GX::INDEX16);

  if (matrixSkinning) {
    vaFlags.setPnMatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 0))
      vaFlags.setTex0MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 1))
      vaFlags.setTex1MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 2))
      vaFlags.setTex2MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 3))
      vaFlags.setTex3MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 4))
      vaFlags.setTex4MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 5))
      vaFlags.setTex5MatIdx(GX::DIRECT);
    if (tcgFlags & (1 << 6))
      vaFlags.setTex6MatIdx(GX::DIRECT);
  }

  XXH32_update(&xxHash, &vaFlags.vaFlags, sizeof(vaFlags.vaFlags));

  XXH32_update(&xxHash, &gx.m_kcolorCount, sizeof(gx.m_kcolorCount));
  for (unsigned i = 0; i < gx.m_kcolorCount; ++i) {
    konstColors.emplace_back(gx.m_kcolors[i]);
    XXH32_update(&xxHash, &gx.m_kcolors[i].num, sizeof(gx.m_kcolors[i].num));
  }

  blendDstFac = BlendFactor(gx.m_blendDst);
  XXH32_update(&xxHash, &gx.m_blendDst, sizeof(gx.m_blendDst));
  blendSrcFac = BlendFactor(gx.m_blendSrc);
  XXH32_update(&xxHash, &gx.m_blendSrc, sizeof(gx.m_blendSrc));
  if (flags.samusReflectionIndirectTexture()) {
    indTexSlot.push_back(textureIdxs.size());
    XXH32_update(&xxHash, &indTexSlot.back(), sizeof(indTexSlot.back()));
  }

  colorChannelCount = 1;
  XXH32_update(&xxHash, &colorChannelCount, sizeof(colorChannelCount));
  colorChannels.emplace_back();
  ColorChannel& ch = colorChannels.back();
  for (unsigned i = 0; i < gx.m_tevCount; ++i) {
    const hecl::Backend::GX::TEVStage& stage = gx.m_tevs[i];
    for (int c = 0; c < 4; ++c)
      if (stage.m_color[c] == hecl::Backend::GX::CC_RASC || stage.m_color[c] == hecl::Backend::GX::CC_RASA ||
          stage.m_alpha[c] == hecl::Backend::GX::CA_RASA) {
        ch.setLighting(true);
        uint8_t one = 1;
        XXH32_update(&xxHash, &one, sizeof(one));
        break;
      }
    if (ch.lighting())
      break;
  }
  ch.setDiffuseFn(GX::DF_CLAMP);
  ch.setAttenuationFn(GX::AF_SPOT);

  tevStageCount = gx.m_tevCount;
  XXH32_update(&xxHash, &tevStageCount, sizeof(tevStageCount));
  tevStages.reserve(gx.m_tevCount);
  for (unsigned i = 0; i < gx.m_tevCount; ++i) {
    const hecl::Backend::GX::TEVStage& stage = gx.m_tevs[i];
    tevStages.emplace_back();
    TEVStage& target = tevStages.back();

    target.setColorInA(stage.m_color[0]);
    target.setColorInB(stage.m_color[1]);
    target.setColorInC(stage.m_color[2]);
    target.setColorInD(stage.m_color[3]);
    target.setAlphaInA(stage.m_alpha[0]);
    target.setAlphaInB(stage.m_alpha[1]);
    target.setAlphaInC(stage.m_alpha[2]);
    target.setAlphaInD(stage.m_alpha[3]);
    target.setColorOp(stage.m_cop);
    target.setColorOpBias(GX::TB_ZERO);
    target.setColorOpScale(GX::CS_SCALE_1);
    target.setColorOpClamp(true);
    target.setColorOpOutReg(stage.m_cRegOut);
    target.setAlphaOp(stage.m_aop);
    target.setAlphaOpBias(GX::TB_ZERO);
    target.setAlphaOpScale(GX::CS_SCALE_1);
    target.setAlphaOpClamp(true);
    target.setAlphaOpOutReg(stage.m_aRegOut);
    target.setKColorIn(stage.m_kColor);
    target.setKAlphaIn(stage.m_kAlpha);

    target.setRASIn(GX::GX_COLOR_NULL);
    for (int c = 0; c < 4; ++c)
      if (stage.m_color[c] == hecl::Backend::GX::CC_RASC || stage.m_color[c] == hecl::Backend::GX::CC_RASA ||
          stage.m_alpha[c] == hecl::Backend::GX::CA_RASA) {
        target.setRASIn(GX::GX_COLOR0A0);
        break;
      }

    XXH32_update(&xxHash, &target.ciFlags, sizeof(target.ciFlags));
    XXH32_update(&xxHash, &target.aiFlags, sizeof(target.aiFlags));
    XXH32_update(&xxHash, &target.ccFlags, sizeof(target.ccFlags));
    XXH32_update(&xxHash, &target.acFlags, sizeof(target.acFlags));
    XXH32_update(&xxHash, &target.kaInput, sizeof(target.kaInput));
    XXH32_update(&xxHash, &target.kcInput, sizeof(target.kcInput));
    XXH32_update(&xxHash, &target.rascInput, sizeof(target.rascInput));
  }

  tcgCount = gx.m_tcgCount;
  XXH32_update(&xxHash, &tcgCount, sizeof(tcgCount));
  for (unsigned i = 0; i < gx.m_tcgCount; ++i) {
    const hecl::Backend::GX::TexCoordGen& tcg = gx.m_tcgs[i];
    tcgs.emplace_back();
    TexCoordGen& target = tcgs.back();
    target.setType(GX::TG_MTX3x4);
    target.setSource(tcg.m_src);
    target.setMtx(tcg.m_mtx);
    target.setNormalize(tcg.m_norm);
    target.setPostMtx(tcg.m_pmtx);

    XXH32_update(&xxHash, &target.flags, sizeof(target.flags));
  }

  uvAnimsSize = 4;
  uvAnimsCount = 0;
  for (; uvAnimsCount < 8;) {
    bool found = false;
    for (unsigned t = 0; t < gx.m_tcgCount; ++t) {
      const hecl::Backend::GX::TexCoordGen& tcg = gx.m_tcgs[t];
      if (tcg.m_mtx == GX::IDENTITY)
        continue;
      if ((tcg.m_mtx - GX::TEXMTX0) / 3 == uvAnimsCount) {
        found = true;
        ++uvAnimsCount;
        uvAnims.emplace_back(tcg.m_gameFunction, tcg.m_gameArgs);
        XXH32_update(&xxHash, tcg.m_gameFunction.data(), sizeof(tcg.m_gameFunction.size()));
        for (const atVec4f& arg : tcg.m_gameArgs)
          XXH32_update(&xxHash, &arg, sizeof(arg));
        size_t tmpUvAnimsSize = uvAnimsSize;
        uvAnims.back().binarySize(tmpUvAnimsSize);
        uvAnimsSize = tmpUvAnimsSize;
        break;
      }
    }
    if (!found)
      break;
  }

  XXH32_update(&xxHash, &uvAnimsSize, sizeof(uvAnimsSize));
  XXH32_update(&xxHash, &uvAnimsCount, sizeof(uvAnimsCount));
#endif

  uniqueIdx = XXH32_digest(&xxHash);
}

HMDLMaterialSet::Material::Material(const hecl::blender::Material& mat) {
  auto search = mat.iprops.find("retro_depth_sort");
  if (search != mat.iprops.end())
    flags.setDepthSorting(search->second != 0);

  search = mat.iprops.find("retro_alpha_test");
  if (search != mat.iprops.end())
    flags.setAlphaTest(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection");
  if (search != mat.iprops.end())
    flags.setSamusReflection(search->second != 0);

  search = mat.iprops.find("retro_depth_write");
  if (search != mat.iprops.end())
    flags.setDepthWrite(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection_persp");
  if (search != mat.iprops.end())
    flags.setSamusReflectionSurfaceEye(search->second != 0);

  search = mat.iprops.find("retro_shadow_occluder");
  if (search != mat.iprops.end())
    flags.setShadowOccluderMesh(search->second != 0);

  search = mat.iprops.find("retro_samus_reflection_indirect");
  if (search != mat.iprops.end())
    flags.setSamusReflectionIndirectTexture(search->second != 0);

  search = mat.iprops.find("retro_lightmapped");
  if (search != mat.iprops.end())
    flags.setLightmap(search->second != 0);

  XXH64_state_t xxh;
  XXH64_reset(&xxh, 0);
  shaderType = mat.shaderType;
  XXH64_update(&xxh, &shaderType, sizeof(shaderType));
  chunkCount = 0;
  chunks.reserve(mat.chunks.size());
  for (const auto& chunk : mat.chunks) {
    chunk.visit([this, &xxh](const auto& var) {
      using T = std::decay_t<decltype(var)>;
      chunks.push_back(Chunk::Build(T::variant_type(), var));
      var.hash(&xxh);
      ++chunkCount;
    });
  }
  blendMode = mat.blendMode;
  XXH64_update(&xxh, &blendMode, sizeof(blendMode));
  int hashFlags = 0;
  if (flags.samusReflection())
    hashFlags |= 1;
  if (flags.samusReflectionIndirectTexture())
    hashFlags |= 2;
  if (flags.depthWrite())
    hashFlags |= 4;
  if (flags.alphaTest())
    hashFlags |= 8;
  XXH64_update(&xxh, &hashFlags, sizeof(hashFlags));
  hash = XXH64_digest(&xxh);
}

MaterialSet::Material::UVAnimation::UVAnimation(const std::string& gameFunction, const std::vector<atVec4f>& gameArgs) {
  if (gameFunction == "RetroUVMode0NodeN")
    mode = Mode::MvInvNoTranslation;
  else if (gameFunction == "RetroUVMode1NodeN")
    mode = Mode::MvInv;
  else if (gameFunction == "RetroUVMode2Node") {
    mode = Mode::Scroll;
    if (gameArgs.size() < 2)
      Log.report(logvisor::Fatal, FMT_STRING("Mode2 UV anim requires 2 vector arguments"));
    vals[0] = gameArgs[0].simd[0];
    vals[1] = gameArgs[0].simd[1];
    vals[2] = gameArgs[1].simd[0];
    vals[3] = gameArgs[1].simd[1];
  } else if (gameFunction == "RetroUVMode3Node") {
    mode = Mode::Rotation;
    if (gameArgs.size() < 2)
      Log.report(logvisor::Fatal, FMT_STRING("Mode3 UV anim requires 2 arguments"));
    vals[0] = gameArgs[0].simd[0];
    vals[1] = gameArgs[1].simd[0];
  } else if (gameFunction == "RetroUVMode4Node") {
    mode = Mode::HStrip;
    if (gameArgs.size() < 4)
      Log.report(logvisor::Fatal, FMT_STRING("Mode4 UV anim requires 4 arguments"));
    vals[0] = gameArgs[0].simd[0];
    vals[1] = gameArgs[1].simd[0];
    vals[2] = gameArgs[2].simd[0];
    vals[3] = gameArgs[3].simd[0];
  } else if (gameFunction == "RetroUVMode5Node") {
    mode = Mode::VStrip;
    if (gameArgs.size() < 4)
      Log.report(logvisor::Fatal, FMT_STRING("Mode5 UV anim requires 4 arguments"));
    vals[0] = gameArgs[0].simd[0];
    vals[1] = gameArgs[1].simd[0];
    vals[2] = gameArgs[2].simd[0];
    vals[3] = gameArgs[3].simd[0];
  } else if (gameFunction == "RetroUVMode6NodeN")
    mode = Mode::Model;
  else if (gameFunction == "RetroUVMode7NodeN") {
    mode = Mode::CylinderEnvironment;
    if (gameArgs.size() < 2)
      Log.report(logvisor::Fatal, FMT_STRING("Mode7 UV anim requires 2 arguments"));
    vals[0] = gameArgs[0].simd[0];
    vals[1] = gameArgs[1].simd[0];
  } else
    Log.report(logvisor::Fatal, FMT_STRING("unsupported UV anim '{}'"), gameFunction);
}

template <class Op>
void MaterialSet::Material::UVAnimation::Enumerate(typename Op::StreamT& s) {
  Do<Op>({}, mode, s);
  switch (mode) {
  case Mode::MvInvNoTranslation:
  case Mode::MvInv:
  case Mode::Model:
    break;
  case Mode::Scroll:
  case Mode::HStrip:
  case Mode::VStrip:
    for (int i = 0; i < 4; ++i)
      Do<Op>({}, vals[i], s);
    break;
  case Mode::Rotation:
  case Mode::CylinderEnvironment:
    for (int i = 0; i < 2; ++i)
      Do<Op>({}, vals[i], s);
    break;
  case Mode::Eight:
    for (int i = 0; i < 9; ++i)
      Do<Op>({}, vals[i], s);
    break;
  }
}

AT_SPECIALIZE_DNA(MaterialSet::Material::UVAnimation)

template <class Op>
void HMDLMaterialSet::Material::PASS::Enumerate(typename Op::StreamT& s) {
  Do<Op>(athena::io::PropId{"type"}, type, s);
  Do<Op>(athena::io::PropId{"texId"}, texId, s);
  Do<Op>(athena::io::PropId{"source"}, source, s);
  Do<Op>(athena::io::PropId{"uvAnimType"}, uvAnimType, s);
  size_t uvParmCount = uvAnimParamsCount();
  for (size_t i = 0; i < uvParmCount; ++i)
    Do<Op>({}, uvAnimParms[i], s);
  Do<Op>(athena::io::PropId{"alpha"}, alpha, s);
}

AT_SPECIALIZE_DNA(HMDLMaterialSet::Material::PASS)

std::string_view HMDLMaterialSet::Material::PASS::DNAType() {
  return "DataSpec::DNAMP1::HMDLMaterialSet::Material::PASS"sv;
}

} // namespace DataSpec::DNAMP1

namespace DataSpec::DNAMP2 {

void MaterialSet::ConstructMaterial(Stream& out, const MaterialSet::Material& material, unsigned groupIdx,
                                    unsigned matIdx) {
  DataSpec::DNAMP1::_ConstructMaterial(out, material, groupIdx, matIdx);
}

} // namespace DataSpec::DNAMP2
