/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of H4H5TOOLS. The full H4H5TOOLS copyright notice,      *
 * including terms governing use, modification, and redistribution, is       *
 * contained in the COPYING file, which can be found at the root of the      *
 * source code distribution tree. The copyright notice can also be found     *
 * at https://www.hdfgroup.org/licenses.  If you do not have access to       *
 * either file, you may request a copy from help@hdfgroup.org.               *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


#ifndef _WIN32
#include <config.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "mfhdf.h"
#include "hdf.h"
#include "HdfEosDef.h"

#define _C_VECTOR_IMPL
#include "hdfeos2.h"

/* this function is not exposed API */
#ifdef __cplusplus
extern "C" {
#endif
intn GDij2ll(int32 projcode, int32 zonecode, float64 projparm[], int32 spherecode, int32 xdimsize, int32 ydimsize, float64 upleftpt[], float64 lowrightpt[], int32 npnts, int32 row[], int32 col[], float64 longitude[], float64 latitude[], int32 pixcen, int32 pixcnr);
#ifdef __cplusplus
}
#endif

#define suicide() _suicide(__FILE__, __LINE__)
#define nomem() _nomem(__FILE__, __LINE__)
#define ASSERT(e) { if (!(e)) _assertfailure(__FILE__, __LINE__); }


void init_namelist_t(namelist_t *e)
{
	challoc_init(&e->name);
}
void free_namelist_t(namelist_t *e)
{
	challoc_free(&e->name);
}

void init_dimension_t(dimension_t *e)
{
	challoc_init(&e->name);
}
void free_dimension_t(dimension_t *e)
{
	challoc_free(&e->name);
}

void init_fieldinfo_t(fieldinfo_t *e)
{
	challoc_init(&e->name);
	dimensionalloc_init(&e->dims);
	challoc_init(&e->data);
	challoc_init(&e->filler);
}
void free_fieldinfo_t(fieldinfo_t *e)
{
	challoc_free(&e->name);
	dimensionalloc_free(&e->dims);
	challoc_free(&e->data);
	challoc_free(&e->filler);
}

void init_attribute_t(attribute_t *e)
{
	challoc_init(&e->name);
	challoc_init(&e->value);
}
void free_attribute_t(attribute_t *e)
{
	challoc_free(&e->name);
	challoc_free(&e->value);
}

void init_gridinfo_t(gridinfo_t *e)
{
}
void free_gridinfo_t(gridinfo_t *e)
{
}

void init_projinfo_t(projinfo_t *e)
{
	float64alloc_init(&e->lons);
	float64alloc_init(&e->lats);
}
void free_projinfo_t(projinfo_t *e)
{
	float64alloc_free(&e->lons);
	float64alloc_free(&e->lats);
}

void init_grid_t(grid_t *e)
{
	challoc_init(&e->name);
	init_gridinfo_t(&e->info);
	init_projinfo_t(&e->proj);
	dimensionalloc_init(&e->dim);
	fieldinfoalloc_init(&e->field);
	attributealloc_init(&e->attr);
}
void free_grid_t(grid_t *e)
{
	challoc_free(&e->name);
	free_gridinfo_t(&e->info);
	free_projinfo_t(&e->proj);
	dimensionalloc_free(&e->dim);
	fieldinfoalloc_free(&e->field);
	attributealloc_free(&e->attr);
}


void init_dimmap_t(dimmap_t *e)
{
	challoc_init(&e->data);
	challoc_init(&e->geo);
}
void free_dimmap_t(dimmap_t *e)
{
	challoc_free(&e->data);
	challoc_free(&e->geo);
}

void init_indexmap_t(indexmap_t *e)
{
	challoc_init(&e->geo);
	challoc_init(&e->data);
	int32alloc_init(&e->indices);
}
void free_indexmap_t(indexmap_t *e)
{
	challoc_free(&e->geo);
	challoc_free(&e->data);
	int32alloc_free(&e->indices);
}

