
/***************************************************************************
 *            tools.c
 *
 *  Mon Apr 18 15:00:13 2005
 *  Copyright  2005  Benjamin Ducke
 ****************************************************************************/

/*
 *  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 Library 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "globals.h"


/* the following are for cross-platform compatibility. They may already exist
   on a Linux or BSD system, but maybe absent e.g. in Win32, so we will define
   them here. */


char *basename(char *path)
{
    char *copy;
    char *element;
    char *backup;

    copy = strdup(path);

    backup = NULL;
    element = strtok(copy, "/");
    if (element == NULL) {
	if (copy != NULL) {
	    free(copy);
	}
	return (NULL);
    }

    backup = strdup(element);
    while (element != NULL) {
	element = strtok(NULL, "/");
	if ((backup != NULL) && (element != NULL)) {
	    free(backup);
	}
	if ((element != NULL) && (*element != '\0')) {
	    backup = strdup(element);
	}
    }

    if (copy != NULL) {
	free(copy);
    }

    return (backup);
}


/*      A shell version of mkdir () 
   needed because the MINGW one does no accept unix style MOD specifications.
   THIS DOES NOT CHECK FOR ERRORS !
 */
void mkdir_s(char *pathname, char *mode)
{
    char tmp[5000];

    sprintf(tmp, "mkdir %s --mode=%s -p", pathname, mode);
    system(tmp);

}

/* 
   Removes all dangling white-spaces and EOL chars from the end of a string.
   Returns total number of chopped-off characters.
 */
int chop(char *string)
{
    int i;
    int chopped;
    int stop = 0;

    chopped = 0;
    i = strlen(string) - 1;
    while (i >= 0) {
	stop = 1;
	if ((string[i] == '\n') || (string[i] == '\t') ||
	    (string[i] == ' ') || (string[i] == '\f') ||
	    (string[i] == '\r')) {
	    chopped++;
	    stop = 0;
	}
	if (stop == 1) {
	    /* got a non white-space char: stop chopping! */
	    break;
	}
	i--;
    }

    /* chop string */
    string[strlen(string) - chopped] = '\0';

    return (chopped);
}


/* 
   Inserts a string into an array of n strings; positions start at 0
   The size of the array will be increased by one char*. 
   Returns new size of array.
   String array must be NULL-terminated.
   There must be at least one slots left after the NULL-terminator to
   hold the new string! This function will not resize the string
   array to accomodate the new string!
 */
int insert_str(char *str, int pos, char **strarr)
{
    char save[MAXSTR];
    char insert[MAXSTR];
    char last[MAXSTR];
    int n, j;
    int len;

    /* check for valid pos */
    n = 0;
    while (strarr[n] != NULL) {
	n++;
    }

    if ((pos < 0) || (pos > (n))) {
	print_error(ERR_REGISTER_ENTRIES_GISMAN,
		    "insert: invalid line number %i.\n", pos);
    }

    /* if a new string is to be added to the end of the array: */
    if (pos == n) {
	len = (1 + strlen(str)) * sizeof(char);
	strarr[n] = malloc(len);
	strcpy(strarr[n], str);
	n = n + 2;
	strarr[n - 1] = NULL;
	return (n);
    }

    strcpy(last, strarr[n - 1]);
    strcpy(insert, strarr[pos]);
    free(strarr[pos]);
    strarr[pos] = malloc((1 + strlen(str)) * sizeof(char));
    strcpy(strarr[pos], str);
    /* now move all strings > pos up one */
    for (j = pos; j < n - 1; j++) {
	strcpy(save, strarr[j + 1]);	/* save string to be overwritten */
	free(strarr[j + 1]);	/* overwrite string */
	len = (1 + strlen(insert)) * sizeof(char);	/* make room for string to be inserted */
	strarr[j + 1] = malloc(len);
	strcpy(strarr[j + 1], insert);	/* insert string */

	strcpy(insert, save);	/* set saved string to next to be inserted */

    }

    /* overwrite NULL terminator with last item */
    strarr[n] = malloc((1 + strlen(last)) * sizeof(char));
    strcpy(strarr[n], last);

    /* increase size of array by one */
    n = n + 2;
    strarr[n - 1] = NULL;	/* set last element of array to NULL */

    return (n);
}


/* 
   Delete a string at position pos (o to n) from an array of n strings; 
   positions start at 0
   The size of the array will be decreased by one char*.
   Returns new size of array.
   String array must be NULL-terminated.
 */
