/* $Id: initdb.c,v 1.18 2002/06/23 20:28:30 richdawe Exp $ */

/*
 *  initdb.c - Database initialisation routines for pakke
 *  Copyright (C) 1999-2002 by Richard Dawe
 *      
 *  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.
 */

#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <limits.h>

/* libpakke includes */
#include <libpakke/package.h>
#include <libpakke/packlist.h>
#include <libpakke/util.h>

#include "pakke.h"
#include "rcfile.h"
#include "platform.h"
#include "initdb.h"

/* Default DSMs for different platforms */
extern char *platform_dsm_table[];
extern char *platform_dsm_name_table[];

extern char pakkerc_default[];     /* Default pakkerc configuration file */
extern char simtelrc_default[];    /* Default simtelrc configuration file */
extern char pakke_dsm[];           /* DSM for pakke */
extern char pakke_dsm_filename[];  /* Filename for DSM for pakke */

typedef struct {
  platform_t platform;
  /* int major_version;
     int minor_version; */
  char dsm_name[PACKAGE_MAXNAMELEN]; /* DSM name for this platform */
  char name[PACKAGE_MAXNAMELEN];     /* More readable name */
} platform_mapping_t;

platform_mapping_t platform_mapping_table[]={
  { PLATFORM_NONE,   "none",   "None"         },
  { PLATFORM_MSDOS,  "msdos",  "MS-DOS"       },
  { PLATFORM_WIN3,   "win3",   "Windows 3"    },
  { PLATFORM_WIN95,  "win95",  "Windows '95"  },
  { PLATFORM_WIN98,  "win98",  "Windows '98"  },
  { PLATFORM_WINME,  "winme",  "Windows ME"   },
  { PLATFORM_WINNT4, "winnt4", "Windows NT 4" },
  { PLATFORM_WIN2K,  "win2k",  "Windows 2000" },
  { PLATFORM_WINXP,  "winxp",  "Windows XP"   },
  { PLATFORM_DOSEMU, "dosemu", "dosemu"       },
  { 0,               "",       ""             }
};	  

/* -----------------------
 * - recursive_copy_file -
 * ----------------------- */

/*
 * Do a recursive copy of 'src' to 'dst'. Don't copy files or directories
 * that are listed in 'ignore_files'. If either of 'src' or 'dst' match
 * 'ignore_files', then fail. On success return 1, else 0.
 */

static int
recursive_copy_file (const char *src,
		     const char *dst,
		     const char **ignore_files)
{
  int i;

  /* Check that 'src' and 'dst' don't match 'ignore_files'. */
  for (i = 0; (ignore_files != NULL) && (ignore_files[i] != NULL); i++) {
    if (   (strcmp(src, ignore_files[i]) == 0)
	|| (strcmp(dst, ignore_files[i]) == 0))
      return(0);
  }

  /* Single file, i.e.: non-recursive. */
  if (!isdir(src))
    return(copy_file(src, dst));

  /* TODO: Copy 'em */

  return(0);
}

/* ------------------
 * - perform_initdb -
 * ------------------ */

/* TODO: Create manifest directory */

