#include <string.h>
#include <grass/gis.h>
#include <grass/glocale.h>

static char *NULL_STRING = "null";
static int reclass_type(FILE *, char **, char **);
static FILE *fopen_cellhd_old(const char *, const char *);
static FILE *fopen_cellhd_new(const char *);
static int get_reclass_table(FILE *, struct Reclass *);


/*!
 * \brief reclass file?
 *
 * This function determines if the raster map
 * <b>name</b> in <b>mapset</b> is a reclass file.
 * If it is, then the name and mapset of the referenced 
 * raster map are copied into the <b>r_name</b> and <b>r_mapset</b> 
 * buffers.
 * Returns 1 if <b>name</b> is a reclass file, 0 if it is not, and -1 if 
 * there was a problem reading the raster header for <b>name.</b>
 *
 *  \param name
 *  \param mapset
 *  \param r_name
 *  \param r_mapset
 *  \return int
 */

int G_is_reclass(const char *name, const char *mapset, char *rname,
		 char *rmapset)
{
    FILE *fd;
    int type;

    fd = fopen_cellhd_old(name, mapset);
    if (fd == NULL)
	return -1;

    type = reclass_type(fd, &rname, &rmapset);
    fclose(fd);
    if (type < 0)
	return -1;
    else
	return type != 0;
}


/*!
 * \brief get child reclass maps list
 *
 * This function generates a
 * child reclass maps list from the cell_misc/reclassed_to file which stores 
 * this list. The cell_misc/reclassed_to file is written by 
 * G_put_reclass().
 * G_is_reclassed_to() is used by g.rename, g.remove and r.reclass to
 * prevent accidentally deleting the parent map of a reclassed raster map.
 *
 *  \param name
 *  \param mapset
 *  \param nrmaps
 *  \param rmaps
 *  \return int
 */

int G_is_reclassed_to(const char *name, const char *mapset, int *nrmaps,
		      char ***rmaps)
{
    FILE *fd;
    int i, j, k, l;
    char buf2[256], buf3[256];

    fd = G_fopen_old_misc("cell_misc", "reclassed_to", name, mapset);

    if (fd == NULL) {
	return -1;
    }

    if (rmaps)
	*rmaps = NULL;
    for (i = 0; !feof(fd) && fgets(buf2, 255, fd);) {
	l = strlen(buf2);
	for (j = 0, k = 0; j < l; j++) {
	    if (buf2[j] == '#' ||
		((buf2[j] == ' ' || buf2[j] == '\t' || buf2[j] == '\n') && k))
		break;
	    else if (buf2[j] != ' ' && buf2[j] != '\t')
		buf3[k++] = buf2[j];
	}

	if (k) {
	    buf3[k] = 0;
	    i++;
	    if (rmaps) {
		*rmaps = (char **)G_realloc(*rmaps, i * sizeof(char *));
		(*rmaps)[i - 1] = (char *)G_malloc(k + 1);
		strncpy((*rmaps)[i - 1], buf3, k);
		(*rmaps)[i - 1][k] = 0;
	    }
	}
    }

    if (nrmaps)
	*nrmaps = i;

    if (i && rmaps) {
	i++;
	*rmaps = (char **)G_realloc(*rmaps, i * sizeof(char *));
	(*rmaps)[i - 1] = NULL;
    }

    fclose(fd);

    return i;
}

int G_get_reclass(const char *name, const char *mapset,
		  struct Reclass *reclass)
{
    FILE *fd;
    int stat;

    fd = fopen_cellhd_old(name, mapset);
    if (fd == NULL)
	return -1;
    reclass->name = NULL;
    reclass->mapset = NULL;
    reclass->type = reclass_type(fd, &reclass->name, &reclass->mapset);
    if (reclass->type <= 0) {
	fclose(fd);
	return reclass->type;
    }

    switch (reclass->type) {
    case RECLASS_TABLE:
	stat = get_reclass_table(fd, reclass);
	break;
    default:
	stat = -1;
    }

    fclose(fd);
    if (stat < 0) {
	if (stat == -2)
	    G_warning(_("Too many reclass categories for [%s in %s]"),
		      name, mapset);
	else
	    G_warning(_("Illegal reclass format in header file for [%s in %s]"),
		      name, mapset);
	stat = -1;
    }
    return stat;
}

int G_free_reclass(struct Reclass *reclass)
{
    switch (reclass->type) {
    case RECLASS_TABLE:
	if (reclass->num > 0)
	    G_free(reclass->table);
	reclass->num = 0;
	if (reclass->name)
	    G_free(reclass->name);
	if (reclass->mapset)
	    G_free(reclass->mapset);
	reclass->name = NULL;
	reclass->mapset = NULL;
	break;
    default:
	break;
    }

    return 0;
}