int delete_str(int pos, char **strarr)
{
    int i;

    /* check for valid pos */
    i = 0;
    while (strarr[i] != NULL) {
	i++;
    }

    if ((pos < 0) || (pos > (i))) {
	print_error(ERR_REGISTER_ENTRIES_GISMAN,
		    "delete: invalid line number %i.\n", pos);
    }

    /* now move all strings > pos down one */
    i = pos;
    while (strarr[i] != NULL) {
	free(strarr[i]);
	if (strarr[i + 1] != NULL) {
	    strarr[i] = malloc((1 + (strlen(strarr[i + 1]))) * sizeof(char));
	    strcpy(strarr[i], strarr[i + 1]);
	}
	i++;
    }

    /* decrease size of array by one */
    i = i - 1;
    strarr[i] = NULL;		/* set last element of array to NULL */

    return (i);
}


 /*
    Returns the first line number in which *str is found.
    Search starts after line number 'start'.
    Returns -1 if string not found.
    String array must be NULL-terminated.
  */
int find_pos(char *str, char **strarr, int start)
{
    int i, j;

    /* check for valid pos */
    i = 0;
    while (strarr[i] != NULL) {
	i++;
    }

    if ((start < 0) || (start > (i))) {
	exit(ERR_REGISTER_ENTRIES_GISMAN);
    }

    for (j = start; j < (i); j++) {
	if (strstr(strarr[j], str) != NULL) {
	    return (j);
	}
    }

    return (-1);
}


 /*
    Dumps an array of strings to a file.
    String array must be NULL-terminated.
  */
void dump_str(FILE * f, char **strarr)
{
    int i;

    i = 0;
    while (strarr[i] != NULL) {
	fprintf(f, "%i: %s", i, strarr[i]);
	i++;
    }
}

/*
   Get package name. Copies the package name as found in
   the 'name' info file into the char array *name.
 */
void get_package_name(char *path, char *name)
{
    FILE *f;
    char file[MAXSTR];
    char tmp[MAXSTR];

    sprintf(file, "%s/%s", path, "name");

    /* get extension name */
    f = fopen(file, "r");
    if (f == NULL) {
	print_error(ERR_INVALID_EXT, "'name' file not readable.\n");
    }
    else {
	if (nc_fgets_nb(tmp, MAXSTR, f) == NULL) {
	    fclose(f);
	    print_error(ERR_INVALID_EXT,
			"invalid or missing extension name.\n");
	}
	else {
	    chop(tmp);
	    strcpy(name, tmp);
	}
    }
    fclose(f);
}

/*
   A replacement function for fgets to filter out comments and blank lines.
   Useful for parsing settings files.
   Returns a line from a file only if it does not start with '#'.
   Returns only the part of the line left of '#'.
   Otherwise, tries to read the next line from the file or returns NULL on EOF.
 */
char *nc_fgets(char *s, int size, FILE * stream)
{
    char *hashmark;
    char *tmp;

    if (fgets(s, size, stream) == NULL) {
	return (NULL);
    }

    hashmark = strchr(s, '#');

    if (hashmark != NULL) {
	if (s - hashmark == 0) {
	    /* line starts with a hashmark: recursively call nc_fgets() */
	    return (nc_fgets(s, size, stream));
	}
	else {
	    /* return only the part before '#' */
	    tmp = malloc(sizeof(char) * MAXSTR);
	    strcpy(tmp, s);
	    tmp = strtok(tmp, "#");
	    sprintf(s, "%s\n", tmp);
	    free(tmp);
	}
    }
    return (s);
}


/*
   Same as nc_fgets (). Additionally, this filters for HTML tags.
 */
