// Protocol Buffers - Google's data interchange format
// Copyright 2025 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "upb/mini_table/debug_string.h"

#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#include "upb/base/descriptor_constants.h"
#include "upb/hash/common.h"
#include "upb/hash/int_table.h"
#include "upb/mem/arena.h"
#include "upb/mini_table/enum.h"
#include "upb/mini_table/field.h"
#include "upb/mini_table/internal/field.h"
#include "upb/mini_table/internal/message.h"
#include "upb/mini_table/message.h"
#include "upb/port/vsnprintf_compat.h"

// Must be last.
#include "upb/port/def.inc"

typedef struct {
  char* buf;
  char* ptr;
  char* end;
  int overflow;
  upb_Arena* arena;
  int count;

  // This table maps from a pointer to a 64-bit integer. The lower 32 bits are
  // a unique ID for the object. The upper 32 bits are a flag that is 0x1
  // if the object has already been printed.
  upb_inttable inttable;
} upb_MiniTablePrinter;

UPB_PRINTF(2, 3)
static void upb_MiniTablePrinter_Printf(upb_MiniTablePrinter* p,
                                        const char* fmt, ...) {
  size_t n;
  size_t have = p->end - p->ptr;
  va_list args;

  va_start(args, fmt);
  n = _upb_vsnprintf(p->ptr, have, fmt, args);
  va_end(args);

  if (UPB_LIKELY(have > n)) {
    p->ptr += n;
  } else {
    p->ptr = UPB_PTRADD(p->ptr, have);
    p->overflow += (n - have);
  }
}

static size_t upb_MiniTablePrinter_NullTerminate(upb_MiniTablePrinter* p,
                                                 size_t size) {
  size_t ret = p->ptr - p->buf + p->overflow;

  if (size > 0) {
    if (p->ptr == p->end) p->ptr--;
    *p->ptr = '\0';
  }

  return ret;
}

static int upb_MiniTablePrinter_InsertNext(upb_MiniTablePrinter* p,
                                           const void* key, bool visited) {
  uint64_t id = p->count++;
  upb_inttable_insert(&p->inttable, (intptr_t)key,
                      upb_value_uint64(id | (visited ? 0x100000000 : 0)),
                      p->arena);
  return id;
}

// Returns the ID of the object referenced by key, but does *not* mark the
// object as visited. This is used for printing a reference to another object
// that may or may not have been printed yet.
static int upb_MiniTablePrinter_GetIdForRef(upb_MiniTablePrinter* p,
                                            const void* key) {
  upb_value v;
  if (upb_inttable_lookup(&p->inttable, (intptr_t)key, &v)) {
    return (int)upb_value_getuint64(v);
  }
  return upb_MiniTablePrinter_InsertNext(p, key, false);
}

// Returns the ID of the object referenced by key, and marks the object as
// visited. This is used for printing the object itself.
static int upb_MiniTablePrinter_GetIdForEmit(upb_MiniTablePrinter* p,
                                             const void* key) {
  UPB_ASSERT(key);
  upb_value v;
  if (upb_inttable_lookup(&p->inttable, (intptr_t)key, &v)) {
    uint64_t val = upb_value_getuint64(v);
    if (val >> 32) return -1;
    upb_inttable_replace(&p->inttable, (intptr_t)key,
                         upb_value_int64(val | 0x100000000));
    return (int)val;
  }
  return upb_MiniTablePrinter_InsertNext(p, key, true);
}

static void upb_MiniTablePrinter_PrintEnum(upb_MiniTablePrinter* p,
                                           const upb_MiniTableEnum* enum_) {
  int id = upb_MiniTablePrinter_GetIdForEmit(p, enum_);
  if (id < 0) return;

  upb_MiniTablePrinter_Printf(p, "MiniTableEnum#%d {\n", id);
  upb_MiniTablePrinter_Printf(p, "  .mask_limit = %d\n",
                              enum_->UPB_PRIVATE(mask_limit));
  upb_MiniTablePrinter_Printf(p, "  .value_count = %d\n",
                              enum_->UPB_PRIVATE(value_count));
  upb_MiniTablePrinter_Printf(p, "  .values = {\n");

  for (uint32_t i = 0; i < enum_->UPB_PRIVATE(mask_limit); i++) {
    if (!upb_MiniTableEnum_CheckValue(enum_, i)) continue;
    upb_MiniTablePrinter_Printf(p, "    %d,\n", (int)i);
  }

  const uint32_t* start =
      &enum_->UPB_PRIVATE(data)[enum_->UPB_PRIVATE(mask_limit) / 32];
  for (uint32_t i = 0; i < enum_->UPB_PRIVATE(value_count); i++) {
    upb_MiniTablePrinter_Printf(p, "    %d,\n", (int)start[i]);
  }

  upb_MiniTablePrinter_Printf(p, "  }\n");
  upb_MiniTablePrinter_Printf(p, "}\n\n");
}