void init_swath_t(swath_t *e)
{
	challoc_init(&e->name);
	dimensionalloc_init(&e->dim);
	dimmapalloc_init(&e->dimmap);
	indexmapalloc_init(&e->index);
	fieldinfoalloc_init(&e->geofield);
	fieldinfoalloc_init(&e->datafield);
	attributealloc_init(&e->attr);
}
void free_swath_t(swath_t *e)
{
	challoc_free(&e->name);
	dimensionalloc_free(&e->dim);
	dimmapalloc_free(&e->dimmap);
	indexmapalloc_free(&e->index);
	fieldinfoalloc_free(&e->geofield);
	fieldinfoalloc_free(&e->datafield);
	attributealloc_free(&e->attr);
}


static void _suicide(const char *fname, int line)
{
	fprintf(stderr, "suicide at %s %d\n", fname, line);
	exit(1);
}
static void _nomem(const char *fname, int line)
{
	fprintf(stderr, "no memory at %s %d\n", fname, line);
	exit(1);
}

static void _assertfailure(const char *fname, int line)
{
	fprintf(stderr, "assertion failure at %s %d\n", fname, line);
	exit(1);
}

static int parse_list(const char *s, int len, char sep, namelistalloc *names)
{
	int i, j;

	namelistalloc_init(names);
	for (i = j = 0; j <= len; ++j) {
		if ((j == len && len) || s[j] == sep) {
			namelist_t *elem;
			if (!namelistalloc_readyplus(names, 1)) nomem();
			elem = &names->s[names->len++];
			{
				challoc_init(&elem->name);
				if (!challoc_copyb(&elem->name, s + i, j - i)) nomem();
			}
			i = j + 1;
			continue;
		}
	}
	return 0;
}

static int parse_namelist(const char *filename, int32 (*inq)(char *, char *, int32 *), namelistalloc *names)
{
	char *fname = (char *)filename;
	int32 bufsize;
	int numobjs;

	if ((numobjs = inq(fname, NULL, &bufsize)) == -1) suicide();
	if (numobjs > 0) {
		char *namelist;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if (inq(fname, namelist, &bufsize) == -1) suicide();
		parse_list(namelist, bufsize, ',', names);
		ASSERT(names->len == numobjs)
		free(namelist);
	}
	return 0;
}

static int get_dimension(int id, int32 (*entries)(int32, int32, int32 *), int32 (*inq)(int32, char *, int32 *), dimensionalloc *dims)
{
	int32 numdims, bufsize;

	if ((numdims = entries(id, HDFE_NENTDIM, &bufsize)) == -1) suicide();
	if (numdims > 0) {
		char *namelist;
		int32 *dimsize;
		int i, j;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if ((dimsize = (int32 *)malloc(numdims * sizeof(int32))) == NULL) nomem();
		if (inq(id, namelist, dimsize) == -1) suicide();

		for (i = j = 0; j <= bufsize; ++j) {
			if ((j == bufsize && bufsize) || namelist[j] == ',') {
				dimension_t *dim;
				if (!dimensionalloc_readyplus(dims, 1)) nomem();
				dim = &dims->s[dims->len++];
				ASSERT(dims->len <= numdims)
				{
					challoc_init(&dim->name);
					if (!challoc_copyb(&dim->name, namelist + i, j - i)) nomem();
					dim->dimsize = dimsize[dims->len - 1];
				}
				i = j + 1;
				continue;
			}
		}
		free(dimsize);
		free(namelist);
	}
	return 0;
}

