#include "gs.h"

#include "common/util/Assert.h"

#include "third-party/fmt/core.h"

std::string reg_descriptor_name(GifTag::RegisterDescriptor reg) {
  switch (reg) {
    case GifTag::RegisterDescriptor::PRIM:
      return "PRIM";
    case GifTag::RegisterDescriptor::RGBAQ:
      return "RGBAQ";
    case GifTag::RegisterDescriptor::ST:
      return "ST";
    case GifTag::RegisterDescriptor::UV:
      return "UV";
    case GifTag::RegisterDescriptor::XYZF2:
      return "XYZF2";
    case GifTag::RegisterDescriptor::XYZ2:
      return "XYZ2";
    case GifTag::RegisterDescriptor::TEX0_1:
      return "TEX0_1";
    case GifTag::RegisterDescriptor::TEX0_2:
      return "TEX0_2";
    case GifTag::RegisterDescriptor::CLAMP_1:
      return "CLAMP_1";
    case GifTag::RegisterDescriptor::CLAMP_2:
      return "CLAMP_2";
    case GifTag::RegisterDescriptor::FOG:
      return "FOG";
    case GifTag::RegisterDescriptor::XYZF3:
      return "XYZF3";
    case GifTag::RegisterDescriptor::XYZ3:
      return "XYZ3";
    case GifTag::RegisterDescriptor::AD:
      return "A+D";
    case GifTag::RegisterDescriptor::NOP:
      return "NOP";

    default:
      ASSERT(false);
  }
}

std::string GifTag::print() const {
  std::string result;
  switch (flg()) {
    case Format::PACKED:
      result += "packed ";
      break;
    case Format::REGLIST:
      result += "reglist ";
      break;
    case Format::IMAGE:
      result += "image ";
      return result;
    case Format::DISABLE:
      result += "disable ";
      return result;
    default:
      ASSERT(false);
  }

  result += fmt::format("nloop: {} ", nloop());

  if (pre()) {
    result += fmt::format("prim: 0x{:x} ", prim());
  }

  if (eop()) {
    result += "eop ";
  }

  result += '\n';
  result += ' ';

  for (u32 i = 0; i < nreg(); i++) {
    result += reg_descriptor_name(reg(i));
    result += ' ';
  }
  result += "\n";
  return result;
}

std::string register_address_name(u32 reg) {
  return register_address_name(GsRegisterAddress(reg));
}

std::string register_address_name(GsRegisterAddress reg) {
  switch (reg) {
    case GsRegisterAddress::PRIM:
      return "PRIM";
    case GsRegisterAddress::RGBAQ:
      return "RGBAQ";
    case GsRegisterAddress::ST:
      return "ST";
    case GsRegisterAddress::UV:
      return "UV";
    case GsRegisterAddress::XYZF2:
      return "XYZF2";
    case GsRegisterAddress::XYZ2:
      return "XYZ2";
    case GsRegisterAddress::TEX0_1:
      return "TEX0_1";
    case GsRegisterAddress::TEX0_2:
      return "TEX0_2";
    case GsRegisterAddress::CLAMP_1:
      return "CLAMP_1";
    case GsRegisterAddress::CLAMP_2:
      return "CLAMP_2";
    case GsRegisterAddress::FOG:
      return "FOG";
    case GsRegisterAddress::XYZF3:
      return "XYZF3";
    case GsRegisterAddress::XYZ3:
      return "XYZ3";
    case GsRegisterAddress::TEX1_1:
      return "TEX1_1";
    case GsRegisterAddress::TEX1_2:
      return "TEX1_2";
    case GsRegisterAddress::TEX2_1:
      return "TEX2_1";
    case GsRegisterAddress::TEX2_2:
      return "TEX2_2";
    case GsRegisterAddress::XYOFFSET_1:
      return "XYOFFSET_1";
    case GsRegisterAddress::XYOFFSET_2:
      return "XYOFFSET_2";
    case GsRegisterAddress::PRMODECONT:
      return "PRMODECONT";
    case GsRegisterAddress::PRMODE:
      return "PRMODE";
    case GsRegisterAddress::TEXCLUT:
      return "TEXCLUT";
    case GsRegisterAddress::SCANMSK:
      return "SCANMSK";
    case GsRegisterAddress::MIPTBP1_1:
      return "MIPTBP1_1";
    case GsRegisterAddress::MIPTBP1_2:
      return "MIPTBP1_2";
    case GsRegisterAddress::MIPTBP2_1:
      return "MIPTBP2_1";
    case GsRegisterAddress::MIPTBP2_2:
      return "MIPTBP2_2";
    case GsRegisterAddress::TEXA:
      return "TEXA";
    case GsRegisterAddress::FOGCOL:
      return "FOGCOL";
    case GsRegisterAddress::TEXFLUSH:
      return "TEXFLUSH";
    case GsRegisterAddress::SCISSOR_1:
      return "SCISSOR_1";
    case GsRegisterAddress::SCISSOR_2:
      return "SCISSOR_2";
    case GsRegisterAddress::ALPHA_1:
      return "ALPHA_1";
    case GsRegisterAddress::ALPHA_2:
      return "ALPHA_2";
    case GsRegisterAddress::DIMX:
      return "DIMX";
    case GsRegisterAddress::DTHE:
      return "DTHE";
    case GsRegisterAddress::COLCLAMP:
      return "COLCLAMP";
    case GsRegisterAddress::TEST_1:
      return "TEST_1";
    case GsRegisterAddress::TEST_2:
      return "TEST_2";
    case GsRegisterAddress::PABE:
      return "PABE";
    case GsRegisterAddress::FBA_1:
      return "FBA_1";
    case GsRegisterAddress::FBA_2:
      return "FBA_2";
    case GsRegisterAddress::FRAME_1:
      return "FRAME_1";
    case GsRegisterAddress::FRAME_2:
      return "FRAME_2";
    case GsRegisterAddress::ZBUF_1:
      return "ZBUF_1";
    case GsRegisterAddress::ZBUF_2:
      return "ZBUF_2";
    case GsRegisterAddress::BITBLTBUF:
      return "BITBLTBUF";
    case GsRegisterAddress::TRXPOS:
      return "TRXPOS";
    case GsRegisterAddress::TRXREG:
      return "TRXREG";
    case GsRegisterAddress::TRXDIR:
      return "TRXDIR";
    case GsRegisterAddress::HWREG:
      return "HWREG";
    case GsRegisterAddress::SIGNAL:
      return "SIGNAL";
    case GsRegisterAddress::FINISH:
      return "FINISH";
    case GsRegisterAddress::LABEL:
      return "LABEL";
    default:
      ASSERT(false);
  }
}