static void upb_MiniTablePrinter_PrintField(upb_MiniTablePrinter* p,
                                            const upb_MiniTable* mini_table,
                                            const upb_MiniTableField* field) {
  upb_MiniTablePrinter_Printf(p, "    MiniTableField {\n");
  upb_MiniTablePrinter_Printf(p, "      .number = %d\n",
                              field->UPB_PRIVATE(number));
  upb_MiniTablePrinter_Printf(p, "      .offset = %d\n",
                              field->UPB_PRIVATE(offset));
  upb_MiniTablePrinter_Printf(p, "      .presence = %d", field->presence);

  if (field->presence > 0) {
    upb_MiniTablePrinter_Printf(p, " (hasbit=%d)\n", field->presence);
  } else if (field->presence < 0) {
    upb_MiniTablePrinter_Printf(p, " (oneof_index=%d)\n", ~field->presence);
  } else {
    upb_MiniTablePrinter_Printf(p, " (no explicit presence)\n");
  }

  if (field->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) {
    upb_MiniTablePrinter_Printf(p, "      .submsg_ofs = %d\n",
                                field->UPB_PRIVATE(submsg_ofs));
  }
  upb_MiniTablePrinter_Printf(p, "      .type = %d\n",
                              field->UPB_PRIVATE(descriptortype));
  upb_MiniTablePrinter_Printf(p, "      .mode = %02x (",
                              field->UPB_PRIVATE(mode));

  switch (field->UPB_PRIVATE(mode) & kUpb_FieldMode_Mask) {
    case kUpb_FieldMode_Scalar:
      upb_MiniTablePrinter_Printf(p, "Scalar");
      break;
    case kUpb_FieldMode_Array:
      upb_MiniTablePrinter_Printf(p, "Array");
      break;
    case kUpb_FieldMode_Map:
      upb_MiniTablePrinter_Printf(p, "Map");
      break;
  }

  switch (field->UPB_PRIVATE(mode) >> kUpb_FieldRep_Shift) {
    case kUpb_FieldRep_1Byte:
      upb_MiniTablePrinter_Printf(p, " | 1Byte");
      break;
    case kUpb_FieldRep_4Byte:
      upb_MiniTablePrinter_Printf(p, " | 4Byte");
      break;
    case kUpb_FieldRep_8Byte:
      upb_MiniTablePrinter_Printf(p, " | 8Byte");
      break;
    case kUpb_FieldRep_StringView:
      upb_MiniTablePrinter_Printf(p, " | StringView");
      break;
  }

  if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsPacked) {
    upb_MiniTablePrinter_Printf(p, " | Packed");
  }
  if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsExtension) {
    upb_MiniTablePrinter_Printf(p, " | Extension");
  }
  if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsAlternate) {
    upb_MiniTablePrinter_Printf(p, " | Alternate");
  }

  upb_MiniTablePrinter_Printf(p, ")\n");

  if (field->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) {
    if (upb_MiniTableField_CType(field) == kUpb_CType_Message) {
      int id =
          upb_MiniTablePrinter_GetIdForRef(p, upb_MiniTable_SubMessage(field));
      upb_MiniTablePrinter_Printf(p, "      .submsg = MiniTable#%d\n", id);
    } else {
      int id = upb_MiniTablePrinter_GetIdForRef(
          p, upb_MiniTable_GetSubEnumTable(field));
      upb_MiniTablePrinter_Printf(p, "      .subenum = MiniTableEnum#%d\n", id);
    }
  }

  upb_MiniTablePrinter_Printf(p, "    },\n");
}