static int get_fieldinfo(int id, int32 (*entries)(int32, int32, int32 *), int32 (*inq)(int32, char *, int32 *, int32 *), intn (*fldinfo)(int32, char *, int32 *, int32 *, int32 *, char *), intn (*readfld)(int32, char *, int32 *, int32 *, int32 *, VOIDP), intn (*getfill)(int32, char *, VOIDP), int geofield, fieldinfoalloc *fields)
{
	int32 numfields, bufsize;
	namelistalloc dimnames;

	namelistalloc_init(&dimnames);
	if ((numfields = entries(id, geofield ? HDFE_NENTGFLD : HDFE_NENTDFLD, &bufsize)) == -1) suicide();
	if (numfields > 0) {
		char *namelist;
		int i, j;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if (inq(id, namelist, NULL, NULL) == -1) suicide();

		for (i = j = 0; j <= bufsize; ++j) {
			if ((j == bufsize && bufsize) || namelist[j] == ',') {
				fieldinfo_t *field;
				if (!fieldinfoalloc_readyplus(fields, 1)) nomem();
				field = &fields->s[fields->len++];
				init_fieldinfo_t(field);
				ASSERT(fields->len <= numfields)
				// TODO: compression info
				{
					int k, numelem = 0;
					int32 dimsize[16]; // XXX: 16?
					char dimlist[512]; // XXX: one HDF-EOS2 developer recommeded this

					if (!challoc_copyb(&field->name, namelist + i, j - i)) nomem();
					if ((fldinfo(id, field->name.s, &field->rank, dimsize, &field->type, dimlist)) == -1) suicide();

					namelistalloc_empty(&dimnames);
					if (parse_list(dimlist, (int)strlen(dimlist), ',', &dimnames) == FAIL) suicide();
					if (dimnames.len != field->rank) suicide();
					for (k = 0; k < dimnames.len; ++k) {
						dimension_t *dim;
						if (!dimensionalloc_readyplus(&field->dims, 1)) nomem();
						dim = &field->dims.s[field->dims.len++];
						challoc_init(&dim->name);
						challoc_copy(&dim->name, &dimnames.s[k].name);
						dim->dimsize = dimsize[k];
					}

					for (k = 0, numelem = 1; k < field->dims.len; ++k)
						numelem *= field->dims.s[k].dimsize;

					challoc_init(&field->data);
					if (!challoc_ready(&field->data, numelem * DFKNTsize(field->type))) nomem();
					if (readfld(id, field->name.s, NULL, NULL, NULL, field->data.s) == -1) suicide();
					field->data.len = numelem * DFKNTsize(field->type);

					challoc_init(&field->filler);
					if (!challoc_ready(&field->filler, DFKNTsize(field->type))) nomem();
					if (getfill(id, field->name.s, field->filler.s) != -1)
						field->filler.len = DFKNTsize(field->type);
				}
				i = j + 1;
				continue;
			}
		}
		free(namelist);
	}
	namelistalloc_free(&dimnames);
	return 0;
}

static int get_attribute(int id, int32 (*inq)(int32, char *, int32 *), intn (*attrinfo)(int32, char *, int32 *, int32 *), intn (*readattr)(int32, char *, VOIDP), attributealloc *attrs)
{
	int32 numattrs, bufsize;

	if ((numattrs = inq(id, NULL, &bufsize)) == -1) suicide();
	if (numattrs > 0) {
		char *namelist;
		int i, j;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if (inq(id, namelist, &bufsize) == -1) suicide();

		for (i = j = 0; j <= bufsize; ++j) {
			if ((j == bufsize && bufsize) || namelist[j] == ',') {
				attribute_t *attr;
				if (!attributealloc_readyplus(attrs, 1)) nomem();
				attr = &attrs->s[attrs->len++];
				ASSERT(attrs->len <= numattrs)
				{
					int32 count;
					challoc_init(&attr->name);
					if (!challoc_copyb(&attr->name, namelist + i, j - i)) nomem();
					if (attrinfo(id, attr->name.s, &attr->type, &count) == -1) suicide();

					challoc_init(&attr->value);
					if (!challoc_ready(&attr->value, count)) nomem();
					if (readattr(id, attr->name.s, attr->value.s) == -1) suicide();
					attr->value.len = count;
				}
				i = j + 1;
				continue;
			}
		}
		free(namelist);
	}
	return 0;
}

int parse_gridnames(const char *filename, namelistalloc *names)
{
	return parse_namelist(filename, GDinqgrid, names);
}

static int get_gridinfo(int gdid, gridinfo_t *info)
{
	// TODO: unpack degree may be necessary
	if (GDgridinfo(gdid, &info->xdim, &info->ydim, info->upleft, info->lowright) == -1) suicide();

	return 0;
}

