/* msgunfmt - converts binary .mo files to Uniforum style .po files
   Copyright (C) 1995 Free Software Foundation, Inc.
   Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.

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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/types.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
#endif

#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif

#include "hash.h"

#include "error.h"
#include "getline.h"
#include "printf.h"
#include <system.h>

#include "gettext.h"
#include "domain.h"
#include "hash-string.h"
#include <libintl.h>
#include "message.h"

#define _(str) gettext (str)

#ifndef errno
extern int errno;
#endif


/* String containing name the program is called with.  */
const char *program_name;

/* Long options.  */
static const struct option long_options[] =
{
  { "escape", no_argument, NULL, 'E' },
  { "no-escape", no_argument, NULL, 'e' },
  { "help", no_argument, NULL, 'h' },
  { "version", no_argument, NULL, 'V' },
  { "indent", no_argument, NULL, 'i' },
  { "strict", no_argument, NULL, 'S' },
  { NULL, 0, NULL, 0 }
};


/* This defines the byte order within the file.  It needs to be set
   appropriately once we have the file open.  */
static enum { MO_LITTLE_ENDIAN, MO_BIG_ENDIAN } endian;


/* Prototypes for local functions.  */
static void usage __P ((int __status));
static void error_print __P ((void));
static nls_uint32 read32 __P ((FILE *__fp, char *__fn));
static void seek32 __P ((FILE *__fp, char *__fn, long __offset));
static char *string32 __P ((FILE *__fp, char *__fn, long __offset));
static message_list_ty *read_mo_file __P ((char *__fn));


int
main (argc, argv)
     int argc;
     char **argv;
{
  int optchar;
  int do_help = 0;
  int do_version = 0;
  message_list_ty *mlp;

  /* Set program name for messages.  */
  program_name = argv[0];
  error_print_progname = error_print;

#ifdef HAVE_SETLOCALE
  /* Set locale via LC_ALL.  */
  setlocale (LC_ALL, "");
#endif

  /* Set the text message domain.  */
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  while ((optchar = getopt_long (argc, argv, "hio:SV", long_options, NULL))
	 != EOF)
    switch (optchar)
      {
      case '\0':
	/* long option */
	break;

      case 'e':
	message_print_style_escape (0);
	break;

      case 'E':
	message_print_style_escape (1);
	break;

      case 'h':
	do_help = 1;
	break;

      case 'i':
	message_print_style_indent ();
	break;

      case 'S':
	message_print_style_uniforum ();
	break;

      case 'V':
	do_version = 1;
	break;

      default:
	usage (EXIT_FAILURE);
	break;
      }

  /* Version information is requested.  */
  if (do_version)
    {
      fprintf (stderr, "%s - GNU %s %s\n", program_name, PACKAGE, VERSION);
      exit (EXIT_SUCCESS);
    }

  /* Help is requested.  */
  if (do_help)
    usage (EXIT_SUCCESS);

  /* Test whether we have an .po file name as argument.  */
  if (optind + 2 != argc)
    {
      error (0, 0, _("exactly two files must be specified"));
      usage (EXIT_FAILURE);
    }

  /* Read the given .mo file. */
  mlp = read_mo_file (argv[optind]);

  /* Write the resulting message list to the given .po file.  */
  message_list_print (mlp, argv[optind + 1]);

  /* No problems.  */
  exit (EXIT_SUCCESS);
}


/* Display usage information and exit.  */
static void
usage (status)
     int status;
{
  if (status != EXIT_SUCCESS)
    fprintf (stderr, _("Try `%s --help' for more information\n"),
	     program_name);
  else
    {
      printf (_("\
Usage: %s [OPTION] filename.mo filename.po\n\
Mandatory arguments to long options are mandatory for short options too.\n\
  -e, --no-escape          do not use C escapes in output (default)\n\
  -E, --escape             use C escapes in output, no extended chars\n\
  -h, --help               display this help and exit\n\
  -i, --indent             write indented output style\n\
  -S, --strict	           write strict uniforum style\n\
  -V, --version            output version information and exit\n"),
	      program_name);
      fputs (_("\n\
Convert binary .mo files to Uniforum style .po files.\n\
Both little-endian and big-endian .mo files are handled.\n\
If input file is -, standard input is read.  If output file is -,\n\
output is written to standard output.\n"),
	     stdout);
    }

  exit (status);
}


/* The address of this function will be assigned to the hook in the error
   functions.  */
static void
error_print ()
{
  /* We don't want the program name to be printed in messages.  Emacs'
     compile.el does not like this.  */
}


