#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <tuple>
#include <cstring>

#include "../archive/test.h"

#include "libipc/imp/fmt.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/span.h"
#include "libipc/imp/result.h"

TEST(fmt, spec) {
  EXPECT_STREQ(ipc::spec("hello")(123).fstr.data(), "hello");
  EXPECT_EQ(ipc::spec("hello")(123).param , 123);
  EXPECT_STREQ(ipc::spec("hello")("world").fstr.data(), "hello");
  EXPECT_STREQ(ipc::spec("hello")("world").param , "world");
}

TEST(fmt, to_string) {
  std::string joined;
  ipc::fmt_context ctx(joined);

  auto check = [&](auto &&txt, auto &&...val) {
    ctx.reset();
    EXPECT_TRUE(ipc::to_string(ctx, std::forward<decltype(val)>(val)...));
    ctx.finish();
    EXPECT_EQ(joined, std::forward<decltype(txt)>(txt));
  };

  /// \brief string
  check("", "");
  check("%what%", "%what%");
  check("    %what%", "%what%", "10");
  check("%what%    ", "%what%", "-10");

  /// \brief character
  check("A", 'A');
  check("A", L'A');
  check("A", u'A');
  check("A", U'A');

  /// \brief numeric
  check("123"       , (signed char)123      );
  check("-65"       , (signed char)-321     );
  check("123"       , (unsigned char)123    );
  check("65"        , (unsigned char)321    );
  check("123"       , (short)123            );
  check("-321"      , (short)-321           );
  check("123"       , (unsigned short)123   );
  check("321"       , (unsigned short)321   );
  check("-7949"     , (short)123123         );
  check("6359"      , (short)-321321        );
  check("57587"     , (unsigned short)123123);
  check("59177"     , (unsigned short)321321);
  check("123123"    , 123123                );
  check("-321321"   , -321321               );
  check("123123"    , 123123u               );
  check("321321"    , 321321u               );
  check("123123"    , 123123ll              );
  check("-321321"   , -321321ll             );
  check("123123"    , 123123ull             );
  check("321321"    , 321321ull             );
  check("1e0f3"     , 123123, "x"           );
  check("1e0f3"     , 123123u, "x"          );
  check("1CAAB5C3B3", 123123123123ll, "X"   );
  check("1CAAB5C3B3", 123123123123ull, "X"  );

  /// \brief floating point
  check("123.123"     , 123.123f, ".3");
  check("0123.12300"  , 123.123, "010.5");
  check("123.123000"  , 123.123l, "010.6");
  check("1.500000e+00", 1.5, "e");
  check("1.500000E+00", 1.5, "E");
  double r = 0.0;
  ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 0.0/r)); ctx.finish();
  std::cout << joined << "\n";
  ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 1.0/r)); ctx.finish();
  std::cout << joined << "\n";

  /// \brief pointer
  check("null", nullptr);
  int *p = (int *)0x0f013a04;
  ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, (void *)p)); ctx.finish();
  std::cout << joined << "\n";

  /// \brief date and time
  auto tp = std::chrono::system_clock::now();
  auto tt = std::chrono::system_clock::to_time_t(tp);
  auto tm = *std::localtime(&tt);
  ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tm)); ctx.finish();
  std::cout << joined << "\n";
  std::string tm_str = joined;
  ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tp)); ctx.finish();
  EXPECT_EQ(tm_str, joined);
}

TEST(fmt, fmt) {
  char const txt[] = "hello world.";

  /// \brief hello world
  auto s = ipc::fmt("hello", " ", "world", ".");
  EXPECT_EQ(s, txt);

  /// \brief chrono
  std::cout << ipc::fmt('[', std::chrono::system_clock::now(), "] ", s) << "\n";

  /// \brief long string
  s = ipc::fmt(ipc::spec("4096")(txt));
  std::string test(4096, ' ');
  std::memcpy(&test[test.size() - sizeof(txt) + 1], txt, sizeof(txt) - 1);
  EXPECT_EQ(s, test);

  EXPECT_EQ(ipc::fmt("", 1, "", '2', "", 3.0), "123.000000");
  char const * nc = nullptr;
  EXPECT_EQ(ipc::fmt(nc, 1, "", '2', "", 3.0), "123.000000");
  std::string empty;
  EXPECT_EQ(ipc::fmt(empty, 1, "", '2', "", 3.0), "123.000000");
}