char *nc_fgets_html(char *s, int size, FILE * stream)
{
    char *hashmark;
    char *tmp;
    char *tag;
    char *tag_2;
    char *tag_insert;
    char *tag_content;
    char *pos;
    char *insert;
    int space;

    if (fgets(s, size, stream) == NULL) {
	return (NULL);
    }

    /* look for HTML tags: this discards all text inside the tags except for:
       <br> becomes \n
       <p> becomes \n\n
     */
    tmp = malloc(sizeof(char) * (strlen(s) + 1));
    tag_content = malloc(sizeof(char) * (strlen(s) + 1));

    insert = tmp;
    pos = s;

    while (*pos != '\0') {
	if (*pos == '<') {	/* possibly an html open tag */
	    tag = pos;
	    tag_insert = tag_content;
	    pos--;
	    if (pos >= s) {
		if (*pos == 32) {
		    space = 1;
		}
		else {
		    space = 0;
		}
	    }
	    while (*tag != '\0') {
		*tag_insert = *tag;
		(*tag_insert)++;
		if (*tag == '>') {	/* OK, we got a tag */
		    *tag_insert = '\0';

		    /* only add additional newlines, if this is not the end of the line already! */
		    tag_2 = tag;
		    tag_2++;
		    if (*tag_2 != '\n') {
			if (strstr(tag_content, "<br>") != NULL) {
			    /* a <br> at the start of a line produce no additional linefeed! */
			    if (insert > tmp) {
				*insert = '\n';
				(*insert)++;
			    }
			}
			if (strstr(tag_content, "<BR>") != NULL) {
			    if (insert > tmp) {
				*insert = '\n';
				(*insert)++;
			    }
			}
			if (strstr(tag_content, "<p>") != NULL) {
			    if (insert > tmp) {
				*insert = '\n';
				(*insert)++;
			    }
			    *insert = '\n';
			    (*insert)++;
			}
			if (strstr(tag_content, "<P>") != NULL) {
			    if (insert > tmp) {
				*insert = '\n';
				(*insert)++;
			    }
			    *insert = '\n';
			    (*insert)++;
			}
		    }

		    pos = tag;	/* skip this */

		    /* if the next character is a space and there was none
		       before the tag: skip that, too 
		     */
		    if (*pos == 32) {
			if (space == 1) {
			    pos++;
			    space = 0;
			}
		    }
		    break;
		}
		tag++;
	    }
	}
	if (*pos != '>') {
	    *insert = *pos;
	    insert++;
	}
	pos++;
    }

    *insert = '\0';

    strcpy(s, tmp);

    free(tmp);
    free(tag_content);

    hashmark = strchr(s, '#');

    if (hashmark != NULL) {
	if (s - hashmark == 0) {
	    /* line starts with a hashmark: recursively call nc_fgets_html() */
	    return (nc_fgets_html(s, size, stream));
	}
	else {
	    /* return only the part before '#' */
	    tmp = malloc(sizeof(char) * MAXSTR);
	    strcpy(tmp, s);
	    tmp = strtok(tmp, "#");
	    sprintf(s, "%s\n", tmp);
	    free(tmp);
	}
    }

    return (s);
}


/*
   Checks whether a string actually contains text or is a blank line/whitespace only.
   Returns 1 if text , 0 if only blank/whitespace.
 */
int is_text(char *s)
{
    int i;
    int nonws;

    /* check for a blank or white-space only line */
    nonws = 0;
    for (i = strlen(s) - 1; i >= 0; i--) {
	if ((s[i] == ' ') || (s[i] == '\t') || (s[i] == '\n') ||
	    (s[i] == '\f') || (s[i] == '\r')) {
	    nonws = 0;
	}
	else {
	    nonws = 1;
	    break;		/* break at first non-ws char! */
	}
    }

    return (nonws);
}


/*
   Same as nc_fgets() but also skips over blank lines and those that contain only 
   whitespace chars.
 */
char *nc_fgets_nb(char *s, int size, FILE * stream)
{
    char *hashmark;
    char *tmp;

    if (fgets(s, size, stream) == NULL) {
	return (NULL);
    }


    if (is_text(s) == 0) {
	/* line is ws only: recursively call nc_fgets() to get next line */
	return (nc_fgets_nb(s, size, stream));
    }

    hashmark = strchr(s, '#');

    if (hashmark != NULL) {
	if (s - hashmark == 0) {
	    /* line starts with a hashmark: recursively call nc_fgets() */
	    return (nc_fgets_nb(s, size, stream));
	}
	else {
	    /* return only the part before '#' */
	    tmp = malloc(sizeof(char) * MAXSTR);
	    strcpy(tmp, s);
	    tmp = strtok(tmp, "#");
	    sprintf(s, "%s\n", tmp);
	    free(tmp);
	}
    }
    return (s);
}


/*
   This just dumps an ASCII file to stdout, line by line, but
   skips over comments.
 */