static int reclass_type(FILE * fd, char **rname, char **rmapset)
{
    char buf[128];
    char label[128], arg[128];
    int i;
    int type;

    /* Check to see if this is a reclass file */
    if (fgets(buf, sizeof(buf), fd) == NULL)
	return 0;
    if (strncmp(buf, "reclas", 6))
	return 0;
    /* later may add other types of reclass */
    type = RECLASS_TABLE;

    /* Read the mapset and file name of the REAL cell file */
    if (*rname)
	**rname = '\0';
    if (*rmapset)
	**rmapset = '\0';
    for (i = 0; i < 2; i++) {
	if (fgets(buf, sizeof buf, fd) == NULL)
	    return -1;
	if (sscanf(buf, "%[^:]:%s", label, arg) != 2)
	    return -1;
	if (strncmp(label, "maps", 4) == 0) {
	    if (*rmapset)
		strcpy(*rmapset, arg);
	    else
		*rmapset = G_store(arg);
	}
	else if (strncmp(label, "name", 4) == 0) {
	    if (*rname)
		strcpy(*rname, arg);
	    else
		*rname = G_store(arg);
	}
	else
	    return -1;
    }
    if (**rmapset && **rname)
	return type;
    else
	return -1;
}

static FILE *fopen_cellhd_old(const char *name, const char *mapset)
{
    return G_fopen_old("cellhd", name, mapset);
}

int G_put_reclass(const char *name, const struct Reclass *reclass)
{
    FILE *fd;
    long min, max;
    int found;
    char buf1[GPATH_MAX], buf2[GNAME_MAX], *p;
    char *xname;

    switch (reclass->type) {
    case RECLASS_TABLE:
	if (reclass->min > reclass->max || reclass->num <= 0) {
	    G_fatal_error(_("Illegal reclass request"));
	    return -1;
	}
	break;
    default:
	G_fatal_error(_("Illegal reclass type"));
	return -1;
    }

    fd = fopen_cellhd_new(name);
    if (fd == NULL) {
	G_warning(_("Unable to create header file for [%s in %s]"),
		  name, G_mapset());
	return -1;
    }

    fprintf(fd, "reclass\n");
    fprintf(fd, "name: %s\n", reclass->name);
    fprintf(fd, "mapset: %s\n", reclass->mapset);

    /* find first non-null entry */
    for (min = 0; min < reclass->num; min++)
	if (!G_is_c_null_value(&reclass->table[min]))
	    break;
    /* find last non-zero entry */
    for (max = reclass->num - 1; max >= 0; max--)
	if (!G_is_c_null_value(&reclass->table[max]))
	    break;

    /*
     * if the resultant table is empty, write out a dummy table
     * else write out the table
     *   first entry is #min
     *   rest are translations for cat min+i
     */
    if (min > max)
	fprintf(fd, "0\n");
    else {
	fprintf(fd, "#%ld\n", (long)reclass->min + min);
	while (min <= max) {
	    if (G_is_c_null_value(&reclass->table[min]))
		fprintf(fd, "%s\n", NULL_STRING);
	    else
		fprintf(fd, "%ld\n", (long)reclass->table[min]);
	    min++;
	}
    }
    fclose(fd);

    strcpy(buf2, reclass->name);
    if ((p = strchr(buf2, '@')))
	*p = 0;

    G__file_name_misc(buf1, "cell_misc", "reclassed_to", reclass->name,
		      reclass->mapset);

    fd = fopen(buf1, "a+");
    if (fd == NULL) {
#if 0
	G_warning(_("Unable to create dependency file in [%s in %s]"),
		  buf2, reclass->mapset);
#endif
	return 1;
    }

    fseek(fd, 0L, SEEK_SET);

    xname = G_fully_qualified_name(name, G_mapset());
    found = 0;
    for (;;) {
	char buf[GNAME_MAX + GMAPSET_MAX];
	if (!G_getl2(buf, sizeof(buf), fd))
	    break;
	if (strcmp(xname, buf) == 0) {
	    found = 1;
	    break;
	}
    }

    if (!found)
	fprintf(fd, "%s\n", xname);

    G_free(xname);
    fclose(fd);

    return 1;
}

static FILE *fopen_cellhd_new(const char *name)
{
    return G_fopen_new("cellhd", name);
}

static int get_reclass_table(FILE * fd, struct Reclass *reclass)
{
    char buf[128];
    int n;
    int first, null_str_size;
    CELL cat;
    long len;

    /*
     * allocate the table, expanding as each entry is read
     * note that G_realloc() will become G_malloc() if ptr in
     * NULL
     */
    reclass->min = 0;
    reclass->table = NULL;
    null_str_size = strlen(NULL_STRING);
    n = 0;
    first = 1;
    while (fgets(buf, sizeof buf, fd)) {
	if (first) {
	    first = 0;
	    if (sscanf(buf, "#%d", &cat) == 1) {
		reclass->min = cat;
		continue;
	    }
	}
	if (strncmp(buf, NULL_STRING, null_str_size) == 0)
	    G_set_c_null_value(&cat, 1);
	else {
	    if (sscanf(buf, "%d", &cat) != 1)
		return -1;
	}
	n++;
	len = (long)n *sizeof(CELL);

	if (len != (int)len) {	/* check for int overflow */
	    if (reclass->table != NULL)
		G_free(reclass->table);
	    return -2;
	}
	reclass->table = (CELL *) G_realloc((char *)reclass->table, (int)len);
	reclass->table[n - 1] = cat;
    }
    reclass->max = reclass->min + n - 1;
    reclass->num = n;
    return 1;
}