std::string GsTest::print() const {
  std::string result;
  if (alpha_test_enable()) {
    result += "alpha-test: ";
    switch (alpha_test()) {
      case AlphaTest::NEVER:
        result += "NEVER ";
        break;
      case AlphaTest::ALWAYS:
        result += "ALWAYS ";
        break;
      case AlphaTest::LESS:
        result += "LESS ";
        break;
      case AlphaTest::LEQUAL:
        result += "LEQUAL ";
        break;
      case AlphaTest::EQUAL:
        result += "EQUAL ";
        break;
      case AlphaTest::GEQUAL:
        result += "GEQUAL ";
        break;
      case AlphaTest::GREATER:
        result += "GREATER ";
        break;
      case AlphaTest::NOTEQUAL:
        result += "NOTEQUAL ";
        break;
      default:
        ASSERT(false);
    }
    result += fmt::format("ref: 0x{:x} alpha-fail: ", aref());
    switch (afail()) {
      case AlphaFail::KEEP:
        result += "KEEP ";
        break;
      case AlphaFail::FB_ONLY:
        result += "FB_ONLY ";
        break;
      case AlphaFail::ZB_ONLY:
        result += "ZB_ONLY ";
        break;
      case AlphaFail::RGB_ONLY:
        result += "RGB_ONLY ";
        break;
      default:
        ASSERT(false);
    }
  }

  if (date()) {
    result += fmt::format("dest-alpha-mode {} ", (int)datm());
  }

  if (zte()) {
    result += fmt::format("ztest: ");
    switch (ztest()) {
      case ZTest::NEVER:
        result += "NEVER";
        break;
      case ZTest::ALWAYS:
        result += "ALWAYS";
        break;
      case ZTest::GEQUAL:
        result += "GEQUAL";
        break;
      case ZTest::GREATER:
        result += "GREATER";
        break;
      default:
        ASSERT(false);
    }
  }
  return result;
}

