/*
 * palm2gcf.c:
 *
 * Copyright (c) 2003 Guralp Systems Limited
 * Author James McKenzie, contact <software@guralp.com>
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

static char rcsid[] =
  "$Id: palm2gcf.c,v 1.8 2002/07/18 19:25:57 root Exp root $";

/*
 * $Log: palm2gcf.c,v $
 * Revision 1.8  2002/07/18 19:25:57  root
 * #
 *
 * Revision 1.7  2001/11/19 11:28:10  root
 * #
 *
 * Revision 1.6  2001/10/29 16:44:28  root
 * #
 *
 * Revision 1.5  2001/10/29 16:44:25  root
 * #
 *
 * Revision 1.4  2001/10/26 14:28:24  root
 *
 * Revision 1.1  2001/10/21 01:26:19  root
 * Initial revision
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

#ifndef WIN32
#include <strings.h>
#endif


FILE *infile;
FILE *outfile;

/*Type and Creator ID for the palm database*/
#define TYPECREAT "GCFBSCRM"

/*Structure to represent time*/
typedef struct Jgcf_utc_struct
{
  int year;
  int yday;
  int mon;
  int day;
  int hour;
  int min;
  int sec;
}
Jgcf_utc;


/*GCF time representation*/
typedef struct
{
  int day;
  int sec;
}
Jgcf_time;

/************************ Lowlevel gcf ********************/

/*Extract various datatypes portably - not as easy as you think*/

long
gp_uint8 (unsigned char *buf)
{
  return (long) *buf;
}

long
gp_uint16 (unsigned char *buf)
{
  return ((long) buf[0] << 8) | (long) buf[1];
}

long
gp_uint24 (unsigned char *buf)
{
  return ((long) buf[0] << 16) | ((long) buf[1] << 8) | (long) buf[2];
}

long
gp_uint32 (unsigned char *buf)
{
  return ((long) buf[0] << 24) | ((long) buf[1] << 16) | ((long) buf[2] << 8)
    | (long) buf[3];
}

void
pp_uint32 (unsigned char *buf, unsigned long i)
{
  buf[0] = (unsigned char) ((i >> 24) & 0xffL);
  buf[1] = (unsigned char) ((i >> 16) & 0xffL);
  buf[2] = (unsigned char) ((i >> 8) & 0xffL);
  buf[3] = (unsigned char) (i & 0xffL);
}

long
gp_int8 (unsigned char *buf)
{
  if ((*buf) & 0x80)
    {
      return ((long) *buf) - 0x100L;
    }
  return gp_uint8 (buf);
}

long
gp_int16 (unsigned char *buf)
{
  if (buf[0] & 0x80)
    {
      return (((long) buf[0] << 8) | (long) buf[1]) - 0x10000L;
    }
  return gp_uint16 (buf);
}


long
gp_int24 (unsigned char *buf)
{

  if (buf[0] & 0x80)
    {
      return (((long) buf[0] << 16) | ((long) buf[1] << 8) | (long) buf[2]) -
        0x1000000L;
    }
  return gp_uint24 (buf);
}

long
gp_int32 (unsigned char *buf)
{
  long ret;
  if (buf[0] & 0x80)
    {

      /*Bad voodoo below to make sure we don't overflow */
      ret =
        (((long) buf[1] << 16) | ((long) buf[2] << 8) | (long) buf[3]) -
        0x1000000L;
      ret -= ((0xffL - (long) buf[0]) << 24);

      return ret;
    }
  return gp_uint32 (buf);
}


/*Make a 6 char string from a 32bit quantity */

char *
gp_base36_to_a (long i)
{
  char ret[20];
  char buf[20];
  long j, k;

  ret[0] = 0;

  while (i)
    {
      k = (long) i / 36;
      j = i - (k * 36);

      strcpy (buf, ret);

      if (j < 10)
        ret[0] = j + '0';
      else
        ret[0] = (j - 10) + 'A';

      ret[1] = 0;
      strcat (ret, buf);
      i = k;
    }

  return strdup (ret);
}


/*************************** Mid level gcf ***********************/

int
jgcf_days_in_year (int year)
{
/*FIXME:*/
  if (year % 4)
    return 365;                 /*Not divisable by 4 */
  if (year % 100)
    return 366;                 /*Not divisable by 20 */
  if (year % 400)
    return 365;                 /*Not divisable by 400 */

  return 366;
}