void dump_ascii(char *file, char *heading)
{
    char tmp[MAXSTR];
    FILE *f;

    fprintf(stdout, "%s\n", heading);
    f = fopen(file, "r");
    if (f == NULL) {
	fprintf(stdout, "  No information available.\n");
    }
    else {
	while (nc_fgets_html(tmp, MAXSTR, f) != NULL) {
	    fprintf(stdout, "  %s", tmp);
	}
	fprintf(stdout, "\n");
	fclose(f);
    }
}


/* 
   Dumps the contents of an ASCII file without comment lines but with blank lines
   to a temporary file.
 */
void dump_plain(char *file, char *tmpfile)
{
    char tmp[MAXSTR];
    FILE *f_in;
    FILE *f_out;

    /* create a temporary menu.tcl file for write access */
    /* TODO: Do not hardcode temp path */
    strcpy(tmpfile, "/tmp/grass.extensions.db.XXXXXX");	/* TMP_GISMAN is a global variable */
    mkstemp(tmpfile);

    f_out = fopen(tmpfile, "w+");
    if (f_out == NULL) {
	print_error(ERR_DUMP_PLAIN_TXT,
		    "could not create temp file '%s': %s\n \
		Make sure that directory /tmp exists on your system and you have write permission.\n", tmpfile, strerror(errno));
    }

    atexit(&exit_db);		/* now need to register an at exit func to remove tmpfile automatically! */

    f_in = fopen(file, "r");
    while (nc_fgets(tmp, MAXSTR, f_in) != NULL) {
	fprintf(f_out, tmp);
    }

    fclose(f_in);
    fclose(f_out);
}


/* 
   Same as dump_plain() but adds a <br> after each newline in the stream.
   Also replaces blank lines with a <p>.
 */
void dump_html(char *file, char *tmpfile)
{
    char tmp[MAXSTR];
    char line[MAXSTR];
    FILE *f_in;
    FILE *f_out;
    int fd;

    /* create a temporary menu.tcl file for write access */
    /* TODO: Do not hardcode temp path */
    strcpy(tmpfile, "/tmp/grass.extensions.db.XXXXXX");	/* TMP_GISMAN is a global variable */
    mkstemp(tmpfile);

    f_out = fopen(tmpfile, "w+");
    if (f_out == NULL) {
	print_error(ERR_DUMP_PLAIN_TXT,
		    "could not create temp file '%s': %s\n \
		Make sure that directory /tmp exists on your system and you have write permission.\n", tmpfile, strerror(errno));
    }

    atexit(&exit_db);		/* now need to register an at exit func to remove tmpfile automatically! */

    f_in = fopen(file, "r");
    while (nc_fgets(line, MAXSTR, f_in) != NULL) {
	chop(line);
	if (!is_text(line)) {	/* replace blank lines with a <p> */
	    fprintf(f_out, "<p>\n");
	}
	else {
	    sprintf(tmp, "%s <br>\n", line);
	    fprintf(f_out, tmp);
	}
    }

    fclose(f_in);
    fclose(f_out);
    close(fd);
}


/* 
   A pretty dumb function: this just lists all directories which are directly below
   top level and not called "src". It is assumed that these contain binary distributions
   which can be installed by just doing a "make install" in the respective directory.
 */
void list_binaries(char *package)
{
    char tmp[MAXSTR];
    struct stat buf;
    DIR *dir;
    struct dirent *dir_entry;
    int n_dirs = 0;

    fprintf(stdout, "Binary installation files\n");

    dir = opendir(package);
    if (dir == NULL) {
	fprintf(stdout, "  None.\n\n");
	return;
    }

    dir_entry = readdir(dir);
    while (dir_entry != NULL) {
	if ((strcmp(dir_entry->d_name, ".")) &&
	    (strcmp(dir_entry->d_name, "..")) &&
	    (strcmp(dir_entry->d_name, "src"))
	    ) {
	    /* check if it is a directory */
	    sprintf(tmp, "%s/%s", package, dir_entry->d_name);
	    stat(tmp, &buf);
	    if (S_ISDIR(buf.st_mode)) {
		if (n_dirs == 0) {
		    fprintf(stdout, "  %s", dir_entry->d_name);
		}
		else {
		    fprintf(stdout, ", %s", dir_entry->d_name);
		}
		n_dirs++;
	    }
	}
	dir_entry = readdir(dir);
    }
    if (n_dirs == 0) {
	fprintf(stdout, "  None.");
    }
    fprintf(stdout, "\n\n");
}


/* 
   Just as dumb: checks, if a specified directory 'binaries' exists in addition to 'src'
   Returns 1 if so, 0 otherwise 
 */
