/**************************************************************************
* Copyright  (c) 2001 by Acunia N.V. All rights reserved.                 *
*                                                                         *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other       *
* interests in the software remain the property of Acunia N.V. and its    *
* licensors, if any.                                                      *
*                                                                         *
* This software may only be used in accordance with the corresponding     *
* license agreement. Any unauthorized use, duplication, transmission,     *
*  distribution or disclosure of this software is expressly forbidden.    *
*                                                                         *
* This Copyright notice may not be removed or modified without prior      *
* written consent of Acunia N.V.                                          *
*                                                                         *
* Acunia N.V. reserves the right to modify this software without notice.  *
*                                                                         *
*   Acunia N.V.                                                           *
*   Vanden Tymplestraat 35      info@acunia.com                           *
*   3000 Leuven                 http://www.acunia.com                     *
*   Belgium - EUROPE                                                      *
**************************************************************************/

/*
** $Id: argument.c,v 1.9 2003/01/21 08:39:00 gray Exp $
*/

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <dirent.h>

#include "argument.h"

void print_usage(char * msg) {

  if (msg) {
    printf("%s\n", msg);
  }

  printf("Willy Wonka's jamjar tool, an enhanced jar replacement, version %s. Copyright ACUNIA 2001-2002.\n", VERSION);
  printf("Usage: jamjar {ctxuU}[vfmMS0] [jar] [manifest] [-C path file] [-Z path] [-x file] [-t file] files ...\n");
  printf("\n");
  printf("Switches:\n");
  printf("  'c' Create a new archive (can not be used with the 'u' option as jar refuses it. See 'U' option).\n");
  printf("  't' List table of contents of jarfile. If specified after [jar] as '-t <file> list only <file>'.\n");
  printf("  'x' Extract all files in jar. If specified after <jarfile> as '-x <file>' extract only <file>.\n");
  printf("  'u' Update an existing archive (can not be used together with 'c' as jar refuses. See 'U' option).\n");
  printf("  'U' Create the jarfile if it doesn't exist, otherwise update it (non compliant - cfr. 'ar cu').\n");
  printf("\n");
  printf("Modifiers:\n");
  printf("  'v' Be verbose. Can be given several times to increase verbosity.\n");
  printf("  'f' An explicit jarfile [jar] follows the switches/modifiers.\n");
  printf("  'm' Include [manifest]. If not specified with 'm', a manifest will be created automatically.\n");
  printf("  'M' Do not generate or include a manifest file.\n");
  printf("  '0' Store only, do not compress (faster).\n");
  printf("  'S' When a jarfile to be included is allready compressed, only store it (non compliant - faster).\n");
  printf(" '-C' <path> <file> Include <path>/<file> in the jar. Note that <file> may be a directory.\n");
  printf(" '-Z' <path> The files that follow on the command line, that are NOT class files, will be included\n");
  printf("      in the jarfile with <path>. I.e. strip the existing path from the file and use <path> instead\n");
  printf("      in the jarfile. The -Z modifier can be given any number of times and stays in effect until\n");
  printf("      the next -Z modifier or the end of the argument list is reached (non compliant).\n\n");
  printf("If any <file> is a directory, it will be recursively scanned and all files in it will be taken in\n");
  printf("the output jar file. If during the scanning other directories are found, these will also be scanned.\n");
  printf("\n");
  printf("NOTES: the manifest file name and the jarfile name need to be specified in the same order as\n");
  printf("       the 'm' and 'f' flags given on the argument line.\n");
  printf("       Because jamjar scans classes for the package name and has the -Z modifier, file names can be\n");
  printf("       given with absolute paths. This makes jamjar ideal to be used with jam, the make replacement.\n");
  printf("       Oh yes, jamjar will also sort the archive for easier listing. All META-INF files come first, than\n");
  printf("       come the directories contained in the archive, followed by individual files; all alpha sorted...\n");

  exit(1);

}

void arg_msg(j_arg arg, char * msg) {

  if (arg->msg == NULL) {
    arg->msg = jj_calloc(strlen(msg) + 1, sizeof(char));
    strcpy(arg->msg, msg);
  }

}

/*
** Check that the argument exists and if it is a directory, add the ARG_DIR
** flag to it.
*/

