/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <pkimsg.h>
#include <pkiqdxpg.h>
#include <ctype.h>

#ifdef BOOTSTRAP
#include "bootstrap_xpgmsg.h"
#else
#include "xpgmsg.h"
#endif

/* #define DEBUG */

uint32 read_id(const char * line, 
               int & i, 
               int & id,
               char name[64],
               bool need_ident = false) {
// Read a numeric or named value.
  int s = 0;
  id = 0;
  memset(name, 0, 64);
  while (isspace(line[i])) i++;
  if (line[i] == 0) return GENCAT_EXPECTING_IDENTIFIER;
  if (isdigit(line[i])) {
    if (need_ident) return GENCAT_EXPECTING_IDENTIFIER;
// This is a numeric identifier
    while (isdigit(line[i])) id = id * 10 + line[i++] - '0';
  } else if (isalpha(line[i]) || (line[i] == '_')) {
// This is a named identifier
    while ((isalnum(line[i]) || (line[i] == '_'))&& (s < 63)) {
      name[s++] = line[i++];
      name[s] = 0;
    };
  } else return GENCAT_EXPECTING_IDENTIFIER;
// Skip over any trailing whitespace and return.
  while (isspace(line[i])) i++;
  return 0;
}

char * makeId(const char * s) {
  char * res;
  char * temp;
  char * file_ptr;
  char filebuf[512];
  int i;
  int len;
  unsigned idx;

  len = strlen(s);
  
  for (i=0; i<sizeof(filebuf) && (i <= len); i++) {
    filebuf[sizeof(filebuf)-i-1] = toupper(s[len-i]);
  };
  file_ptr = filebuf+sizeof(filebuf)-i;

  temp = strrchr(file_ptr, '\\');
  if (temp != NULL) file_ptr = temp+1;
  temp = strrchr(file_ptr, '/');
  if (temp != NULL) file_ptr = temp+1;
  temp = strrchr(file_ptr, ':');
  if (temp != NULL) file_ptr = temp+1;
      
  while ((idx = strspn(file_ptr, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")) < strlen(file_ptr))
    file_ptr[idx] = '_';

  res = (char *)malloc(strlen(file_ptr) + 8);
  strcpy(res, "GENCAT_");
  strcat(res, file_ptr);
  return res;
}

void processLine(char * l) {
// Run through the line, converting escaped characters to their char equivalents.
  char * s;
  char * d;

  for (d=l,s=l; *s != 0; d++,s++) {
    if (*s == '\\') {
      s++;
      switch (*s) {
      case 'n' : *d='\n'; break;
      case 't' : *d='\t'; break;
      case 'b' : *d='\b'; break;
      case 'r' : *d='\r'; break;
      case 'f' : *d='\f'; break;
      case 'a' : *d='\a'; break;
      default : *d=*s; break;
      }
    } else {
      *d=*s;
    };
  };
  *d=0;
}

char * makeName(const char * s, const char * ext) {
  char * res = NULL;
  const char * filetype;
  int i;
  filetype = strrchr(s, '.');
  if (filetype == NULL) {
// We justy need to append the extension...
    res = (char *)malloc(strlen(s) + strlen(ext) + 1);
    strcpy(res, s);
    strcat(res, ext);
  } else {
// More complicated.  Need to replace the existing extension...
    i=0;
// Does the "type" contain directory parts?
    while ((filetype[i] != 0) && (filetype[i] != '/') && (filetype[i] != '\\')) i++;
    if (filetype[i] != 0) {
// Yes.  It's not a type after all, so ignore it.
      res = (char *)malloc(strlen(s) + strlen(ext) + 1);
      strcpy(res, s);
      strcat(res, ext);
    } else {
// No.  So strip off the extension, and replace it with <ext>
      res = (char *)malloc(filetype - s + strlen(ext) + 1);
      memset(res, 0, filetype - s + strlen(ext) + 1);
      strncpy(res, s, filetype-s);
      strcat(res, ext);
    };
  };
  return res;
}

int main(int argc, char * argv[]) {
  FILE * source;
  FILE * temp_catalog;
  FILE * temp_header;
  char * header_id = NULL;
  int srcArg;
  int dstArg;
  int hdrArg;
  bool append = false;
  const char * catalog_name = NULL;
  const char * temp_catalog_name = NULL;
  const char * header_name = NULL;
  const char * temp_header_name = NULL;
  char catName[512];
  time_t longtime;
  pki_msgcat_handle_t new_catalog;
  pki_msgcat_handle_t gencat_message_catalog;
  int line_length;
  int currentSet = 0;
  char currentSetName[64];
  int currentMsg = 0;
  int msgId = 0;
  int setId = 0;
  char currentMsgName[64];
  char quoteChar = '"';
  int i;
  uint32 st;
  char line[2048];
  char * cptr;

  
  gencat_message_catalog = pkiCatOpen("Jonahmsg.cat");

#ifdef DEBUG
  fprintf(stderr, "Result of CatOpen: handle = %d\n", gencat_message_catalog);
  fflush(stderr);
#endif

#ifdef DEBUG
  xpg_catalog_t::display();
#endif

  if ((argc < 2) || (argc > 5)) {
#ifdef DEBUG
    fprintf(stderr, "Usage: Wrong number of parameters\n");
    fflush(stderr);
#endif
    PKI_OSSRV_fprintMsg(stderr, 
                        gencat_message_catalog, 
                        GENCAT_USAGE_ERROR, 
                        "Usage: gencat [-c] <source-file> [<catalog> [<header-file>]]\n");
    return EXIT_FAILURE;
  };

  if (strcmp(argv[1], "-c") == 0) {
    append = true;
    srcArg = 2;

    fprintf(stderr, "Error: Create access not yet implemented.\n");
    return EXIT_FAILURE;

  } else srcArg = 1;

  if (srcArg < argc-1) {
    dstArg = srcArg+1;
    if (dstArg < argc-1) {
      hdrArg = dstArg+1;
    } else hdrArg = 0;
  } else {
    dstArg = 0;
    hdrArg = 0;
  };

#ifdef DEBUG
    fprintf(stderr, "Using %s as source\n", argv[srcArg]);
    fflush(stderr);
#endif

  if ((source = fopen(argv[srcArg], "r")) == NULL) {
#ifdef DEBUG
    fprintf(stderr, "Can't open source file\n");
    fflush(stderr);
#endif
    PKI_OSSRV_fprintMsg(stderr, 
                        gencat_message_catalog, 
                        GENCAT_CANT_OPEN_SOURCE, 
                        "Error: Can't open source file %s\n",
                        argv[srcArg]);
    return EXIT_FAILURE;
  };

  if (dstArg == 0) {
    catalog_name = makeName(argv[srcArg], ".cat");
  } else {
    catalog_name = makeName(argv[dstArg], ".cat");
  };

#ifdef DEBUG
    fprintf(stderr, "Using %s as catalog\n", catalog_name);
    fflush(stderr);
#endif

  if (hdrArg == 0) {
    header_name = makeName(catalog_name, ".h");
  } else {
    header_name = makeName(argv[hdrArg], ".h");
  };
  
#ifdef DEBUG
    fprintf(stderr, "Using %s as header\n", header_name);
    fflush(stderr);
#endif

  temp_header_name = makeName(header_name, ".hmp");
  temp_catalog_name = makeName(catalog_name, ".tmp");

#ifdef DEBUG
    fprintf(stderr, "Using %s as temporary header\n", temp_header_name);
    fprintf(stderr, "and %s as temporary catalog\n", temp_catalog_name);
    fflush(stderr);
#endif

  if ((temp_catalog = fopen(temp_catalog_name , "wb")) == NULL) {
#ifdef DEBUG
    fprintf(stderr, "Error opening temporary catalog\n");
    fflush(stderr);
#endif
    PKI_OSSRV_fprintMsg(stderr, 
                        gencat_message_catalog, 
                        GENCAT_CANT_OPEN_CATALOG, 
                        "Error: Can't open temporary catalog %s\n",
                        temp_catalog_name);
    return EXIT_FAILURE;
  };


  if ((temp_header = fopen(temp_header_name, "w")) == NULL) {
#ifdef DEBUG
    fprintf(stderr, "Error opening temporary header\n");
    fflush(stderr);
#endif
    PKI_OSSRV_fprintMsg(stderr, 
                        gencat_message_catalog, 
                        GENCAT_CANT_CREATE_HEADER, 
                        "Error: Can't create temporary header %s\n",
                        temp_header_name);
    return EXIT_FAILURE;
  };
  header_id = makeId(header_name);

  new_catalog = pkiCatOpen(catalog_name);
#ifdef DEBUG
    fprintf(stderr, "pkiCatOpen returned %d for open of %s\n", new_catalog, catalog_name);
    fflush(stderr);
#endif
  if (new_catalog == 0) {
// Create the in-memory catalog...
    time(&longtime);
    sprintf(catName, "Catalog(%s)%s", asctime(localtime(&longtime)), catalog_name);
    new_catalog = xpg_catalog_t::createCatalog(catName);
  };

#ifdef DEBUG
  xpg_catalog_t::display();
#endif


  fprintf(temp_header, "#ifndef %s\n", header_id);
  fprintf(temp_header, "#define %s\n", header_id);
  fprintf(temp_header, "/*******************************************************************/\n");
  fprintf(temp_header, "/*                                                                 */\n");
  fprintf(temp_header, "/*            Auto-generated header - DO NOT EDIT                  */\n");
  fprintf(temp_header, "/*                                                                 */\n");
  fprintf(temp_header, "/*******************************************************************/\n");
  fprintf(temp_header, "\n");

// Now parse the source-file and load messages...
#ifdef DEBUG
    fprintf(stderr, "Reading source...\n");
    fflush(stderr);
#endif

  while (fgets(line, sizeof(line), source) != NULL) {
    line_length = strlen(line);
    if (line[line_length-1] == '\n') line[--line_length] = 0;

// Handle continuation lines...
    while (line[line_length-1] == '\\') {
      if (fgets(&line[line_length-1], sizeof(line) - line_length, source) == NULL) break;
      line_length = strlen(line);
      if ((line_length > 0) && (line[line_length-1] == '\n')) 
        line[--line_length] = 0;
    };
    if ((line_length > 0) && (line[line_length-1] == '\n'))
      line[--line_length] = 0;

// Handle blank lines...
    if (line_length == 0) continue;
    for (i=0;i<line_length;i++) if (!isspace(line[i])) break;
    if (i >= line_length) continue;

// Handle comments...
    if ((line_length >= 2) && (line[0] == '$') && isspace(line[1])) {
      fprintf(temp_header, "/* %s */\n", &(line[2]));
      continue;
    };
    if ((line_length == 1) && (line[0] == '$')) continue;

// Handle $quote directives...
    if (strncmp(line, "$quote", 6) == 0) {
      for(i=6;(line[i]!=0) && !isspace(line[i]); i++);
      if (line[i] != 0) quoteChar = line[i];
      continue;
    };

// Handle set directives...
    if (strncmp(line, "$set", 4) == 0) {
      for(i=4;(line[i]!=0) && !isspace(line[i]); i++);
      if ((st = read_id(line, i, setId, currentSetName, true)) != 0) {
        PKI_OSSRV_fprintMsg(stderr, 
                            gencat_message_catalog, 
                            st,
                            "Error parsing $set identifier");
        return EXIT_FAILURE;
      };
#ifdef DEBUG
      fprintf(stderr, "Starting set %s\n", currentSetName);
      fflush(stderr);
#endif

      fprintf(temp_header, "/* Starting Set %s */\n", currentSetName);
      continue;
    };

// Handle unknown directives...
    if (line[0] == '$') {
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_UNKNOWN_DIRECTIVE,
                          "Unknown directive: %s",
                          line);
      return EXIT_FAILURE;
    };

// Handle message directives...
    if ((st = read_id(line,
                      i,
                      msgId,
                      currentMsgName, 
                      true)) != 0) {
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          st,
                          "Error parsing message identifier");
      return EXIT_FAILURE;
    };
#ifdef DEBUG
      fprintf(stderr, "Defining message %s\n", currentMsgName);
      fflush(stderr);
#endif
    if (line[i] == quoteChar) {
      i++;
      cptr = strrchr(&line[i], quoteChar);
      if (cptr) *cptr=0;
    };
    processLine(&(line[i]));
    if ((st = xpg_catalog_t::defineMessage(new_catalog,
                                           currentSetName,
                                           currentMsgName,
                                           &line[i],
                                           currentSet,
                                           currentMsg)) != 0) {
#ifdef DEBUG
      fprintf(stderr, "xpg_catalog_t::defineMessage returned %d\n", st);
      fprintf(stderr, "Checking for MSG_ALREADY_DEFINED (%lu)\n", XPG_MSG_ALREADY_DEFINED);
      fflush(stderr);
#endif
      if (st != XPG_MSG_ALREADY_DEFINED) {
        PKI_OSSRV_fprintMsg(stderr, 
                            gencat_message_catalog, 
                            st,
                            "Error defining message\n");
        return EXIT_FAILURE;
      } else {
#ifdef DEBUG
        fprintf(stderr, "Message already defined (ignored)\n");
        fflush(stderr);
#endif
      };
    };
#ifdef DEBUG
    fprintf(stderr, "Message defined: %lu\n", pkiMakeSts(currentSet, currentMsg));
    fflush(stderr);
#endif
    fprintf(temp_header, "#define %s %lu\n", currentMsgName, pkiMakeSts(currentSet, currentMsg));
  }


  fclose(source);
  fprintf(temp_header, "\n#endif\n");
  fclose(temp_header);


#ifdef DEBUG
  xpg_catalog_t::display();
#endif

#ifdef DEBUG
    fprintf(stderr, "Writing catalog...\n");
    fflush(stderr);
#endif

// If we get here, we've parsed the entire message file succesfully.
// We now just have to create the output files.
// First write out the in-memory catalog to the file...
  xpg_catalog_t::write(new_catalog, temp_catalog);
  fclose(temp_catalog);


#ifdef DEBUG
    fprintf(stderr, "Temporary files closed, copying to targets...\n");
    fflush(stderr);
#endif
  
// Now delete the old catalog, replacing it with the new one..
  remove(catalog_name);
  if (rename(temp_catalog_name, catalog_name)) {
// For some reason the rename failed.  Do it by a block-copy instead.
    FILE * src;
    FILE * dst;
    unsigned char block[2048];
    size_t chars;

    if ((src = fopen(temp_catalog_name, "rb")) == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Error opening temporary catalog for copy\n");
      fflush(stderr);
#endif
  
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_ERROR_COPYING_SRC,
                          "Error opening source catalog for copy");
      return EXIT_FAILURE;      
    };
    if ((dst = fopen(catalog_name, "wb")) == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Error opening target catalog for copy\n");
      fflush(stderr);
#endif
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_ERROR_COPYING_DST,
                          "Error opening target catalog for copy");
      return EXIT_FAILURE;      
    };
    while ((chars = fread(block, 1, sizeof(block), src)) > 0) 
      fwrite(block, 1, chars, dst);
    fclose(dst);
    fclose(src);
    remove(temp_catalog_name);
  };
  

