/*
 * Copyright (C) 2008 Red Hat, Inc.
 * Copyright (C) 2018 Tomasz Miąsko
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * The idea here is that we want to compare offset information two ways:
 *
 *  1) As generated by the compiler
 *  2) As found in the typelib
 *
 * So we write the field offsets using G_STRUCT_OFFSET() to one file and the
 * field offsets using the typelib to the another file. We can then diff the
 * two files to see if they are the same.
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <girepository.h>
#include "offsets.h"

static GIRepository *repository;
static const char *namespace = "Offsets";
static const char *version = "1.0";

static void
introspected_struct (FILE *outfile, const gchar *name)
{
  gint i, n_fields;
  GIStructInfo *struct_info;

  struct_info = g_irepository_find_by_name(repository, namespace, name);
  if (!struct_info)
     g_error ("Can't find GIStructInfo for '%s'", name);

  g_fprintf (outfile, "%s%s: size=%" G_GSIZE_FORMAT ", alignment=%" G_GSIZE_FORMAT "\n",
             namespace, name,
             g_struct_info_get_size (struct_info),
             g_struct_info_get_alignment (struct_info));

   n_fields = g_struct_info_get_n_fields (struct_info);
   for (i = 0; i != n_fields; ++i)
     {
       GIFieldInfo *field_info;

       field_info = g_struct_info_get_field (struct_info, i);

       g_fprintf (outfile, "%s %d\n",
                  g_base_info_get_name ((GIBaseInfo *) field_info),
                  g_field_info_get_offset (field_info));

       g_base_info_unref ((GIBaseInfo *)field_info);
     }

  g_fprintf (outfile, "\n");

  g_base_info_unref ((GIBaseInfo *)struct_info);
}

static void
compiled (FILE *outfile)
{
#define ALIGNOF(type) G_STRUCT_OFFSET(struct {char a; type b;}, b)
#define PRINT_TYPE(type) g_fprintf (outfile, \
  "%s: size=%" G_GSIZE_FORMAT ", alignment=%ld\n", \
  #type, sizeof (type), ALIGNOF (type))
#define PRINT_MEMBER(type, member) g_fprintf (outfile, \
  "%s %ld\n", \
  #member, G_STRUCT_OFFSET(type, member))

  PRINT_TYPE (OffsetsArray);
  PRINT_MEMBER (OffsetsArray, some_ints);
  PRINT_MEMBER (OffsetsArray, some_int8s);
  PRINT_MEMBER (OffsetsArray, some_doubles);
  PRINT_MEMBER (OffsetsArray, some_enum);
  PRINT_MEMBER (OffsetsArray, some_ptrs);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsMultiDimArray);
  PRINT_MEMBER (OffsetsMultiDimArray, ints);
  PRINT_MEMBER (OffsetsMultiDimArray, chars);
  PRINT_MEMBER (OffsetsMultiDimArray, floats);
  PRINT_MEMBER (OffsetsMultiDimArray, pointers1);
  PRINT_MEMBER (OffsetsMultiDimArray, pointers2);
  PRINT_MEMBER (OffsetsMultiDimArray, pointers3);
  PRINT_MEMBER (OffsetsMultiDimArray, dummy);
  fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsBasic);
  PRINT_MEMBER (OffsetsBasic, dummy1);
  PRINT_MEMBER (OffsetsBasic, field_int8);
  PRINT_MEMBER (OffsetsBasic, dummy2);
  PRINT_MEMBER (OffsetsBasic, field_int16);
  PRINT_MEMBER (OffsetsBasic, dummy3);
  PRINT_MEMBER (OffsetsBasic, field_int32);
  PRINT_MEMBER (OffsetsBasic, dummy4);
  PRINT_MEMBER (OffsetsBasic, field_int64);
  PRINT_MEMBER (OffsetsBasic, dummy5);
  PRINT_MEMBER (OffsetsBasic, field_pointer);
  PRINT_MEMBER (OffsetsBasic, dummy6);
  PRINT_MEMBER (OffsetsBasic, field_float);
  PRINT_MEMBER (OffsetsBasic, dummy7);
  PRINT_MEMBER (OffsetsBasic, field_double);
  PRINT_MEMBER (OffsetsBasic, dummy8);
  PRINT_MEMBER (OffsetsBasic, field_size);
  PRINT_MEMBER (OffsetsBasic, dummy9);
  PRINT_MEMBER (OffsetsBasic, field_uchar1);
  PRINT_MEMBER (OffsetsBasic, dummy10);
  PRINT_MEMBER (OffsetsBasic, field_uchar2);
  PRINT_MEMBER (OffsetsBasic, dummy11);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsEnum);
  PRINT_MEMBER (OffsetsEnum, enum1);
  PRINT_MEMBER (OffsetsEnum, dummy1);
  PRINT_MEMBER (OffsetsEnum, enum2);
  PRINT_MEMBER (OffsetsEnum, dummy2);
  PRINT_MEMBER (OffsetsEnum, enum3);
  PRINT_MEMBER (OffsetsEnum, dummy3);
  PRINT_MEMBER (OffsetsEnum, enum4);
  PRINT_MEMBER (OffsetsEnum, dummy4);
  PRINT_MEMBER (OffsetsEnum, enum5);
  PRINT_MEMBER (OffsetsEnum, dummy5);
  PRINT_MEMBER (OffsetsEnum, enum6);
  PRINT_MEMBER (OffsetsEnum, dummy6);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsNested);
  PRINT_MEMBER (OffsetsNested, dummy1);
  PRINT_MEMBER (OffsetsNested, nestee);
  PRINT_MEMBER (OffsetsNested, dummy2);
  PRINT_MEMBER (OffsetsNested, nestee_union);
  PRINT_MEMBER (OffsetsNested, dummy3);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsNestee);
  PRINT_MEMBER (OffsetsNestee, field1);
  PRINT_MEMBER (OffsetsNestee, field2);
  PRINT_MEMBER (OffsetsNestee, field3);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsObj);
  PRINT_MEMBER (OffsetsObj, parent_instance);
  PRINT_MEMBER (OffsetsObj, other);
  g_fprintf (outfile, "\n");

  PRINT_TYPE (OffsetsObjClass);
  PRINT_MEMBER (OffsetsObjClass, parent_class);
  g_fprintf (outfile, "\n");

#undef ALIGNOF
#undef PRINT_TYPE
#undef PRINT_MEMBER
}

int main(int argc, char **argv)
{
  GError *error = NULL;
  FILE *outfile;

  if (argc != 3)
    g_error ("Usage: gitestoffsets COMPILED_OFFSETS_FILE INTROSPECTED_OFFSETS_FILE");

  repository = g_irepository_get_default ();
  if (!g_irepository_require (repository, namespace, version, 0, &error))
     g_error ("Failed to load %s-%s.typelib: %s", namespace, version, error->message);

  outfile = fopen (argv[1], "w");
  if (!outfile)
    g_error ("Cannot open '%s': %s'", argv[1], g_strerror(errno));

  compiled (outfile);
  fclose (outfile);

  outfile = fopen (argv[2], "w");
  if (!outfile)
    g_error ("Cannot open '%s': %s'", argv[1], g_strerror(errno));

  introspected_struct (outfile, "Array");
  introspected_struct (outfile, "MultiDimArray");
  introspected_struct (outfile, "Basic");
  introspected_struct (outfile, "Enum");
  introspected_struct (outfile, "Nested");
  introspected_struct (outfile, "Nestee");
  introspected_struct (outfile, "Obj");
  introspected_struct (outfile, "ObjClass");

  fclose (outfile);

  return 0;
}