namespace {

class foo {};

bool tag_invoke(decltype(ipc::fmt_to), ipc::fmt_context &, foo arg) noexcept(false) {
  throw arg;
  return {};
}

} // namespace

TEST(fmt, throw) {
  EXPECT_THROW(std::ignore = ipc::fmt(foo{}), foo);
}

TEST(fmt, byte) {
  {
    ipc::byte b1{}, b2(31);
    EXPECT_EQ(ipc::fmt(b1), "00");
    EXPECT_EQ(ipc::fmt(b2), "1f");
    EXPECT_EQ(ipc::fmt(ipc::spec("03X")(b2)), "01F");
  }
  {
    ipc::byte bs[] {31, 32, 33, 34, 35, 36, 37, 38};
    EXPECT_EQ(ipc::fmt(ipc::make_span(bs)), "1f 20 21 22 23 24 25 26");
  }
}

TEST(fmt, span) {
  EXPECT_EQ(ipc::fmt(ipc::span<int>{}), "");
  EXPECT_EQ(ipc::fmt(ipc::make_span({1, 3, 2, 4, 5, 6, 7})), "1 3 2 4 5 6 7");
}

TEST(fmt, result) {
  {
    ipc::result<std::uint64_t> r1;
    EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
    ipc::result<std::uint64_t> r2(65537);
    EXPECT_EQ(ipc::fmt(r2), "succ, value = 65537");
    ipc::result<std::uint64_t> r3(0);
    EXPECT_EQ(ipc::fmt(r3), "succ, value = 0");
  }
  {
    ipc::result<int> r0;
    EXPECT_EQ(ipc::fmt(r0), ipc::fmt(ipc::result<std::uint64_t>()));
    ipc::result<int> r1 {std::error_code(-1, std::generic_category())};
    EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));

    ipc::result<void *> r2 {&r1};
    EXPECT_EQ(ipc::fmt(r2), ipc::fmt("succ, value = ", (void *)&r1));

    int aaa {};
    ipc::result<int *> r3 {&aaa};
    EXPECT_EQ(ipc::fmt(r3), ipc::fmt("succ, value = ", (void *)&aaa));
    ipc::result<int *> r4 {nullptr};
    EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
    r4 = std::error_code(1234, std::generic_category());
    EXPECT_EQ(ipc::fmt(r4), ipc::fmt("fail, error = ", std::error_code(1234, std::generic_category())));
    ipc::result<int *> r5;
    EXPECT_EQ(ipc::fmt(r5), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
  }
  {
    ipc::result<std::int64_t> r1 {-123};
    EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, value = ", -123));
  }
  {
    ipc::result<void> r1;
    EXPECT_EQ(ipc::fmt(r1), ipc::fmt("fail, error = ", std::error_code(-1, std::generic_category())));
    r1 = std::error_code{};
    EXPECT_TRUE(r1);
    EXPECT_EQ(ipc::fmt(r1), ipc::fmt("succ, error = ", std::error_code()));
  }
}