int
jgcf_days_in_month (int month, int year)
{
  switch (month)
    {
    case 0:                    /*Jan */
      return 31;
    case 1:                    /*Feb */
      return (jgcf_days_in_year (year) == 365) ? 28 : 29;
    case 2:                    /*Mar */
      return 31;
    case 3:                    /*Apr */
      return 30;
    case 4:                    /*May */
      return 31;
    case 5:                    /*Jun */
      return 30;
    case 6:                    /*Jul */
      return 31;
    case 7:                    /*Aug */
      return 31;
    case 8:                    /*Sept */
      return 30;
    case 9:                    /*Oct */
      return 31;
    case 10:                   /*Nov */
      return 30;
    case 11:                   /*Dec */
      return 31;
    default:
      fprintf (stderr, "Days in month called for Nonexistant month\n");
      exit (1);
    }

  return 0;                     /*Keep irix compiler happy */
}

Jgcf_utc
jgcf_gtime_to_utc (Jgcf_time t)
{
  Jgcf_utc ret;
  int ds;

  ret.year = 1989;
  ret.yday = t.day + 320;       /*Epoch Day 320 1989 = Nov 17th */


  ds = jgcf_days_in_year (ret.year);

  while (ret.yday >= ds)
    {
      ret.year++;
      ret.yday -= ds;

      ds = jgcf_days_in_year (ret.year);
    }

  ret.day = ret.yday;
  ret.mon = 0;

  ds = jgcf_days_in_month (ret.mon, ret.year);
  while (ret.day >= ds)
    {
      ret.day -= ds;
      ret.mon++;
      ds = jgcf_days_in_month (ret.mon, ret.year);
    }


  ret.day++;
  ret.mon++;


  ret.sec = t.sec % 60;
  t.sec = t.sec / 60;
  ret.min = t.sec % 60;
  t.sec = t.sec / 60;
  ret.hour = t.sec;

  return ret;
}

void
jgcf_print_utc (Jgcf_utc u)
{
  static char *mon_name[] = { "", "Jan", "Feb", "Mar",
    "Apr", "May", "Jun",
    "Jul", "Aug", "Sep",
    "Oct", "Nov", "Dec"
  };
  printf ("%02d:%02d:%02d %02d/%s/%04d\n",
          u.hour, u.min, u.sec, u.day, mon_name[u.mon], u.year);
}




/*************************** Lowlevel palm ***********************/

void
pg_seekto (long o)
{
  fseek (infile, o, SEEK_SET);
  if (ftell (infile) != o)
    {
      fprintf (stderr, "fseek(infile,%ld,SEEK_SET) failed\n", o);
      exit (1);
    }
}

void
pg_read (void *buf, int n)
{
  if (fread (buf, 1, n, infile) != n)
    {
      fprintf (stderr, "fread(buf,1,%d,infile) failed\n", n);
      fprintf (stderr, "offset was %ld\n", (long) ftell (infile));
      exit (1);
    }
}


void
pg_write (void *buf, int n)
{
  if (fwrite (buf, 1, n, outfile) != n)
    {
      fprintf (stderr, "fwrite(buf,1,%d,outfile) failed\n", n);
      fprintf (stderr, "offset was %ld\n", (long) ftell (outfile));
      perror ("fish");
      exit (1);
    }
}

int
pg_readshort (void)
{
  unsigned char buf[2];
  pg_read (buf, 2);
  return buf[1] | (buf[0] << 8);
}

long
pg_readlong (void)
{
  unsigned char buf[4];
  pg_read (buf, 4);
  return buf[3] | (buf[2] << 8) | (buf[1] << 16) | (buf[0] << 24);
}

char *
pg_readstring (int n)
{
  char *ret = malloc (n + 1);
  pg_read (ret, n);
  ret[n] = 0;
  return ret;
}




/************************ High level gcf **************************/

#define BLOCKSIZE 1024