static void upb_MiniTablePrinter_PrintMessage(upb_MiniTablePrinter* p,
                                              const upb_MiniTable* mini_table) {
  int id = upb_MiniTablePrinter_GetIdForEmit(p, mini_table);
  if (id < 0) return;

  upb_MiniTablePrinter_Printf(p, "MiniTable#%d {\n", id);
  upb_MiniTablePrinter_Printf(p, "  .size = %d\n",
                              mini_table->UPB_PRIVATE(size));
  upb_MiniTablePrinter_Printf(p, "  .required_count = %d\n",
                              mini_table->UPB_PRIVATE(required_count));
  upb_MiniTablePrinter_Printf(p, "  .table_mask = %02x\n",
                              mini_table->UPB_PRIVATE(table_mask));
  upb_MiniTablePrinter_Printf(p, "  .dense_below = %d\n",
                              mini_table->UPB_PRIVATE(dense_below));

  upb_MiniTablePrinter_Printf(p, "  .ext = %02x (",
                              mini_table->UPB_PRIVATE(ext));
  switch (mini_table->UPB_PRIVATE(ext) & 3) {
    case kUpb_ExtMode_NonExtendable:
      upb_MiniTablePrinter_Printf(p, "NonExtendable");
      break;
    case kUpb_ExtMode_Extendable:
      upb_MiniTablePrinter_Printf(p, "Extendable");
      break;
    case kUpb_ExtMode_IsMessageSet:
      upb_MiniTablePrinter_Printf(p, "MessageSet");
      break;
    case kUpb_ExtMode_IsMessageSet_ITEM:
      upb_MiniTablePrinter_Printf(p, "MessageSetItem");
      break;
  }
  if (mini_table->UPB_PRIVATE(ext) & kUpb_ExtMode_IsMapEntry) {
    upb_MiniTablePrinter_Printf(p, " | MapEntry");
  }
  upb_MiniTablePrinter_Printf(p, ")\n");
  upb_MiniTablePrinter_Printf(p, "  .fields[%d] = {\n",
                              mini_table->UPB_PRIVATE(field_count));

  for (int i = 0; i < mini_table->UPB_PRIVATE(field_count); i++) {
    const upb_MiniTableField* field = &mini_table->UPB_PRIVATE(fields)[i];
    upb_MiniTablePrinter_PrintField(p, mini_table, field);
  }

  upb_MiniTablePrinter_Printf(p, "  }\n");

  int mask = (int8_t)mini_table->UPB_PRIVATE(table_mask);
  if (mask != -1) {
    int size = (mask >> 3) + 1;
    upb_MiniTablePrinter_Printf(p, "  .fasttable[%d] = {\n", size);

    for (int i = 0; i < size; i++) {
      const _upb_FastTable_Entry* entry =
          &mini_table->UPB_PRIVATE(fasttable)[i];
      upb_MiniTablePrinter_Printf(p, "    FastTableEntry {\n");
      upb_MiniTablePrinter_Printf(p, "      .field_data = %016" PRIx64 ",\n",
                                  entry->field_data);
      upb_MiniTablePrinter_Printf(p, "      .field_parser = %p\n",
                                  entry->field_parser);
      upb_MiniTablePrinter_Printf(p, "      .field_number = %d\n",
                                  (((int)entry->field_data >> 3) & 0xf) |
                                      (((int)entry->field_data >> 4) & 0x7f0));
      upb_MiniTablePrinter_Printf(p, "    }\n");
    }

    upb_MiniTablePrinter_Printf(p, "  }\n");
  }

  upb_MiniTablePrinter_Printf(p, "}\n\n");

  for (int i = 0; i < mini_table->UPB_PRIVATE(field_count); i++) {
    const upb_MiniTableField* field = &mini_table->UPB_PRIVATE(fields)[i];
    if (field->UPB_PRIVATE(submsg_ofs) == kUpb_NoSub) continue;
    if (upb_MiniTableField_CType(field) == kUpb_CType_Message) {
      upb_MiniTablePrinter_PrintMessage(p, upb_MiniTable_SubMessage(field));
    } else {
      upb_MiniTablePrinter_PrintEnum(p, upb_MiniTable_GetSubEnumTable(field));
    }
  }
}

size_t upb_MiniTable_DebugString(const upb_MiniTable* mini_table, char* buf,
                                 size_t size) {
  upb_MiniTablePrinter p = {buf, buf, buf + size, 0, upb_Arena_New(), 0};

  if (!p.arena) return 0;
  if (!upb_inttable_init(&p.inttable, p.arena)) return 0;

  upb_MiniTablePrinter_PrintMessage(&p, mini_table);

  upb_Arena_Free(p.arena);

  return upb_MiniTablePrinter_NullTerminate(&p, size);
}