std::string GsAlpha::print() const {
  std::string result = "(";
  switch (a_mode()) {
    case BlendMode::SOURCE:
      result += "Cs ";
      break;
    case BlendMode::DEST:
      result += "Cd ";
      break;
    case BlendMode::ZERO_OR_FIXED:
      result += "0 ";
      break;
    default:
      ASSERT(false);
  }

  switch (b_mode()) {
    case BlendMode::SOURCE:
      result += "- Cs) * ";
      break;
    case BlendMode::DEST:
      result += "- Cd) * ";
      break;
    case BlendMode::ZERO_OR_FIXED:
      result += "- 0) * ";
      break;
    default:
      ASSERT(false);
  }

  switch (c_mode()) {
    case BlendMode::SOURCE:
      result += "As / 128.0";
      break;
    case BlendMode::DEST:
      result += "Ad / 128.0";
      break;
    case BlendMode::ZERO_OR_FIXED: {
      float div = fix();
      div /= 128.0;
      result += fmt::format("{:.4f}", div);
    } break;
    default:
      ASSERT(false);
  }

  switch (d_mode()) {
    case BlendMode::SOURCE:
      result += " + Cs";
      break;
    case BlendMode::DEST:
      result += " + Cd";
      break;
    case BlendMode::ZERO_OR_FIXED:
      break;
    default:
      ASSERT(false);
  }

  return result;
}

std::string GsTex1::print() const {
  return fmt::format("lcm: {} mxl: {} mmag: {} mmin: {} mtba: {} l: {} k: {}\n", lcm(), mxl(),
                     mmag(), mmin(), mtba(), l(), k());
}

std::string GsTexa::print() const {
  return fmt::format("ta0: {} aem: {} ta1: {}\n", ta0(), aem(), ta1());
}

std::string GsTex0::print() const {
  return fmt::format(
      "tbp0: {} tbw: {} psm: {} tw: {} th: {} tcc: {} tfx: {} cbp: {} cpsm: {} csm: {}\n", tbp0(),
      tbw(), psm(), tw(), th(), tcc(), tfx(), cbp(), cpsm(), csm());
}

std::string GsPrim::print() const {
  return fmt::format("0x{:x}, kind {}\n", data, kind());
}

std::string DrawMode::to_string() const {
  std::string result;
  result += fmt::format(" depth-write: {}\n", get_depth_write_enable());
  result += fmt::format(" depth-test: ");
  switch (get_depth_test()) {
    case GsTest::ZTest::NEVER:
      result += "never\n";
      break;
    case GsTest::ZTest::GEQUAL:
      result += "gequal\n";
      break;
    case GsTest::ZTest::ALWAYS:
      result += "always\n";
      break;
    case GsTest::ZTest::GREATER:
      result += "greater\n";
      break;
    default:
      ASSERT(false);
  }
  result += fmt::format(" alpha: ");
  switch (get_alpha_blend()) {
    case AlphaBlend::SRC_0_SRC_DST:
      result += "src, 0, src, dst\n";
      break;
    case AlphaBlend::SRC_DST_SRC_DST:
      result += "src, dst, src, dst\n";
      break;
    case AlphaBlend::DISABLED:
      result += "disabled\n";
      break;
    case AlphaBlend::SRC_DST_FIX_DST:
      result += "src, dst, fix, dst\n";
      break;
    case AlphaBlend::SRC_0_DST_DST:
      result += "src, 0, dst, dst\n";
      break;
    case AlphaBlend::SRC_SRC_SRC_SRC:
      result += "src, src, src, src\n";
      break;
    case AlphaBlend::ZERO_SRC_SRC_DST:
      result += "0, src, src, dst\n";
      break;
    case AlphaBlend::SRC_0_FIX_DST:
      result += "src, 0, fix, dst\n";
      break;
    default:
      ASSERT(false);
  }
  result += fmt::format(" clamp: s {} t {}\n", get_clamp_s_enable(), get_clamp_t_enable());
  result += fmt::format(" filt: {}\n", get_filt_enable());
  result += fmt::format(" tcc: {}\n", get_tcc_enable());
  result += fmt::format(" aref: {}\n", get_aref());
  result += fmt::format(" ate: {}\n", get_at_enable());
  result += fmt::format(" atst: ");
  switch (get_alpha_test()) {
    case AlphaTest::ALWAYS:
      result += "always\n";
      break;
    case AlphaTest::GEQUAL:
      result += "gequal\n";
      break;
    case AlphaTest::NEVER:
      result += "never\n";
      break;
    default:
      result += "invalid!\n";
      break;
  }
  result += fmt::format(" zte: {}\n", get_zt_enable());
  result += fmt::format(" abe: {}\n", get_ab_enable());
  result += fmt::format(" afail: ");
  switch (get_alpha_fail()) {
    case GsTest::AlphaFail::KEEP:
      result += "keep\n";
      break;
    case GsTest::AlphaFail::FB_ONLY:
      result += "fb-only\n";
      break;
    case GsTest::AlphaFail::RGB_ONLY:
      result += "rgb-only\n";
      break;
    case GsTest::AlphaFail::ZB_ONLY:
      result += "zb-only\n";
      break;
    default:
      ASSERT(false);
  }
  result += fmt::format(" fog: {}\n decal: {}\n", get_fog_enable(), get_decal());
  return result;
}