void
block_repack_24_to_32 (unsigned char *buf, int blen, int samples)
{
  unsigned char blk[BLOCKSIZE];
  unsigned char *rptr, *wptr;
  long val, nval;               /*needs to be at least 32 bits signed */
  unsigned long diff;

  if (sizeof (long) < 4)
    {
      fprintf (stderr, "repack_24_to_32: assertion sizeof(long)>=4 failed\n");
      exit (1);
    }

  memcpy (blk, buf, BLOCKSIZE);

  rptr = blk + 16;
  wptr = buf + 16;

/*First is 4 bits of First absolute value, same */

  val = gp_int32 (rptr);

  rptr += 4;
  wptr += 4;

  if (gp_uint24 (rptr))
    {
      fprintf (stderr,
               "repack_24_to_32: assertion first_difference==0 failed\n");
      exit (1);
    }

/*Extracting this into integers whose size we don't */
/*know in a portable way is a headache */

  while (samples--)
    {
/*Use unsigned 24 bit offsets, (note this could be a negative difference */
/*in which case we've added an extra 0x1000000*/
      nval = val + gp_uint24 (rptr); /*Integrate */
      rptr += 3;

/*Now restore negatives, we need the while because there is one case in */
/*which we need to subtract twice*/
      while (nval >= 0x800000L)
        nval -= 0x1000000L;


/*And for the next trick we have to put this difference back */
/*nval-val is a 33 bit quantity, and diff is at least 32 bits */
/*So we have to take care of the sign bit ourselves, (the ambiguity) */
/*we create by packing 33 bits into 32 bits is resolved because the */
/*number that comes out after integration must fit in 32 bits */
/*(see above for how this is done for 25/24 bits)*/

      if (nval < val)
        {
          diff = (val - nval) - 1L;
          diff = 0xffffffffL - diff;
          pp_uint32 (wptr, diff);
          wptr += 4;
        }
      else
        {
          diff = nval - val;
          pp_uint32 (wptr, diff);
          wptr += 4;
        }

      val = nval;
    }

/*And finally we have to copy the final absolute value*/
  memcpy (wptr, rptr, 4);
  nval = gp_int32 (rptr);

  if (nval != val)
    {
      fprintf (stderr,
               "repack_24_to_32: fic!=ric (Not Fatal - but check data)\n");
    }
}

void
new_block (int bnum, int blen)
{
  unsigned char buf[BLOCKSIZE];
  char *sys;
  char *str;
  int rate;
  int format;
  int records;
  int samples;
  int i;
  Jgcf_time start;
  Jgcf_utc utc;

  if (blen > 1024)
    {
      fprintf (stderr, "Error: blocklen > 1024\n");
      exit (1);
    }

  memset (buf, 0, BLOCKSIZE);

  pg_read (buf, blen);

  sys = gp_base36_to_a (gp_uint32 (buf));
  str = gp_base36_to_a (gp_uint32 (buf + 4));
  rate = gp_uint8 (buf + 13);
  format = gp_uint8 (buf + 14) & 7;
  records = gp_uint8 (buf + 15);

  samples = records * format;

  i = gp_uint16 (buf + 8);
  start.day = i >> 1;
  i &= 1;
  i <<= 16;
  start.sec = i | gp_uint16 (buf + 10);

  if (format == 1)
    {
      if ((samples * 3) == (blen - 24))
        {
          block_repack_24_to_32 (buf, blen, samples);
        }
      else if ((samples * 4) == (blen - 24))
        {
          ;
        }
      else
        {
          fprintf (stderr, "A record with format==1 is made neither of\n");
          fprintf (stderr, "  24 bit quantities or 32 bit quantities\n");
          exit (1);
        }

    }

  printf ("Block %5d: %s %s %3d sps %3d samples time ", bnum, sys, str, rate,
          samples);

  free (sys);
  free (str);

  utc = jgcf_gtime_to_utc (start);
  jgcf_print_utc (utc);

  pg_write (buf, BLOCKSIZE);


}

int
main (int argc, char *argv[])
{
  char *ptr;
  int recs;
  long rptr;
  int i;
  int blen;

  if (argc != 3)
    {
      fprintf (stderr, "Usage:\n");
      fprintf (stderr, "palm2gcf infile outfile\n");
      exit (1);
    }

#ifndef WIN32
  infile = fopen (argv[1], "r");
#else
  infile = fopen (argv[1], "rb");
#endif

  if (!infile)
    {
      fprintf (stderr, "can't open %s for reading\n", argv[1]);
      exit (1);
    }

#ifndef WIN32
  outfile = fopen (argv[2], "w");
#else
  outfile = fopen (argv[2], "wb");
#endif

  if (!outfile)
    {
      fprintf (stderr, "can't open %s for writing\n", argv[2]);
      exit (1);
    }

  pg_seekto (60L);
  ptr = pg_readstring (8);
  if (strncmp (ptr, TYPECREAT, 8))
    {
      fprintf (stderr, "%s is not Scream Record DB file\n", argv[1]);
      fprintf (stderr, "Type and creator are %s\n", ptr);
      exit (1);
    }

  pg_seekto (76L);
  recs = pg_readshort ();

  pg_seekto (78L);
  rptr = pg_readlong ();
  pg_seekto (rptr);
  rptr = pg_readlong ();


  printf ("File %s contains %d blocks total %ld bytes\n",
          argv[1], recs, rptr);

  for (i = 1; i < recs; ++i)
    {
      pg_seekto ((long) ((i * 8) + 78));
      rptr = pg_readlong ();
      pg_seekto (rptr);
      blen = pg_readshort ();

      new_block (i, blen);
    }

  printf ("All blocks processed\n");
  printf ("GCF stream is in %s\n", argv[2]);

  fclose (outfile);
  fclose (infile);
  exit (0);
}