/// \brief Test case for issue #171: Compiler failures under MinGW
/// \see https://github.com/mutouyun/cpp-ipc/issues/171
/// 
/// The issue is that std::hex and std::dec (I/O manipulators) were incorrectly
/// used with ipc::fmt. These are function pointers of type:
///   std::ios_base& (*)(std::ios_base&)
/// which ipc::fmt does not support.
/// 
/// The correct way to output hexadecimal values with ipc::fmt is to use ipc::spec.
TEST(fmt, hex_output_with_spec) {
  /// \brief Basic hexadecimal formatting using ipc::spec
  /// This is the correct way to format hex values (instead of std::hex)
  {
    unsigned int val = 255;
    EXPECT_EQ(ipc::fmt(ipc::spec("x")(val)), "ff");
    EXPECT_EQ(ipc::fmt(ipc::spec("X")(val)), "FF");
    EXPECT_EQ(ipc::fmt(ipc::spec("08x")(val)), "000000ff");
    EXPECT_EQ(ipc::fmt(ipc::spec("08X")(val)), "000000FF");
  }

  /// \brief Hex formatting with prefix (simulating "0x" prefix like std::hex would produce)
  {
    unsigned int val = 0xDEADBEEF;
    // Correct way: use string concatenation with ipc::spec for hex format
    EXPECT_EQ(ipc::fmt("0x", ipc::spec("x")(val)), "0xdeadbeef");
    EXPECT_EQ(ipc::fmt("0x", ipc::spec("X")(val)), "0xDEADBEEF");
    EXPECT_EQ(ipc::fmt("0x", ipc::spec("08x")(val)), "0xdeadbeef");
  }

  /// \brief Mixed decimal and hex output (simulating the problematic log pattern from issue #171)
  /// Original problematic code pattern was:
  ///   log.error("fail WaitForSingleObject[", ::GetLastError(), "]: 0x", std::hex, ret, std::dec);
  /// Correct pattern should be:
  ///   log.error("fail WaitForSingleObject[", ::GetLastError(), "]: ", ipc::spec("08x")(ret));
  {
    unsigned long error_code = 5;  // ERROR_ACCESS_DENIED
    unsigned int ret = 0x00000102; // WAIT_TIMEOUT value
    
    // Correct way to format the log message
    auto msg = ipc::fmt("fail WaitForSingleObject[", error_code, "]: ", ipc::spec("08x")(ret));
    EXPECT_EQ(msg, "fail WaitForSingleObject[5]: 00000102");
    
    // Alternative with "0x" prefix
    auto msg2 = ipc::fmt("fail WaitForSingleObject[", error_code, "]: 0x", ipc::spec("x")(ret));
    EXPECT_EQ(msg2, "fail WaitForSingleObject[5]: 0x102");
  }

  /// \brief Various integer types with hex formatting
  /// Note: ipc::spec format string should NOT include length modifiers (like "ll").
  /// The to_string function automatically adds the correct length modifier based on
  /// the argument type. Just use "x" or "X" for hex conversion.
  {
    EXPECT_EQ(ipc::fmt(ipc::spec("x")((unsigned char)0xAB)), "ab");
    EXPECT_EQ(ipc::fmt(ipc::spec("x")((unsigned short)0xABCD)), "abcd");
    EXPECT_EQ(ipc::fmt(ipc::spec("x")((unsigned int)0xABCDEF01)), "abcdef01");
    EXPECT_EQ(ipc::fmt(ipc::spec("x")((unsigned long)0xABCDEF01)), "abcdef01");
    // For unsigned long long, just use "x" - the length modifier is added automatically
    EXPECT_EQ(ipc::fmt(ipc::spec("x")((unsigned long long)0xABCDEF0123456789ULL)), "abcdef0123456789");
  }

  /// \brief Width and padding with hex
  {
    unsigned int val = 0x1F;
    EXPECT_EQ(ipc::fmt(ipc::spec("2x")(val)), "1f");
    EXPECT_EQ(ipc::fmt(ipc::spec("4x")(val)), "  1f");
    EXPECT_EQ(ipc::fmt(ipc::spec("04x")(val)), "001f");
    EXPECT_EQ(ipc::fmt(ipc::spec("8x")(val)), "      1f");
    EXPECT_EQ(ipc::fmt(ipc::spec("08x")(val)), "0000001f");
  }
}