static void arg_check(j_arg arg) {

  struct stat fs;
  int status;

  status = stat(arg->file, &fs);
  if (status == 0) {
    if (S_ISREG(fs.st_mode)) {
      setFlag(arg->flags, ARG_EXISTS);
      arg->size = fs.st_size;
      arg->time = fs.st_mtime;
    }
    else if (S_ISDIR(fs.st_mode)) {
      setFlag(arg->flags, ARG_EXISTS | ARG_DIR);
    }
  }
    
}

void arg_free(j_arg arg) {

}

#define BUFSIZE (1024)
static char errbuff[BUFSIZE];

static void arg_error(char * msg, char * msg2) {

  memset(errbuff, 0x00, BUFSIZE);
  
  if (msg2 != NULL) {
    sprintf(errbuff, "error: %s '%s'\n", msg, msg2);
  }
  else {
    sprintf(errbuff, "error: %s\n", msg);
  }

  print_usage(errbuff);
  
}

static void attach_dir(j_arg arg, char * change_dir) {

  char * old;
  
  if (change_dir) {
    arg->dir = jj_calloc(strlen(change_dir) + 1, sizeof(char));
    strcpy(arg->dir, change_dir);

    old = arg->file;
    arg->file = jj_calloc(strlen(change_dir) + strlen(old) + 1, sizeof(char));
    strcpy(arg->file, change_dir);
    strcat(arg->file, old);
    jj_free(old);
  }
  
}

/*
** Extract the file name argument from fullname. This means stripping all './'
** components from the fullname argument and copying the resulting string
** to the arg->file field.
*/

static void set_file(j_arg arg, char * fullname) {

  char * name;
  size_t i = 0;
  
  while (fullname[i] == '.' && fullname[i + 1] == '/') {
    i += 2;
  }

  name = fullname + i;
  
  arg->file = jj_calloc(strlen(name) + 1, sizeof(char));
  strcpy(arg->file, name);

}

/*
** Set both file from a name component and a path component.
*/

static void set_file_path(j_arg arg, char * fullname, char * fullpath) {

  char * name;
  char * path;
  size_t i = 0;
  
  while (fullname[i] == '.' && fullname[i + 1] == '/') {
    i += 2;
  }

  name = fullname + i;

  while (fullpath[i] == '.' && fullpath[i + 1] == '/') {
    i += 2;
  }
  
  path = fullpath + i;
  
  arg->file = jj_calloc(strlen(name) + strlen(path) + 2, sizeof(char));
  strcat(arg->file, path);
  if (arg->file[strlen(path) - 1] != '/') {
    arg->file[strlen(path)] = '/';
  }
  strcat(arg->file, name);

}

/*
** For setting the z_name, we take the base component from 'file' and add the 'path'
** component.
*/

static void set_zname(j_arg arg, char * path, char * file) {

  char * base;
  char * location;
  int alloc_base = 0;
  
  location =  strrchr(file, '/');
  if (location) {
    location += 1;
    base = jj_calloc(strlen(file) - (location - file) + 1, sizeof(char));
    memcpy(base, location, strlen(file) - (location - file));
    alloc_base = 1;
  }
  else {
    base = file;
  }
  arg->z_name = jj_calloc(strlen(path) + strlen(base) + 2, sizeof(char));
  strcpy(arg->z_name, path);
  if (arg->z_name[strlen(path) - 1] != '/') {
    arg->z_name[strlen(path)] = '/';
  }
  strcat(arg->z_name, base);

  if (alloc_base) {
    jj_free(base);
  }

}


char * z_name_make(j_arg f) {

  char * location;
  char * z_name;
  char * strip = f->dir;
  
  if (strip) {
    location = strstr(f->file, strip);
    if (location) {
      z_name = jj_calloc(strlen(location) - strlen(strip) + 1, sizeof(char));
      strcpy(z_name, location + strlen(strip));
      return z_name;
    }
  }
  
  return f->file;
  
}

static char * strclone(const char * string) {

  char * clone;
  
  if (string) {
    clone = jj_calloc(strlen(string) + 1, sizeof(char));
    strcpy(clone, string);
    return clone;
  }
  
  return NULL;
  
}