// Now we should copy the temp header into the current header if they're different.  Do a block-by block comparison...
  {
    FILE * f1;
    FILE * f2;
    unsigned char block1[2048];
    unsigned char block2[sizeof(block1)];
    size_t chars1;
    size_t chars2;
    
    if ((f1 = fopen(temp_header_name, "rb")) == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Error opening temporary header for copy\n");
      fflush(stderr);
#endif
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_ERROR_COPYING_SRC,
                          "Error opening source header for copy");
      return EXIT_FAILURE;      
    };
    if ((f2 = fopen(header_name, "rb")) != NULL) {
      do {
        chars1 = fread(block1, 1, sizeof(block1), f1);
        chars2 = fread(block2, 1, sizeof(block2), f2);
      } while ((chars1 != 0) && (chars2 != 0) && (chars1 == chars2) && (memcmp(block1, block2, chars1) == 0));
      fclose(f2);
      fclose(f1);
      if ((chars1 == 0) && (chars2 == 0)) {
// The new file is the same as the current one, so leave the current one alone.  That
// way, we won't muck with its modified date, and won't cause an unnecessary re-build.
#ifdef DEBUG
      fprintf(stderr, "Previous version of header identical to new one.  Will leave old one alone\n");
      fflush(stderr);
#endif
        remove(temp_header_name);
        return EXIT_SUCCESS;
      };
// The files differed.  Rename the temp file to the output file...    
#ifdef DEBUG
      fprintf(stderr, "Previous version of header differs from new one.  Will replace\n");
      fflush(stderr);
#endif
    } else {
// No old copy of header.  Copy the temp one
#ifdef DEBUG
      fprintf(stderr, "Previous version of header not found.  Will copy temporary header\n");
      fflush(stderr);
#endif
      fclose(f1);
    };
  };
  remove(header_name);
  if (rename(temp_header_name, header_name)) {
// For some reason the rename failed.  Do it by a block-copy instead.
    FILE * src;
    FILE * dst;
    unsigned char block[2048];
    size_t chars;

    if ((src = fopen(temp_header_name, "rb")) == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Error opening temporary header for copy\n");
      fflush(stderr);
#endif
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_ERROR_COPYING_SRC,
                          "Error opening source header for copy");
      return EXIT_FAILURE;      
    };
    if ((dst = fopen(header_name, "wb")) == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Error opening target header for copy\n");
      fflush(stderr);
#endif
      PKI_OSSRV_fprintMsg(stderr, 
                          gencat_message_catalog, 
                          GENCAT_ERROR_COPYING_DST,
                          "Error opening target header for copy");
      return EXIT_FAILURE;      
    };
    while ((chars = fread(block, 1, sizeof(block), src)) > 0) 
      fwrite(block, 1, chars, dst);
    fclose(dst);
    fclose(src);
    remove(temp_header_name);
  };
#ifdef DEBUG
      fprintf(stderr, "Normal succesfull completion\n");
      fflush(stderr);
#endif

  return 0;
}