int binaries_exist(char *package, char *binaries)
{
    char tmp[MAXSTR];
    struct stat buf;
    DIR *dir;
    struct dirent *dir_entry;

    dir = opendir(package);
    if (dir == NULL) {
	return (0);
    }

    dir_entry = readdir(dir);
    while (dir_entry != NULL) {
	if ((strcmp(dir_entry->d_name, ".")) &&
	    (strcmp(dir_entry->d_name, "..")) &&
	    (strcmp(dir_entry->d_name, "src"))
	    ) {
	    /* check if it is a directory */
	    sprintf(tmp, "%s/%s", package, dir_entry->d_name);
	    stat(tmp, &buf);
	    if (S_ISDIR(buf.st_mode)) {
		if (!strcmp(dir_entry->d_name, binaries)) {
		    return (1);	/* found it */
		}
	    }
	}
	dir_entry = readdir(dir);
    }
    return (0);
}


/* 
   Tests for a known file-extension:
   .tar.gz, .tgz
   .tar.bz2, .tbz
   .zip
   Returns:
   0 = unknown (TYPE_UNKNOWN)
   1 = tar file with gzip compression (TAR_GZIP)
   2 = tar file with bzip2 compression (TAR_BZIP2)
   3 = zip archive (ZIP)
   4 = plain tar archive, uncompressed (TAR)

   VERY primitive!
 */
int check_filetype(char *myfile)
{

    if (strstr(myfile, ".tar.gz") != NULL) {
	return (1);
    }
    if (strstr(myfile, ".tgz") != NULL) {
	return (1);
    }
    if (strstr(myfile, ".tar.bz2") != NULL) {
	return (2);
    }
    if (strstr(myfile, ".tbz") != NULL) {
	return (2);
    }
    if (strstr(myfile, ".zip") != NULL) {
	return (3);
    }
    if (strstr(myfile, ".tar") != NULL) {
	return (4);
    }

    return (0);
}

/*
   Retrieves an extension from the WWW using wget and
   stores the file in the current directory.
 */
void wget_extension(char *url)
{
    char str[MAXSTR];
    int error;

    fprintf(stdout, "Downloading...");

    if (VERBOSE) {
	sprintf(str, "wget -N %s", url);
    }
    else {
	sprintf(str, "wget -N -q %s", url);
    }
    error = system(str);
    if (error == -1) {
	print_error(ERR_DOWNLOAD,
		    "could not run 'wget' to download extension. Is it installed?\n");
    }
    if (error > 0) {
	print_error(ERR_DOWNLOAD, "running command '%s'.\n", str);
    }

    print_done();
}

/*
   This function checks if there is write access to the GRASS directory.
   If not, it aborts with an error message.
   Otherwise, 'cmd' is simply executed as currently running user.
 */
void su(char *gisbase, char *cmd)
{
    char tmpfile[MAXSTR];
    int error;
    static unsigned long next;
    FILE *f;

    /* check for permission  */
    next = next * 1103515245 + 54321;	/* generate a random file name */
    next = (next / 65536) % 32768;
    srand(next);
    sprintf(tmpfile, "%s/gem.test.%i", gisbase, rand());

    f = fopen(tmpfile, "w+");

    if (errno == EACCES) {	/* permission denied */
	print_error(ERR_INSTALL_EXT,
		    "You don't have write access to your local GRASS installation.\nPlease consult your system administrator.\n");
    }
    else {
	remove(tmpfile);
	fclose(f);
	error = system(cmd);
	if (error != 0) {
	    print_error(ERR_MISSING_CMD, "could not run '%s'.\n", cmd);
	}
    }
}


/*
   Compares two version descriptions consisting of three ints each
   returns: -1, if major.minor.revision < major2.minor2.revision2,
   0, if equal
   1, if major.minor.revision > major2.minor2.revision2,
 */
int vercmp(int major, int minor, int revision, int major2, int minor2,
	   int revision2)
{
    if ((major == major2) && (minor == minor2) && (revision == revision2)) {
	return (0);
    }
    if (major2 > major) {
	return (-1);
    }
    if (major2 < major) {
	return (1);
    }
    if (minor2 > minor) {
	return (-1);
    }
    if (minor2 < minor) {
	return (1);
    }
    if (revision2 > revision) {
	return (-1);
    }
    if (revision2 < revision) {
	return (1);
    }

    return (0);
}
