/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <inttypes.h>
#include <string.h>
#include <unistd.h>

#include <thrift/c_glib/thrift.h>
#include <thrift/c_glib/thrift_application_exception.h>

#include "thrift_test_handler.h"

/* A handler that implements the TTestThriftTestIf interface */

G_DEFINE_TYPE (ThriftTestHandler,
               thrift_test_handler,
               T_TEST_TYPE_THRIFT_TEST_HANDLER);

gboolean
thrift_test_handler_test_void (TTestThriftTestIf  *iface,
                               GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testVoid()\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_string (TTestThriftTestIf  *iface,
                                 gchar             **_return,
                                 const gchar        *thing,
                                 GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testString(\"%s\")\n", thing);
  *_return = g_strdup (thing);

  return TRUE;
}

gboolean
thrift_test_handler_test_byte (TTestThriftTestIf  *iface,
                               gint8              *_return,
                               const gint8         thing,
                               GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testByte(%d)\n", (gint)thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_i32 (TTestThriftTestIf  *iface,
                              gint32             *_return,
                              const gint32        thing,
                              GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testI32(%d)\n", thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_i64 (TTestThriftTestIf  *iface,
                              gint64             *_return,
                              const gint64        thing,
                              GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testI64(%" PRId64 ")\n", thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_double (TTestThriftTestIf  *iface,
                                 gdouble            *_return,
                                 const gdouble       thing,
                                 GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testDouble(%f)\n", thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_struct (TTestThriftTestIf  *iface,
                                 TTestXtruct       **_return,
                                 const TTestXtruct  *thing,
                                 GError            **error)
{
  gchar *string_thing = NULL;
  gint   byte_thing;
  gint   i32_thing;
  gint64 i64_thing;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  g_object_get ((TTestXtruct *)thing,
                "string_thing", &string_thing,
                "byte_thing",   &byte_thing,
                "i32_thing",    &i32_thing,
                "i64_thing",    &i64_thing,
                NULL);

  printf ("testStruct({\"%s\", %d, %d, %" PRId64 "})\n",
          string_thing,
          (gint)byte_thing,
          i32_thing,
          i64_thing);

  g_object_set (*_return,
                "string_thing", string_thing,
                "byte_thing",   byte_thing,
                "i32_thing",    i32_thing,
                "i64_thing",    i64_thing,
                NULL);

  if (string_thing != NULL)
    g_free (string_thing);

  return TRUE;
}

gboolean
thrift_test_handler_test_nest (TTestThriftTestIf   *iface,
                               TTestXtruct2       **_return,
                               const TTestXtruct2  *thing,
                               GError             **error)
{
  gchar *inner_string_thing = NULL;
  gint   byte_thing, inner_byte_thing;
  gint   i32_thing, inner_i32_thing;
  gint64 inner_i64_thing;
  TTestXtruct *struct_thing;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  g_object_get ((TTestXtruct2 *)thing,
                "byte_thing",   &byte_thing,
                "struct_thing", &struct_thing,
                "i32_thing",    &i32_thing,
                NULL);
  g_object_get (struct_thing,
                "string_thing", &inner_string_thing,
                "byte_thing",   &inner_byte_thing,
                "i32_thing",    &inner_i32_thing,
                "i64_thing",    &inner_i64_thing,
                NULL);

  printf ("testNest({%d, {\"%s\", %d, %d, %" PRId64 "}, %d})\n",
          byte_thing,
          inner_string_thing,
          inner_byte_thing,
          inner_i32_thing,
          inner_i64_thing,
          i32_thing);

  g_object_set (*_return,
                "byte_thing",   byte_thing,
                "struct_thing", struct_thing,
                "i32_thing",    i32_thing,
                NULL);

  if (inner_string_thing != NULL)
    g_free (inner_string_thing);
  g_object_unref (struct_thing);

  return TRUE;
}

gboolean
thrift_test_handler_test_map (TTestThriftTestIf  *iface,
                              GHashTable        **_return,
                              const GHashTable   *thing,
                              GError            **error)
{
  GHashTableIter hash_table_iter;
  gpointer key;
  gpointer value;
  gboolean first = TRUE;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testMap({");
  g_hash_table_iter_init (&hash_table_iter, (GHashTable *)thing);
  while (g_hash_table_iter_next (&hash_table_iter,
                                 &key,
                                 &value)) {
    gint32 *new_key;
    gint32 *new_value;

    if (first)
      first = FALSE;
    else
      printf (", ");

    printf ("%d => %d", *(gint32 *)key, *(gint32 *)value);

    new_key = g_malloc (sizeof *new_key);
    *new_key = *(gint32 *)key;
    new_value = g_malloc (sizeof *new_value);
    *new_value = *(gint32 *)value;
    g_hash_table_insert (*_return, new_key, new_value);
  }
  printf ("})\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_string_map (TTestThriftTestIf  *iface,
                                     GHashTable        **_return,
                                     const GHashTable   *thing,
                                     GError            **error)
{
  GHashTableIter hash_table_iter;
  gpointer key;
  gpointer value;
  gboolean first = TRUE;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testStringMap({");
  g_hash_table_iter_init (&hash_table_iter, (GHashTable *)thing);
  while (g_hash_table_iter_next (&hash_table_iter,
                                 &key,
                                 &value)) {
    gchar *new_key;
    gchar *new_value;

    if (first)
      first = FALSE;
    else
      printf (", ");

    printf ("%s => %s", (gchar *)key, (gchar *)value);

    new_key = g_strdup ((gchar *)key);
    new_value = g_strdup ((gchar *)value);
    g_hash_table_insert (*_return, new_key, new_value);
  }
  printf ("})\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_set (TTestThriftTestIf  *iface,
                              GHashTable        **_return,
                              const GHashTable   *thing,
                              GError            **error)
{
  GHashTableIter hash_table_iter;
  gpointer key;
  gboolean first = TRUE;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testSet({");
  g_hash_table_iter_init (&hash_table_iter, (GHashTable *)thing);
  while (g_hash_table_iter_next (&hash_table_iter,
                                 &key,
                                 NULL)) {
    gint32 *new_key;

    if (first)
      first = FALSE;
    else
      printf (", ");

    printf ("%d", *(gint32 *)key);

    new_key = g_malloc (sizeof *new_key);
    *new_key = *(gint32 *)key;
    g_hash_table_insert (*_return, new_key, NULL);
  }
  printf ("})\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_list (TTestThriftTestIf  *iface,
                               GArray            **_return,
                               const GArray       *thing,
                               GError            **error)
{
  guint i;
  gboolean first = TRUE;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testList({");
  for (i = 0; i < thing->len; i += 1) {
    gint32 value;
    gint32 *new_value;

    if (first)
      first = FALSE;
    else
      printf (", ");

    value = g_array_index (thing, gint32, i);
    printf ("%d", value);

    new_value = g_malloc (sizeof *new_value);
    *new_value = value;
    g_array_append_val (*_return, *new_value);
  }
  printf ("})\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_enum (TTestThriftTestIf   *iface,
                               TTestNumberz        *_return,
                               const TTestNumberz   thing,
                               GError             **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testEnum(%d)\n", thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_typedef (TTestThriftTestIf  *iface,
                                  TTestUserId        *_return,
                                  const TTestUserId   thing,
                                  GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testTypedef(%" PRId64 ")\n", thing);
  *_return = thing;

  return TRUE;
}

gboolean
thrift_test_handler_test_map_map (TTestThriftTestIf  *iface,
                                  GHashTable        **_return,
                                  const gint32        hello,
                                  GError            **error)
{
  GHashTable *positive;
  GHashTable *negative;
  gint32 *key;
  gint32 *value;
  guint i;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testMapMap(%d)\n", hello);

  positive = g_hash_table_new_full (g_int_hash,
                                    g_int_equal,
                                    g_free,
                                    g_free);
  negative = g_hash_table_new_full (g_int_hash,
                                    g_int_equal,
                                    g_free,
                                    g_free);

  for (i = 1; i < 5; i += 1) {
    key = g_malloc (sizeof *key);
    value = g_malloc (sizeof *value);
    *key = i;
    *value = i;
    g_hash_table_insert (positive, key, value);

    key = g_malloc (sizeof *key);
    value = g_malloc (sizeof *value);
    *key = -i;
    *value = -i;
    g_hash_table_insert (negative, key, value);
  }

  key = g_malloc (sizeof *key);
  *key = 4;
  g_hash_table_insert (*_return, key, positive);

  key = g_malloc (sizeof *key);
  *key = -4;
  g_hash_table_insert (*_return, key, negative);

  return TRUE;
}

gboolean
thrift_test_handler_test_insanity (TTestThriftTestIf    *iface,
                                   GHashTable          **_return,
                                   const TTestInsanity  *argument,
                                   GError              **error)
{
  TTestXtruct *hello;
  TTestXtruct *goodbye;
  TTestXtruct *xtruct_in;

  gchar *string_thing = NULL;
  gint   byte_thing;
  gint   i32_thing;
  gint64 i64_thing;

  GPtrArray *xtructs;

  TTestInsanity *crazy;
  TTestInsanity *looney;

  GHashTable *user_map;
  GHashTable *first_map;
  GHashTable *second_map;

  GHashTableIter hash_table_iter;
  GHashTableIter inner_hash_table_iter;
  GHashTableIter user_map_iter;

  gpointer key;
  gpointer value;

  TTestUserId *user_id;

  guint i;

  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (argument);
  THRIFT_UNUSED_VAR (error);

  printf ("testInsanity()\n");

  hello = g_object_new (T_TEST_TYPE_XTRUCT,
                        "string_thing", "Hello2",
                        "byte_thing",   2,
                        "i32_thing",    2,
                        "i64_thing",    2,
                        NULL);

  goodbye = g_object_new (T_TEST_TYPE_XTRUCT,
                          "string_thing", "Goodbye4",
                          "byte_thing",   4,
                          "i32_thing",    4,
                          "i64_thing",    4,
                          NULL);

  crazy = g_object_new (T_TEST_TYPE_INSANITY, NULL);
  g_object_get (crazy,
                "userMap", &user_map,
                "xtructs", &xtructs,
                NULL);

  user_id = g_malloc(sizeof *user_id);
  *user_id = 8;
  g_hash_table_insert (user_map,
                       GINT_TO_POINTER (T_TEST_NUMBERZ_EIGHT),
                       user_id);

  g_ptr_array_add (xtructs, goodbye);

  user_id = g_malloc(sizeof *user_id);
  *user_id = 5;
  g_hash_table_insert (user_map,
                       GINT_TO_POINTER (T_TEST_NUMBERZ_FIVE),
                       user_id);

  g_ptr_array_add (xtructs, hello);

  g_hash_table_unref (user_map);
  g_ptr_array_unref (xtructs);

  first_map = g_hash_table_new_full (g_direct_hash,
                                     g_direct_equal,
                                     NULL,
                                     g_object_unref);
  second_map = g_hash_table_new_full (g_direct_hash,
                                      g_direct_equal,
                                      NULL,
                                      g_object_unref);

  g_hash_table_insert (first_map,
                       GINT_TO_POINTER (T_TEST_NUMBERZ_TWO),
                       crazy);
  g_hash_table_insert (first_map,
                       GINT_TO_POINTER (T_TEST_NUMBERZ_THREE),
                       crazy);

  /* Increment crazy's ref count since first_map now holds two
     references to it and would otherwise attempt to deallocate it
     twice during destruction. We do this instead of creating a copy
     of crazy in order to mimic the C++ implementation (and since,
     frankly, the world needs less crazy, not more). */
  g_object_ref (crazy);

  looney = g_object_new (T_TEST_TYPE_INSANITY, NULL);
  g_hash_table_insert (second_map,
                       GINT_TO_POINTER (T_TEST_NUMBERZ_SIX),
                       looney);

  user_id = g_malloc (sizeof *user_id);
  *user_id = 1;
  g_hash_table_insert (*_return, user_id, first_map);

  user_id = g_malloc (sizeof *user_id);
  *user_id = 2;
  g_hash_table_insert (*_return, user_id, second_map);

  printf ("return");
  printf (" = {");
  g_hash_table_iter_init (&hash_table_iter, *_return);
  while (g_hash_table_iter_next (&hash_table_iter,
                                 &key,
                                 &value)) {
    printf ("%" PRId64 " => {", *(TTestUserId *)key);

    g_hash_table_iter_init (&inner_hash_table_iter,
                            (GHashTable *)value);
    while (g_hash_table_iter_next (&inner_hash_table_iter,
                                   &key,
                                   &value)) {
      printf ("%d => {", (TTestNumberz)key);

      g_object_get ((TTestInsanity *)value,
                    "userMap", &user_map,
                    "xtructs", &xtructs,
                    NULL);

      printf ("{");
      g_hash_table_iter_init (&user_map_iter, user_map);
      while (g_hash_table_iter_next (&user_map_iter,
                                     &key,
                                     &value)) {
        printf ("%d => %" PRId64 ", ",
                (TTestNumberz)key,
                *(TTestUserId *)value);
      }
      printf ("}, ");
      g_hash_table_unref (user_map);

      printf ("{");
      for (i = 0; i < xtructs->len; ++i) {
        xtruct_in = g_ptr_array_index (xtructs, i);
        g_object_get (xtruct_in,
                      "string_thing", &string_thing,
                      "byte_thing",   &byte_thing,
                      "i32_thing",    &i32_thing,
                      "i64_thing",    &i64_thing,
                      NULL);

        printf ("{\"%s\", %d, %d, %" PRId64 "}, ",
                string_thing,
                byte_thing,
                i32_thing,
                i64_thing);
      }
      printf ("}");
      g_ptr_array_unref (xtructs);

      printf ("}, ");
    }
    printf ("}, ");
  }
  printf ("}\n");

  return TRUE;
}

gboolean
thrift_test_handler_test_multi (TTestThriftTestIf   *iface,
                                TTestXtruct        **_return,
                                const gint8          arg0,
                                const gint32         arg1,
                                const gint64         arg2,
                                const GHashTable    *arg3,
                                const TTestNumberz   arg4,
                                const TTestUserId    arg5,
                                GError             **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);
  THRIFT_UNUSED_VAR (arg3);
  THRIFT_UNUSED_VAR (arg4);
  THRIFT_UNUSED_VAR (arg5);

  printf ("testMulti()\n");

  g_object_set (*_return,
                "string_thing", g_strdup ("Hello2"),
                "byte_thing",   arg0,
                "i32_thing",    arg1,
                "i64_thing",    arg2,
                NULL);

  return TRUE;
}

gboolean
thrift_test_handler_test_exception (TTestThriftTestIf  *iface,
                                    const gchar        *arg,
                                    TTestXception     **err1,
                                    GError            **error)
{
  THRIFT_UNUSED_VAR (iface);

  TTestXtruct *xtruct;
  gboolean result;

  printf ("testException(%s)\n", arg);

  /* Unlike argument objects, exception objects are not pre-created */
  g_assert (*err1 == NULL);

  if (strncmp (arg, "Xception", 9) == 0) {
    /* "Throw" a custom exception: Set the corresponding exception
       argument, set *error to NULL and return FALSE */
    *err1 = g_object_new (T_TEST_TYPE_XCEPTION,
                          "errorCode", 1001,
                          "message",   g_strdup (arg),
                          NULL);
    *error = NULL;
    result = FALSE;
  }
  else if (strncmp (arg, "TException", 11) == 0) {
    /* "Throw" a generic TException (ThriftApplicationException): Set
       all exception arguments to NULL, set *error and return FALSE */
    *err1 = NULL;
    g_set_error (error,
                 thrift_application_exception_error_quark (),
                 THRIFT_APPLICATION_EXCEPTION_ERROR_UNKNOWN,
                 "Default TException.");
    result = FALSE;
  }
  else {
    *err1 = NULL;
    *error = NULL;

    /* This code is duplicated from the C++ test suite, though it
       appears to serve no purpose */
    xtruct = g_object_new (T_TEST_TYPE_XTRUCT,
                           "string_thing", g_strdup (arg),
                           NULL);
    g_object_unref (xtruct);

    result = TRUE;
  }

  return result;
}

gboolean
thrift_test_handler_test_multi_exception (TTestThriftTestIf  *iface,
                                          TTestXtruct       **_return,
                                          const gchar        *arg0,
                                          const gchar        *arg1,
                                          TTestXception     **err1,
                                          TTestXception2    **err2,
                                          GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  TTestXtruct *struct_thing;
  gboolean result;

  printf ("testMultiException(%s, %s)\n", arg0, arg1);

  g_assert (*err1 == NULL);
  g_assert (*err2 == NULL);

  if (strncmp (arg0, "Xception", 9) == 0) {
    *err1 = g_object_new (T_TEST_TYPE_XCEPTION,
                          "errorCode", 1001,
                          "message",   g_strdup ("This is an Xception"),
                          NULL);
    result = FALSE;
  }
  else if (strncmp (arg0, "Xception2", 10) == 0) {
    *err2 = g_object_new (T_TEST_TYPE_XCEPTION2,
                          "errorCode", 2002);

    g_object_get (*err2,
                  "struct_thing", &struct_thing,
                  NULL);
    g_object_set (struct_thing,
                  "string_thing", g_strdup ("This is an Xception2"),
                  NULL);
    g_object_set (*err2,
                  "struct_thing", struct_thing,
                  NULL);
    g_object_unref (struct_thing);

    result = FALSE;
  }
  else {
    g_object_set (*_return,
                  "string_thing", g_strdup (arg1),
                  NULL);
    result = TRUE;
  }

  return result;
}

gboolean
thrift_test_handler_test_oneway (TTestThriftTestIf  *iface,
                                 const gint32        secondsToSleep,
                                 GError            **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("testOneway(%d): Sleeping...\n", secondsToSleep);
  sleep (secondsToSleep);
  printf ("testOneway(%d): done sleeping!\n", secondsToSleep);

  return TRUE;
}

static void
thrift_test_handler_init (ThriftTestHandler *self)
{
  THRIFT_UNUSED_VAR (self);
}

static void
thrift_test_handler_class_init (ThriftTestHandlerClass *klass)
{
  TTestThriftTestHandlerClass *base_class =
    T_TEST_THRIFT_TEST_HANDLER_CLASS (klass);

  base_class->test_void =
    klass->test_void =
    thrift_test_handler_test_void;
  base_class->test_string =
    klass->test_string =
    thrift_test_handler_test_string;
  base_class->test_byte =
    klass->test_byte =
    thrift_test_handler_test_byte;
  base_class->test_i32 =
    klass->test_i32 =
    thrift_test_handler_test_i32;
  base_class->test_i64 =
    klass->test_i64 =
    thrift_test_handler_test_i64;
  base_class->test_double =
    klass->test_double =
    thrift_test_handler_test_double;
  base_class->test_struct =
    klass->test_struct =
    thrift_test_handler_test_struct;
  base_class->test_nest =
    klass->test_nest =
    thrift_test_handler_test_nest;
  base_class->test_map =
    klass->test_map =
    thrift_test_handler_test_map;
  base_class->test_string_map =
    klass->test_string_map =
    thrift_test_handler_test_string_map;
  base_class->test_set =
    klass->test_set =
    thrift_test_handler_test_set;
  base_class->test_list =
    klass->test_list =
    thrift_test_handler_test_list;
  base_class->test_enum =
    klass->test_enum =
    thrift_test_handler_test_enum;
  base_class->test_typedef =
    klass->test_typedef =
    thrift_test_handler_test_typedef;
  base_class->test_map_map =
    klass->test_map_map =
    thrift_test_handler_test_map_map;
  base_class->test_insanity =
    klass->test_insanity =
    thrift_test_handler_test_insanity;
  base_class->test_multi =
    klass->test_multi =
    thrift_test_handler_test_multi;
  base_class->test_exception =
    klass->test_exception =
    thrift_test_handler_test_exception;
  base_class->test_multi_exception =
    klass->test_multi_exception =
    thrift_test_handler_test_multi_exception;
  base_class->test_oneway =
    klass->test_oneway =
    thrift_test_handler_test_oneway;
}