int
perform_initdb (const PAKKE_INITDB *req)
{
  FILE *fp = NULL;
  char db_path[PATH_MAX], db_avail_path[PATH_MAX], share_path[PATH_MAX];
  char packages_path[PATH_MAX], manifest_path[PATH_MAX];
  char path[PATH_MAX];
  int i, j, found, ret;
  mode_t mode;

  /* for the new os detection and adding of the platform to the
   * req->platforms array */
  platform_t   detected_platform;
  char        *detected_platform_dsm_name = NULL;
  char        *detected_platform_name     = NULL;
  char       **platforms = NULL;
  int          detected_platform_already_specified = 0;
  int          n_platforms;

  if (req->with_helpers)
    assert(req->pakke != NULL); /* pakke.c should not let this happen! */

  /* Construct share, db, db-avail and manifest paths. */
  strcpy(share_path, req->root);
  addforwardslash(share_path);
  strcat(share_path, PAKKE_SHARE_PREFIX);
  addforwardslash(share_path);

  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  strcpy(db_avail_path, req->root);
  addforwardslash(db_avail_path);
  strcat(db_avail_path, PAKKE_DB_AVAIL_PREFIX);
  addforwardslash(db_avail_path);

  strcpy(packages_path, req->root);
  addforwardslash(packages_path);
  strcat(packages_path, PAKKE_DOWNLOAD_PREFIX);
  addforwardslash(packages_path);

  strcpy(manifest_path, req->root);
  addforwardslash(manifest_path);
  strcat(manifest_path, "manifest");
  addforwardslash(manifest_path);

  /* Create the root & share/pakke/db directories */
  strcpy(path, share_path);
  printf(". Creating database structure in '%s'...", path);

  mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
  ret =               recursive_mkdir(path, mode);
  ret = (ret == 0) && recursive_mkdir(db_path, mode);
  ret = (ret == 0) && recursive_mkdir(db_avail_path, mode);
  ret = (ret == 0) && recursive_mkdir(packages_path, mode);
  ret = (ret == 0) && recursive_mkdir(manifest_path, mode);

  if ((ret != 0) && (errno != EEXIST)) {
    printf("\n");
    perror("pakke");
    return(EXIT_FAILURE);
  }

  printf("done\n");

  /* Platform detection code added by Kalum (thanks!). */
  detected_platform = detect_platform();

  /* Display the OS detected */
  detected_platform_name = platform_mapping_table[detected_platform].name;

  detected_platform_dsm_name
    = platform_mapping_table[detected_platform].dsm_name;

  printf(". Detected platform: %s\n", detected_platform_name);

  /*
   * Make a copy of the current platform list and the detected platform
   * to it.
   */
  for (n_platforms = i = 0, detected_platform_already_specified = 0;
       (req->platforms != NULL) && (req->platforms[i] != NULL);
       n_platforms++, i++) {
    /* find whether the detected platform has already been
     * specified by the user */
    if (strcmp(req->platforms[i], detected_platform_dsm_name) == 0) {
      /* No need to do anything - set 'already specified' flag, so
       * that the platform is not added twice to the platform list. */
      detected_platform_already_specified = 1;
    }     
  }

  if (!detected_platform_already_specified)
    n_platforms++;

  platforms = malloc(sizeof(char *) * (n_platforms + 1));
  if (platforms == NULL) {
    warnf("Unable to allocate memory - %s:%d", __FILE__, __LINE__);
    return(EXIT_FAILURE);
  }

  /* Copy exisiting platform list. */
#define FREE_STRINGS(x) \
  if (x != NULL) { \
    for (i = 0; (x)[i] != NULL; i++) { \
      free((x)[i]); \
    } \
    free(x); \
  }

  for (i = 0; (req->platforms != NULL) && (req->platforms[i] != NULL); i++) {
    platforms[i] = strdup(req->platforms[i]);
    if (platforms[i] == NULL) {
      FREE_STRINGS(platforms);

      warnf("Unable to allocate memory - %s:%d", __FILE__, __LINE__);
      return(EXIT_FAILURE);
    }
  }

  if (!detected_platform_already_specified) {
    platforms[i] = strdup(detected_platform_dsm_name);
    if (platforms[i] == NULL) {
      FREE_STRINGS(platforms);

      warnf("Unable to allocate memory - %s:%d", __FILE__, __LINE__);
      return(EXIT_FAILURE);
    }

    platforms[i + 1] = NULL;
  } else {
    platforms[i] = NULL;
  }

#undef FREE_STRINGS

  /* Create any platform DSMs necessary. */
  if (platforms != NULL)
    for (i = 0; platforms[i] != NULL; i++) {
      /* Find the DSM name in the table. */
      for (found = -1, j = 0; platform_dsm_name_table[j] != NULL; j++) {
	if (strcmp(platforms[i], platform_dsm_name_table[j]) == 0) {
	  found = j;
	  break;
	}
      }

      if (found == -1) {
	warnf("Unable to find built-in DSM for platform '%s'!", platforms[i]);
	continue;
      }

      /* Create a DSM file. */
      strcpy(path, db_path);
      strcat(path, platforms[i]);
      strcat(path, ".dsm");
      printf(". Creating plaform DSM '%s'...", path);

      fp = fopen(path, "wt");
      if (fp == NULL) {
	printf("\n");
	warnf("Unable to write DSM for platform '%s'!", platforms[i]);
	continue;
      }
      fputs(platform_dsm_table[found], fp);
      fclose(fp);
      printf("done\n");
    }

  /* - Create a default pakkerc, if necessary. - */

  /* Check for a configuration file. */
  strcpy(path, share_path);
  strcat(path, RCFILE_DEFAULT);

  if (access(path, R_OK) != 0) {
    /* Need to create default configuration file. */
    printf(". Creating default configuration file '%s'...",
	   path);

    fp = fopen(path, "wt");
    if (fp == NULL) {
      printf("\n");
      /* TODO: Should this tidy up first? */
      dief("Unable to create configuration file '%s'!\n", path);
    }

    fputs(pakkerc_default, fp);
    fclose(fp);
    printf("done\n");
  } else {
    infof("Configuration file '%s' found & kept!", path);
  }

  /* - Create a default simtelrc, if necessary. - */

  /* Check for a configuration file. */
  strcpy(path, share_path);
  strcat(path, "simtelrc"); /* TODO: Put this in a constant somewhere. */

  if (access(path, R_OK) != 0) {
    /* Need to create default configuration file. */
    printf(". Creating default configuration file '%s'...", path);

    fp = fopen(path, "wt");
    if (fp == NULL) {
      printf("\n");
      /* TODO: Should this tidy up first? */
      dief("Unable to create configuration file '%s'!\n", path);
    }

    fputs(simtelrc_default, fp);
    fclose(fp);
    printf("done\n");
  } else {
    infof("Configuration file '%s' found & kept!", path);
  }

  /* Copy the pakke binary and DSM, if necessary. */
  if (req->pakke != NULL) {
    /* Copy the binary */
    strcpy(path, req->root);
    strcat(path, "bin/");
	
    ret = recursive_mkdir(path, mode);

    if ((ret != 0) && (errno != EEXIST)) {
      warnf("Unable to create program directory '%s'", path);
      perror("pakke");
      return(EXIT_FAILURE);
    }

#ifdef __DJGPP__
    strcat(path, "pakke.exe");
#else
    strcat(path, "pakke");
#endif

    if (strcmp(req->pakke, path) == 0) {
      printf(". Cannot install pakke binary '%s' onto itself - skipping\n",
	     path);
    } else {
      if (!copy_file(req->pakke, path)) {
	warnf("Unable to copy pakke binary '%s' -> '%s'", req->pakke, path);
	return(EXIT_FAILURE);
      } else {
	printf(". Copied pakke binary: '%s' -> '%s'\n",
	       req->pakke, path);
      }
    }

    /* Install the DSM */
    strcpy(path, db_path);
    strcat(path, pakke_dsm_filename);

    printf(". Installing pakke's DSM '%s'...", path);

    fp = fopen(path, "wt");
    if (fp == NULL) {
      printf("\n");
      /* TODO: Should this tidy up first? */
      dief("Unable to create pakke's DSM '%s'!\n", path);
    }

    fputs(pakke_dsm, fp);
    fclose(fp);
    printf("done\n");
  }

  if (req->with_helpers) {
    /* Copy the helpers too */
    char src_path[PATH_MAX];
    char helper_path[PATH_MAX];
    char *p = NULL;

    const char *helper_prefixes[] = {
      PAKKE_DOS_PREFIX,
      PAKKE_WIN16_PREFIX,
      PAKKE_WIN32_PREFIX,
      NULL
    };

    /* Ignore these files, when copying. */
    const char *ignore_files[] = { "CVS", "cvs", NULL };

    /* - Deduce the source share/pakke/ path. - */
    strcpy(src_path, req->pakke);

    /* Strip off the pakke executable's name. */
    p = strrchr(src_path, '/');
    if (p == NULL) {
      /* Relative path? */
      src_path[0] = '\0';
    } else {
      p++;
      *p = '\0';
    }
    forwardslashify(src_path);

    /* Check that the share directory exists. Use 'helper_path'
     * temporarily. */
    strcpy(helper_path, src_path);
    strcat(helper_path, PAKKE_SHARE_PREFIX);

    if (!isdir(helper_path))
      dief("Cannot find pakke share directory, to copy helpers: '%s'!",
	   helper_path);

    /* Now copy the helpers, one by one. */
    for (i = 0; helper_prefixes[i] != NULL; i++) {
      strcpy(helper_path, src_path);
      strcat(helper_path, helper_prefixes[i]);

      /* Strip off the trailing slash, if any. */
      if (helper_path[strlen(helper_path) - 1] == '/')
	helper_path[strlen(helper_path) - 1] = '\0';

      if (!isdir(helper_path)) {
	warnf("Helper directory '%s' does not exist - SKIPPING",
	      helper_path);
	continue;
      }

      /* Do a recursive copy into the target share directory. */
      if (!recursive_copy_file(helper_path, share_path, ignore_files))
	dief("Unable to copy '%s' to '%s'!", helper_path, share_path);
    }
  }

  return(EXIT_SUCCESS);
}