static int dir_scanner(const struct dirent * de) {

  /*
  ** Don't take the current and parent directory.
  */
  
  if (strcmp(de->d_name, ".") == 0) {
    return 0;
  }

  if (strcmp(de->d_name, "..") == 0) {
    return 0;
  }

  /*
  ** We don't want to include our own output jar.
  */
  
  if (options->jar) {
    if (strcmp(de->d_name, options->jar->file) == 0) {
      return 0;
    }
  }

  logmsg(3, "will include '%s'\n", de->d_name);
  
  return 1;
  
}

/*
** Scan a directory to include it's files. The 'list' parameter
** it the list of arguments, the 'dir' parameter is the directory argument. After
** this function returns, it will have been removed and replaced by it's contents.
** We check if a found element is again a directory, but DONT process it, we just 
** report the number of thus found dirs back.
*/

int arg_dir(j_arg list, j_arg dir) {

  int dirs_found = 0;
  j_arg arg;
  struct stat fs;
  int status;
  DIR * dirp;
  struct dirent * de;

  list_remove(dir);

  dirp = opendir(dir->file);
  if (dirp == NULL) {
    logmsg(0, "could not scan dir '%s' (%s).\n", dir->file, strerror(errno));
  }
  else {
    while ( (de = readdir(dirp)) != NULL ) {
      if ( ! dir_scanner(de) ) {
         continue;
      }
      arg = jj_calloc(1, sizeof(j_Arg));
      set_file_path(arg, de->d_name, dir->file);
      list_insert(list, arg);
      arg->dir = strclone(dir->dir);
      status = stat(arg->file, &fs);
      if (status == 0) {
        if (S_ISDIR(fs.st_mode)) {
          setFlag(arg->flags, ARG_DIR);
          dirs_found += 1;
        }
      }
    }
  }
  closedir(dirp);

  arg_free(dir);
  
  return dirs_found;

}