static int convert_proj(const gridinfo_t *grid, projinfo_t *proj)
{
	int32 numpoints, *rows, *cols;
	gridinfo_t *g = (gridinfo_t *)grid;
	int i, j, k;

	numpoints = grid->xdim * grid->ydim;
	if ((rows = (int32 *)malloc(numpoints * sizeof(int32))) == NULL) nomem();
	if ((cols = (int32 *)malloc(numpoints * sizeof(int32))) == NULL) nomem();
	for (k = j = 0; j < grid->ydim; ++j) {
		for (i = 0; i < grid->xdim; ++i) {
			rows[k] = j;
			cols[k] = i;
			++k;
		}
	}

	if (!float64alloc_ready(&proj->lons, numpoints)) nomem();
	if (!float64alloc_ready(&proj->lats, numpoints)) nomem();

	if (GDij2ll(proj->proj, proj->zone, proj->param, proj->sphere, g->xdim, g->ydim, g->upleft, g->lowright, numpoints, rows, cols, proj->lons.s, proj->lats.s, proj->pix, proj->origin) == -1) suicide();
	proj->lons.len = numpoints;
	proj->lats.len = numpoints;
	return 0;
}

static int get_gridprojinfo(int gdid, const gridinfo_t *grid, projinfo_t *proj)
{
	int status;

	if ((status = GDprojinfo(gdid, &proj->proj, &proj->zone, &proj->sphere, proj->param)) == -1) suicide();
	if ((status = GDpixreginfo(gdid, &proj->pix)) == -1) suicide();
	if ((status = GDorigininfo(gdid, &proj->origin)) == -1) suicide();

	float64alloc_init(&proj->lons);
	float64alloc_init(&proj->lats);
	if (convert_proj(grid, proj) == -1) suicide();
	return 0;
}

static int get_griddimension(int gdid, dimensionalloc *dims)
{
	return get_dimension(gdid, GDnentries, GDinqdims, dims);
}

static int get_gridfieldinfo(int gdid, fieldinfoalloc *fields)
{
	return get_fieldinfo(gdid, GDnentries, GDinqfields, GDfieldinfo, GDreadfield, GDgetfillvalue, 0, fields);
}

static int get_gridattribute(int gdid, attributealloc *attrs)
{
	return get_attribute(gdid, GDinqattrs, GDattrinfo, GDreadattr, attrs);
}

int32 open_grids(const char *filename)
{
	int32 readfd = -1;
	if ((readfd = GDopen((char *)filename, DFACC_READ)) == -1) suicide();
	return readfd;
}

void close_grids(int32 readfd)
{
	GDclose(readfd);
}

int read_grid(int32 readfd, const char *gridname, grid_t *grid)
{
	int32 gdid;
	if ((gdid = GDattach(readfd, (char *)gridname)) == -1) suicide();

	if (!challoc_copys(&grid->name, gridname)) nomem();
	if (get_gridinfo(gdid, &grid->info) == -1) suicide();
	if (get_gridprojinfo(gdid, &grid->info, &grid->proj) == -1) suicide();
	if (get_griddimension(gdid, &grid->dim) == -1) suicide();
	if (get_gridfieldinfo(gdid, &grid->field) == -1) suicide();
	if (get_gridattribute(gdid, &grid->attr) == -1) suicide();

	GDdetach(gdid);
	return 0;
}

int parse_swathnames(const char *filename, namelistalloc *names)
{
	return parse_namelist(filename, SWinqswath, names);
}

static int get_swathdimension(int swid, dimensionalloc *dims)
{
	return get_dimension(swid, SWnentries, SWinqdims, dims);
}

static int get_swathdimmap(int swid, dimmapalloc *maps)
{
	int32 nummaps, bufsize;
	namelistalloc name;

	namelistalloc_init(&name);
	if ((nummaps = SWnentries(swid, HDFE_NENTMAP, &bufsize)) == -1) suicide();
	if (nummaps > 0) {
		char *namelist;
		int32 *offset, *increment;
		int i, j;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if ((offset = (int32 *)malloc(nummaps * sizeof(int32))) == NULL) nomem();
		if ((increment = (int32 *)malloc(nummaps * sizeof(int32))) == NULL) nomem();
		if (SWinqmaps(swid, namelist, offset, increment) == -1) suicide();

		for (i = j = 0; j <= bufsize; ++j) {
			if ((j == bufsize && bufsize) || namelist[j] == ',') {
				dimmap_t *map;
				if (!dimmapalloc_readyplus(maps, 1)) nomem();
				map = &maps->s[maps->len++];
				init_dimmap_t(map);
				ASSERT(maps->len <= nummaps)
				{
					namelistalloc_empty(&name);
					if (parse_list(namelist + i, j - i, '/', &name) == FAIL) suicide();
					if (name.len != 2) suicide();
					challoc_init(&map->geo);
					challoc_copy(&map->geo, &name.s[0].name);
					challoc_init(&map->data);
					challoc_copy(&map->data, &name.s[1].name);
					map->offset = offset[maps->len - 1];
					map->increment = increment[maps->len - 1];
				}
				i = j + 1;
				continue;
			}
		}
		free(increment);
		free(offset);
		free(namelist);
	}
	namelistalloc_free(&name);
	return 0;
}

