#include <stdio.h>
#include <time.h>
#include <string.h>
#include <glib.h>


typedef struct { gchar bytes[8]; } gint64_be;
typedef struct { gchar bytes[4]; } gint32_be;
typedef struct { gchar bytes[4]; } guint32_be;

static inline gint64 gint64_from_be (const gint64_be be) {
  gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
}

static inline gint32 gint32_from_be (const gint32_be be) {
  gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
}

static inline guint32 guint32_from_be (const guint32_be be) {
  guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
}


struct tzhead
{
  gchar      tzh_magic[4];
  gchar      tzh_version;
  guchar     tzh_reserved[15];

  guint32_be tzh_ttisgmtcnt;
  guint32_be tzh_ttisstdcnt;
  guint32_be tzh_leapcnt;
  guint32_be tzh_timecnt;
  guint32_be tzh_typecnt;
  guint32_be tzh_charcnt;
};

struct ttinfo
{
  gint32_be tt_gmtoff;
  guint8    tt_isdst;
  guint8    tt_abbrind;
};


struct _GTimeZone
{
  gchar   *name;

  GBytes *zoneinfo;

  const struct tzhead *header;
  const struct ttinfo *infos;
  const gint64_be     *trans;
  const guint8        *indices;
  const gchar         *abbrs;
  gint                 timecnt;

  gint     ref_count;
};


inline static const struct ttinfo *
interval_info (GTimeZone *tz,
               gint       interval)
{
  if (interval)
    return tz->infos + tz->indices[interval - 1];

  return tz->infos;
}

inline static gint64
interval_start (GTimeZone *tz,
		gint       interval)
{
  if (interval)
    return gint64_from_be (tz->trans[interval - 1]);

  return G_MININT64;
}

inline static gint64
interval_end (GTimeZone *tz,
	      gint       interval)
{
  if (interval < tz->timecnt)
    return gint64_from_be (tz->trans[interval]) - 1;

  return G_MAXINT64;
}

inline static gint32
interval_offset (GTimeZone *tz,
                 gint       interval)
{
  return gint32_from_be (interval_info (tz, interval)->tt_gmtoff);
}

inline static gboolean
interval_isdst (GTimeZone *tz,
                gint       interval)
{
  return interval_info (tz, interval)->tt_isdst;
}

inline static guint8
interval_abbrind (GTimeZone *tz,
                  gint       interval)
{
  return interval_info (tz, interval)->tt_abbrind;
}


int
main (int argc, char *argv[])
{
	GTimeZone *tz;
	GDateTime *dt;
	gint64 t;
	gint32 offset;
	char *s;
	int i;


	tz = g_time_zone_new (argv[1]);


	if (!tz->zoneinfo)
	{
		g_printf ("Can't find or parse time zone \"%s\".\n", argv[1]);

		g_time_zone_unref (tz);

		return 1;
	}


	g_printf ("LOCAL TIME TYPES:\n\n");

	for (i = 0; i < guint32_from_be (tz->header->tzh_typecnt); i++)
	{
		g_printf ("Index:    %d\n", i);

		offset = gint32_from_be (tz->infos[i].tt_gmtoff);
		g_printf ("Offset:   %+03d%02d\n",
		          (int) offset / 3600,
			  (int) abs (offset / 60) % 60);

		g_printf ("DST:      %s\n", tz->infos[i].tt_isdst? "Yes" : "No");

		g_printf ("Abbr:     %s\n", tz->abbrs + tz->infos[i].tt_abbrind);

		g_printf ("\n");
	}

	g_printf ("\n");


	g_printf ("TRANSITION TIMES:\n\n");

	for (i = 0; i <= tz->timecnt; i++)
	{
		g_printf ("Index:    %d\n", i);

		g_printf ("Type:     %d\n", i? tz->indices[i - 1] : 0);

		offset = interval_offset (tz, i);
		g_printf ("Offset:   %+03d%02d\n",
		          (int) offset / 3600,
			  (int) abs (offset / 60) % 60);

		g_printf ("DST:      %s\n", interval_isdst (tz, i)? "Yes" : "No");

		g_printf ("Abbr:     %s\n", tz->abbrs + interval_abbrind (tz, i));


		t = interval_start (tz, i);

		if (t != G_MININT64)
		{
			dt = g_date_time_new_from_unix_utc (t);
			s = g_date_time_format (dt, "%c");
			g_printf ("Start:    %s\n", s? s : "(none)");
			g_free (s);
			g_date_time_unref (dt);

			g_printf ("          %" G_GINT64_FORMAT "\n", t);
		}
		else
			g_printf ("Start:    mininum\n");


		t = interval_end (tz, i);

		if (t != G_MAXINT64)
		{
			dt = g_date_time_new_from_unix_utc (t);
			s = g_date_time_format (dt, "%c");
			g_printf ("End:      %s\n", s? s : "(none)");
			g_free (s);
			g_date_time_unref (dt);

			g_printf ("          %" G_GINT64_FORMAT "\n", t);
		}
		else
			g_printf ("End:      maximum\n");

		g_printf ("\n");
	}

	g_time_zone_unref (tz);

	return 0;
}
