// Round-trip regression test for the text-encoding fixes in id3lib
// (Debian #213239 / UTF-8 and the companion UTF-16BE handling).
//
// For each text encoding we build an ID3v2 tag carrying a TIT2 frame, render
// it to an in-memory buffer and parse it back, then check that the stored
// text survived unchanged.  This exercises the patched ParseText()/RenderText()
// dispatch and the single-/double-byte accessors that the encoding patches
// touch.  One PASS/FAIL line is printed per case; the program exits non-zero on
// any failure, so it is usable both as an autopkgtest and by hand.
#include <id3/tag.h>
#include <id3/field.h>
#include <cstdio>
#include <string>
#include <vector>

static int failures = 0;

static void report(const char* name, bool ok, size_t rendered, size_t est)
{
  std::printf("%s: %-22s (rendered %zu bytes, Size()=%zu)\n",
              ok ? "PASS" : "FAIL", name, rendered, est);
  if (!ok) ++failures;
}

static size_t ucslen(const unicode_t* s) { size_t n = 0; while (s[n]) ++n; return n; }

// Build a v2 tag with one TIT2 frame in 'enc', render it and parse it back into
// 'out'.  Returns the parsed TITLE text field (or NULL).  A deliberately large
// buffer is used: ID3_Tag::Render() does no bounds checking, and Size() is only
// an estimate, so we must not rely on it as a hard bound.
static ID3_Field* roundtrip(ID3_TextEnc enc, void (*set)(ID3_Field*),
                            ID3_Tag& out, size_t& rendered, size_t& est)
{
  ID3_Tag in;
  in.SetPadding(false);
  in.SetUnsync(false);

  ID3_Frame frame(ID3FID_TITLE);
  // Set the encoding on the TEXT field itself before storing the data: the
  // double-byte Set() only stores when the field's own encoding is already a
  // double-byte encoding, and the single-byte path likewise keys off the
  // field encoding (this mirrors real callers, e.g. Debian #213239:
  // field.SetEncoding(enc); field.Set(...)).  Also set the ID3FN_TEXTENC field
  // so the rendered frame carries the matching encoding byte.
  frame.GetField(ID3FN_TEXTENC)->Set(enc);
  ID3_Field* tf = frame.GetField(ID3FN_TEXT);
  tf->SetEncoding(enc);
  set(tf);
  in.AddFrame(frame); // copy semantics, as in the upstream examples

  est = in.Size();
  std::vector<unsigned char> buf(65536, 0);
  rendered = in.Render(&buf[0], ID3TT_ID3V2);
  if (rendered == 0 || rendered >= buf.size()) return NULL;

  out.Parse(&buf[0], rendered);
  ID3_Frame* f = out.Find(ID3FID_TITLE);
  return f ? f->GetField(ID3FN_TEXT) : NULL;
}

// --- single-byte cases (ISO-8859-1, UTF-8) ----------------------------------
static std::string g_single;
static void setSingle(ID3_Field* f) { f->Set(g_single.c_str()); }

static void testSingle(const char* name, ID3_TextEnc enc, const std::string& s)
{
  g_single = s;
  ID3_Tag out; size_t rendered = 0, est = 0;
  ID3_Field* f = roundtrip(enc, setSingle, out, rendered, est);
  bool ok = false;
  if (f)
  {
    const char* got = f->GetRawText();
    ok = got && g_single == std::string(got);
  }
  report(name, ok, rendered, est);
}

// --- double-byte cases (UTF-16, UTF-16BE) -----------------------------------
static const unicode_t* g_uni = NULL;
static void setUni(ID3_Field* f) { f->Set(g_uni); }

static void testUnicode(const char* name, ID3_TextEnc enc, const unicode_t* s)
{
  g_uni = s;
  ID3_Tag out; size_t rendered = 0, est = 0;
  ID3_Field* f = roundtrip(enc, setUni, out, rendered, est);
  bool ok = false;
  if (f)
  {
    const unicode_t* got = f->GetRawUnicodeText();
    size_t want = ucslen(s);
    if (got && ucslen(got) == want)
    {
      ok = true;
      for (size_t i = 0; i < want; ++i)
        if (got[i] != s[i]) { ok = false; break; }
    }
  }
  report(name, ok, rendered, est);
}

// --- Size() must be a safe upper bound for Render() (heap-overflow guard) ----
// The documented idiom is "uchar* b = new uchar[tag.Size()]; tag.Render(b);"
// and ID3_Tag::Render() does not bounds-check the buffer, so Render() must
// never write more than Size() reports.  We render into a deliberately large
// buffer (so a regression is detected, not crashed on) and compare.
static void testSizeBound(const char* name, size_t titleLen, bool padding)
{
  ID3_Tag t;
  t.SetPadding(padding);
  ID3_Frame frame(ID3FID_TITLE);
  frame.GetField(ID3FN_TEXTENC)->Set(ID3TE_ISO8859_1);
  ID3_Field* tf = frame.GetField(ID3FN_TEXT);
  tf->SetEncoding(ID3TE_ISO8859_1);
  tf->Set(std::string(titleLen, 'X').c_str());
  t.AddFrame(frame);

  size_t advertised = t.Size();
  std::vector<unsigned char> buf(advertised + 1024 * 1024, 0); // generous head-room
  size_t written = t.Render(&buf[0], ID3TT_ID3V2);

  bool ok = (written <= advertised);
  std::printf("%s: %-22s (Size()=%zu, Render wrote %zu)\n",
              ok ? "PASS" : "FAIL", name, advertised, written);
  if (!ok) ++failures;
}

int main()
{
  // "ABc" plus the non-ASCII code point U+00E9 (e-acute).
  // ISO-8859-1 byte for U+00E9 is 0xE9; the UTF-8 sequence is 0xC3 0xA9.
  const char iso[]  = { 'A', 'B', 'c', (char)0xE9, 0 };
  const char utf8[] = { 'A', 'B', 'c', (char)0xC3, (char)0xA9, 0 };
  const unicode_t uni[] = { 0x0041, 0x0042, 0x0063, 0x00E9, 0x0000 };

  testSingle ("ISO-8859-1 round-trip", ID3TE_ISO8859_1, std::string(iso, 4));
  testSingle ("UTF-8 round-trip",      ID3TE_UTF8,      std::string(utf8, 5));
  testUnicode("UTF-16 round-trip",     ID3TE_UTF16,     uni);
  testUnicode("UTF-16BE round-trip",   ID3TE_UTF16BE,   uni);

  testSizeBound("Size() bound (small, pad on)",   3,    true);
  testSizeBound("Size() bound (small, pad off)",  3,    false);
  testSizeBound("Size() bound (large, pad on)",   5000, true);
  testSizeBound("Size() bound (large, pad off)",  5000, false);

  std::printf("\n%d failure(s)\n", failures);
  return failures ? 1 : 0;
}