static int get_swathindexmap(int swid, indexmapalloc *indices)
{
	int32 numindices, bufsize;
	namelistalloc name;

	namelistalloc_init(&name);
	if ((numindices = SWnentries(swid, HDFE_NENTIMAP, &bufsize)) == -1) suicide();
	if (numindices > 0) {
		// TODO: I have never seen any EOS2 files that have index map.
		char *namelist;
		int i, j;

		if ((namelist = (char *)malloc(bufsize + 1)) == NULL) nomem();
		if (SWinqidxmaps(swid, namelist, NULL) == -1) suicide();

		for (i = j = 0; j <= bufsize; ++j) {
			if ((j == bufsize && bufsize) || namelist[j] == ',') {
				indexmap_t *index;
				if (!indexmapalloc_readyplus(indices, 1)) nomem();
				index = &indices->s[indices->len++];
				ASSERT(indices->len <= numindices)
				{
					int32 dimsize;
					namelistalloc_empty(&name);
					if (parse_list(namelist + i, j - i, '/', &name) == FAIL) suicide();
					if (name.len != 2) suicide();
					challoc_init(&index->geo);
					challoc_copy(&index->geo, &name.s[0].name);
					challoc_init(&index->data);
					challoc_copy(&index->data, &name.s[1].name);

					if ((dimsize = SWdiminfo(swid, index->geo.s)) == -1) suicide();
					int32alloc_init(&index->indices);
					if (!int32alloc_ready(&index->indices, dimsize)) suicide();
					if (SWidxmapinfo(swid, index->geo.s, index->data.s, index->indices.s) == -1) suicide();
					index->indices.len = dimsize;
				}
				i = j + 1;
				continue;
			}
		}
		free(namelist);
	}
	namelistalloc_free(&name);
	return 0;
}

static int get_swathfieldinfo(int swid, int geofield, fieldinfoalloc *fields)
{
	return get_fieldinfo(swid, SWnentries, geofield ? SWinqgeofields : SWinqdatafields, SWfieldinfo, SWreadfield, SWgetfillvalue, geofield, fields);
}

static int get_swathattribute(int swid, attributealloc *attrs)
{
	return get_attribute(swid, SWinqattrs, SWattrinfo, SWreadattr, attrs);
}

int32 open_swaths(const char *filename)
{
	int32 readfd = -1;
	if ((readfd = SWopen((char *)filename, DFACC_READ)) == -1) suicide();
	return readfd;
}

void close_swaths(int32 readfd)
{
	SWclose(readfd);
}

int read_swath(int32 readfd, const char *swathname, swath_t *swath)
{
	int32 swid;
	if ((swid = SWattach(readfd, (char *)swathname)) == -1) suicide();

	if (!challoc_copys(&swath->name, swathname)) nomem();
	if (get_swathdimension(swid, &swath->dim) == -1) suicide();
	if (get_swathdimmap(swid, &swath->dimmap) == -1) suicide();
	if (get_swathindexmap(swid, &swath->index) == -1) suicide();
	if (get_swathfieldinfo(swid, 1, &swath->geofield) == -1) suicide();
	if (get_swathfieldinfo(swid, 0, &swath->datafield) == -1) suicide();
	if (get_swathattribute(swid, &swath->attr) == -1) suicide();

	SWdetach(swid);
	return 0;
}

// vim:ts=4:sw=4:sts=0:cindent