void args_read(int argc, char * argv[]) {

  int i;
  j_Arg List;
  j_arg list = &List;
  j_arg arg;
  j_arg tmp;
  j_arg next;
  char * current;
  int f_seen = 0;
  int m_seen = 0;
  int start;
  char * change_dir;
  int dirs;

  if (argc < 2) {
    print_usage(NULL);
  }
  else if (strcmp(argv[1], "-h") == 0) {
    print_usage(NULL);
  }
  else if (strcmp(argv[1], "--help") == 0) {
    print_usage(NULL);
  }
  else if (strcmp(argv[1], "--h") == 0) {
    print_usage(NULL);
  }

  list_init(list);

  memset(options, 0x00, sizeof(j_Options));
  options->version = VERSION;
  options->verbosity = 0;

  /*
  ** First process the master options.
  */
  
  current = argv[1];
  for (i = 0; i < strlen(current); i++) {
    if (current[i] == 'c') {
      if (isSet(options->flags, OPT_UPDATE)) {
        arg_error("for jar compliance, 'c' together with 'u' fails. Try the 'U' option.", NULL);
      }
      if (isSet(options->flags, OPT_EXTRACT)) {
        arg_error("the 'c' option doesn't work with the 'x' option.\n", NULL);
      }
      setFlag(options->flags, OPT_CREATE);  // Create jarfile
    }
    else if (current[i] == 't') {
      setFlag(options->flags, OPT_LIST);    // Create a listing
    }
    else if (current[i] == 'v') {
      options->verbosity += 1;              // Increase verbosity
    }
    else if (current[i] == 'f') {
      f_seen = i;
    }
    else if (current[i] == 'm') {
      setFlag(options->flags, OPT_ARG_MAN); // Manifest file is passed as argument
      m_seen = i;
    }
    else if (current[i] == 'i') {
      setFlag(options->flags, OPT_INDEX);   // Create an index
    }
    else if (current[i] == '0') {
      setFlag(options->flags, OPT_STORE);   // Only store, don't compress
    }
    else if (current[i] == '-') {
      // Do nothing, just proceed with next
    }
    else if (current[i] == 'M') {
      setFlag(options->flags, OPT_NO_MAN);  // Don't create a manifest file
    }
    else if (current[i] == 'u') {
      if (isSet(options->flags, OPT_CREATE)) {
        arg_error("for jar compliance, 'c' together with 'u' fails. Try the 'U' option.", NULL);
      }
      if (isSet(options->flags, OPT_EXTRACT)) {
        arg_error("the 'u' option doesn't work with the 'x' option.\n", NULL);
      }
      setFlag(options->flags, OPT_UPDATE);  // Update only
    }
    else if (current[i] == 'U') {
      if (isSet(options->flags, OPT_EXTRACT)) {
        arg_error("the 'U' option doesn't work with the 'x' option.\n", NULL);
      }
      setFlag(options->flags, OPT_CREATE);
      setFlag(options->flags, OPT_UPDATE);
    }
    else if (current[i] == 'S') {
      setFlag(options->flags, OPT_STOREJARS);
    }
    else if (current[i] == 'x') {
      setFlag(options->flags, OPT_EXTRACT);
      if (isSet(options->flags, OPT_CREATE | OPT_UPDATE)) {
        arg_error("the 'x' option doesn't work with the 'c' or 'u' option.\n", NULL);
      }
    }
    else {
      arg_error("badly placed option or non existing option in", current);
    }
  }

  /*
  ** The following are files, possibly, the jarfile and/or manifest file.
  */

  start = 2;
  if (f_seen) {
    arg = jj_calloc(1, sizeof(j_Arg));
    list_insert(list, arg);
    set_file(arg, argv[start]);
    start += 1;
    if (m_seen) {
      tmp = arg;
      arg = jj_calloc(1, sizeof(j_Arg));
      list_insert(list, arg);
      set_file(arg, argv[start]);
      start += 1;
      if (f_seen > m_seen) {
        setFlag(tmp->flags, ARG_MANIFEST);
        setFlag(arg->flags, ARG_OUT_JAR);
      }
      else {
        setFlag(tmp->flags, ARG_OUT_JAR);
        setFlag(arg->flags, ARG_MANIFEST);
      }
    }
    else {
      setFlag(arg->flags, ARG_OUT_JAR);
    }
  }
  else if (m_seen) {
    arg = jj_calloc(1, sizeof(j_Arg));
    list_insert(list, arg);
    set_file(arg, argv[start]);
    start += 1;
    setFlag(arg->flags, ARG_MANIFEST);
  }
  else {
    options->out = 1; // No manifest file and output stream is standard output
  }

  if (start == argc) {
    if (isNotSet(options->flags, OPT_LIST | OPT_EXTRACT)) {
      print_usage("Not enough arguments to proceed.");
    }
  }

  /*
  ** From this point, we have the options -C <dir>, x <file>, t <file> or just <file>
  */

  change_dir = NULL;
  for (i = start; i < argc; i++) {
    current = argv[i];

    if (current[0] == '-' && current[1] == 'C') {
      if (i + 1 == argc) {
        arg_error("the 'C' option needs a dir argument.", NULL);
      }
      i += 1;
      change_dir = argv[i];
    }
    else if (current[0] == '-' && current[1] == 'Z') {
      if (i + 1 == argc) {
        arg_error("the 'Z' option needs a path argument.", NULL);
      }
      i += 1;
      arg = jj_calloc(1, sizeof(j_Arg));
      list_insert(list, arg);
      set_file(arg, argv[i]);
      setFlag(arg->flags, ARG_ZPATH);
    }
    else if (current[0] == 'x' && strlen(current) == 1) {
      if (i + 1 == argc) {
        arg_error("the 'x' option at this position needs a file argument.", NULL);
      }
      i += 1;
      arg = jj_calloc(1, sizeof(j_Arg));
      list_insert(list, arg);
      set_file(arg, argv[i]);
      setFlag(arg->flags, ARG_EXTRACT);
    }
    else if (current[0] == 't' && strlen(current) == 1) {
      if (i + 1 == argc) {
        arg_error("the 't' option needs a file argument.", NULL);
      }
      i += 1;
      arg = jj_calloc(1, sizeof(j_Arg));
      list_insert(list, arg);
      set_file(arg, argv[i]);
      setFlag(arg->flags, ARG_LIST);
    }
    else {
      arg = jj_calloc(1, sizeof(j_Arg));
      list_insert(list, arg);
      set_file(arg, argv[i]);
      attach_dir(arg, change_dir);
    }

  }


  /*
  ** Attach the correct options->jar argument and check if it exists.
  */

  setFlag(options->flags, OPT_STDOUT);
  for (arg = list->next; arg != list; arg = arg->next) {
    if (isSet(arg->flags, ARG_OUT_JAR)) {
      list_remove(arg);
      options->jar = arg;
      arg_check(arg);
      if (isNotSet(arg->flags, ARG_EXISTS)) {
        if (isNotSet(options->flags, OPT_CREATE)) {
          logmsg(0, "can not open jar file '%s' (%s)\n", arg->file, strerror(errno));
        }
      }
      unsetFlag(options->flags, OPT_STDOUT);
      break;
    }
  }

  /*
  ** Attach the correct options->manifest argument and check if it exists. Also attach the
  ** correct z_name since the manifest file given on the command line can have whatever name.
  */

  for (arg = list->next; arg != list; arg = arg->next) {
    if (isSet(arg->flags, ARG_MANIFEST)) {
      list_remove(arg);
      arg_check(arg);
      options->manifest = arg;
      arg->z_name = strclone("META-INF/MANIFEST.MF");
      if (isNotSet(arg->flags, ARG_EXISTS)) {
        logmsg(0, "can not open manifest file '%s' (%s)\n", arg->file, strerror(errno));
      }
      break;
    }
  }

  /*
  ** Check if any file is a directory and set the flag if so, also check if the file or 
  ** directory exists.
  */

  for (arg = list->next; arg != list; arg = arg->next) {
    arg_check(arg);
  }

  /*
  ** Recursively process directory arguments.
  */

  dirs = 0;
  do {
    for (arg = list->next; arg != list; arg = arg->next) {
      if (isSet(arg->flags, ARG_DIR)) {
        logmsg(7, "searching for files in '%s'\n", arg->file);
        dirs = arg_dir(list, arg);
      }
    }
  } while (dirs);

  /*
  ** Attach the ARG_CLASS flag to class files.
  */
  
  for (arg = list->next; arg != list; arg = arg->next) {
    if (strcmp(arg->file + strlen(arg->file) - 6, ".class") == 0) {
      setFlag(arg->flags, ARG_CLASS);
    }
  }

  /*
  ** Process all zpath components. When we encounter an ARG_ZPATH element, we remove it
  ** from the list and make sure that all following arguments have their zpath component
  ** updated. We do this for as long as we encounter the next ARG_ZPATH component or the end
  ** of the list BUT only for NON class arguments. Class file arguments will get the proper
  ** z_name from parsing the class file.
  */

  for (arg = list->next; arg != list; arg = arg->next) {
    if (isSet(arg->flags, ARG_ZPATH)) {
      tmp = arg;
      arg = tmp->next;
      list_remove(tmp);
      while (arg != list && isNotSet(arg->flags, ARG_ZPATH)) {
        if (isNotSet(arg->flags, ARG_CLASS)) {
          set_zname(arg, tmp->file, arg->file);
        }
        arg = arg->next;
      }
      arg = arg->previous;
      arg_free(tmp);
    }
  }

  /*
  ** Find other archives to include and set the z_name field if not yet set.
  */

  for (arg = list->next; arg != list; arg = arg->next) {
    current = strstr(arg->file, ".jar");
    if (current == NULL) {
      current = strstr(arg->file, ".ear");
    }
    if (current == NULL) {
      current = strstr(arg->file, ".war");
    }
    if (current == NULL) {
      current = strstr(arg->file, ".car");
    }
    if (current) {
      setFlag(arg->flags, ARG_IN_JAR);
    }
    
    if (! arg->z_name) {
      arg->z_name = z_name_make(arg);
    }
  }

  /*
  ** Throw away all arguments that were not found.
  */

  for (arg = list->next; arg != list; arg = next) {
    next = arg->next;
    arg_check(arg);
    if (isNotSet(arg->flags, ARG_EXISTS)) {
      list_remove(arg);
      arg_free(arg);
    }
  }

  /*
  ** Break the circular list into a non circular list.
  */
  
  if (list->next != list) {
    list_break(list, arg);
    options->args = arg;
  }

  if (options->verbosity > 10) {
    options->verbosity = 10;
  }

}