/* This function reads a 32-bit number from the file, and assembles it
   according to the current ``endian'' setting.  */
static nls_uint32
read32 (fp, fn)
     FILE *fp;
     char *fn;
{
  int c1, c2, c3, c4;

  c1 = getc (fp);
  if (c1 == EOF)
    {
    bomb:
      if (ferror (fp))
	error (EXIT_FAILURE, errno, _("error while reading \"%s\""), fn);
      error (EXIT_FAILURE, 0, _("file \"%s\" truncated"), fn);
    }
  c2 = getc (fp);
  if (c2 == EOF)
    goto bomb;
  c3 = getc (fp);
  if (c3 == EOF)
    goto bomb;
  c4 = getc (fp);
  if (c4 == EOF)
    goto bomb;
  if (endian == MO_LITTLE_ENDIAN)
    return (((nls_uint32) c1)
	    | ((nls_uint32) c2 << 8)
	    | ((nls_uint32) c3 << 16)
	    | ((nls_uint32) c4 << 24));

  return (((nls_uint32) c1 << 24)
	  | ((nls_uint32) c2 << 16)
	  | ((nls_uint32) c3 << 8)
	  | ((nls_uint32) c4));
}


static void
seek32 (fp, fn, offset)
     FILE *fp;
     char *fn;
     long offset;
{
  if (fseek (fp, offset, 0) < 0)
    error (EXIT_FAILURE, errno, _("seek \"%s\" offset %ld failed"),
	   fn, offset);
}


static char *
string32 (fp, fn, offset)
     FILE *fp;
     char *fn;
     long offset;
{
  long length;
  char *buffer;
  long n;

  /* Read the string_desc structure, describing where in the file to
     find the string.  */
  seek32 (fp, fn, offset);
  length = read32 (fp, fn);
  offset = read32 (fp, fn);

  /* Allocate memory for the string to be read into.  Leave space for
     the NUL on the end.  */
  buffer = xmalloc (length + 1);

  /* Read in the string.  Complain if there is an error or it comes up
     short.  Add the NUL ourselves.  */
  seek32 (fp, fn, offset);
  n = fread (buffer, 1, length, fp);
  if (n != length)
    {
      if (ferror (fp))
	error (EXIT_FAILURE, errno, _("error while reading \"%s\""), fn);
      error (EXIT_FAILURE, 0, _("file \"%s\" truncated"), fn);
    }
  buffer[length] = 0;

  /* Return the string to the caller.  */
  return buffer;
}


/* This function reads and existing .mo file.  Return a message list.  */
static message_list_ty *
read_mo_file (fn)
     char *fn;
{
  FILE *fp;
  struct mo_file_header header;
  message_list_ty *mlp;
  int j;

  fp = fopen (fn, "rb");
  if (fp == NULL)
    {
      error (EXIT_FAILURE, errno, _("error while opening \"%s\" for reading"),
	     fn);
    }

  /* We must grope the file to determine which endian it is.
     Perversity of the universe tends towards maximum, so it will
     probably not match the currently executing architecture.  */
  endian = MO_BIG_ENDIAN;
  header.magic = read32 (fp, fn);
  if (header.magic != _MAGIC)
    {
      endian = MO_LITTLE_ENDIAN;
      seek32 (fp, fn, 0L);
      header.magic = read32 (fp, fn);
      if (header.magic != _MAGIC)
	{
	unrecognised:
	  error (EXIT_FAILURE, 0, _("file \"%s\" is not in GNU .mo format"),
		 fn);
	}
    }

  /* Fill the structure describing the header.  */
  header.revision = read32 (fp, fn);
  if (header.revision != MO_REVISION_NUMBER)
    goto unrecognised;
  header.nstrings = read32 (fp, fn);
  header.orig_tab_offset = read32 (fp, fn);
  header.trans_tab_offset = read32 (fp, fn);
  header.hash_tab_size = read32 (fp, fn);
  header.hash_tab_offset = read32 (fp, fn);

  mlp = message_list_alloc ();
  for (j = 0; j < header.nstrings; ++j)
    {
      static lex_pos_ty pos = { __FILE__, __LINE__ };
      message_ty *mp;
      char *msgid;
      char *msgstr;

      /* Read the msgid.  */
      msgid = string32 (fp, fn, header.orig_tab_offset + j * 8);

      /* Read the msgstr.  */
      msgstr = string32 (fp, fn, header.trans_tab_offset + j * 8);

      mp = message_alloc (msgid);
      message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr, &pos);
      message_list_append (mlp, mp);
    }
  fclose (fp);
  return mlp;
}
