/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>

 

#include <dx/dx.h>
#include <ctype.h>
#include <string.h>
#include "import.h"


/* if this ifdef is enabled, then the filename parameter can be a list of
 *  files instead of just single filename.  you can't specify a variable name.
 *  it imports the default object from each file and adds it to a group and 
 *  returns the group at the end of import.  
 * one of the reasons this code isn't compiled in is that the original
 *  requestors asked that we allow empty files or invalid files to be skipped
 *  without ending the import.  if we do decide to enable this feature in the
 *  future, a bad file should set the error code and return.
 */
#define MULTIFILE 0
 

/* prototypes */
static Error  check_extension(char *fname, int *dt);
extern Object _dxfImportBin(char *dataset);   /* binary format importer */ 
extern Error _dxfIsCircular(Object o);        /* checks for loops in the obj */

 
/* 0 = filename         mdf:   name
 * 1 = fieldname               variable
 * 2 = format string           format
 * 3 = startframe              start
 * 4 = endframe                end
 * 5 = deltaframe              delta
 */
int
m_Import(Object *in, Object *out)
{
    char **fp = NULL, *format;
    char *ctmp, **ftmp;
    int i, nfields;
    int entrynum;
    int startframe, endframe, deltaframe;
    int frameset = 0;
    int match = 0;
#if MULTIFILE
    int multifile = 0;
#endif
    Object o = NULL;
    struct parmlist p;
    struct import_table *it;
    
    out[0] = NULL;
    entrynum = -1;
 
    /* check filename 
     */
    if (!in[0]) {
	DXSetError(ERROR_BAD_PARAMETER, "#10000", "file name");
	return ERROR;
    }

    /* unless multifile is enabled, the filename is required to be
     *  a single a string.
     */
    if (!DXExtractString(in[0], &p.filename))
#if MULTIFILE
    {
	if (!DXExtractNthString(in[0], 0, &p.filename)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10200", "file name");
	    return ERROR;
	} 
	else
	    multifile++;
    }
	    
#else
    {
	DXSetError(ERROR_BAD_PARAMETER, "#10200", "file name");
	return ERROR;
    }
#endif

 
    /* check format string. 
     */
    if (in[2]) {
	char *cp, *firstword;

	if(!DXExtractString(in[2], &format)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10200", "format");
	    return ERROR;
	}

	/* stop parsing the format at the end of string or at the first blank
	 */
	firstword = format;
	if ((cp = strchr(format, ' ')) != NULL) {
	    firstword = (char *)DXAllocateLocal(cp-format+1);
	    if (!firstword)
		return ERROR;
	    strncpy(firstword, format, cp-format);
	    firstword[cp-format] = '\0';
	}
	
	for (it=_dxdImportTable, entrynum=0, match=0; 
	                                  it->readin; 
	                                  it++, entrynum++) {
	    ftmp = it->formatlist;
	    while(ftmp && *ftmp && !match) {
		if (!strncmp(firstword, *ftmp, strlen(*ftmp))) {
		    match++;
		    break;
		}
		ftmp++;
	    }
	    if (match)
		break;
	}
	
	if (!match) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10210", format, "file format");
	    return ERROR;
	}

	if (firstword != format)
	    DXFree((Pointer)firstword);

	p.format = format;
    } else
	p.format = NULL;
    
    
    /* fieldname(s) - build null terminated list
     */
    if(in[1]) {
	/* count the number of strings */
	nfields = 0;
	while (DXExtractNthString(in[1], nfields, &ctmp))
	    nfields++;
 
	if(nfields == 0) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10200", "variable");
	    return ERROR;
	}
	if(!(fp = (char **)DXAllocate(sizeof(char *) * (nfields + 1))))
	    return ERROR;
	
	for(i = 0; i < nfields; i++)
	    if(!DXExtractNthString(in[1], i, &fp[i])) {
		DXSetError(ERROR_BAD_PARAMETER, "#10200", "variable");
		goto done;
	    }
 
	fp[i] = NULL;
    }
    p.fieldlist = fp;
 
 
    /* start, end and delta for series groups.  you need to know if the
     *  values were set, and what they were set to.  the defaults if the
     *  values aren't specified is the first series member, the last series
     *  member, and 1 respectively.
     */
    if(in[3]) {
	if(!DXExtractInteger(in[3], &startframe)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10010", "start");
	    goto done;
	}
	p.startframe = &startframe;
    } else
	p.startframe = NULL;
 
    if(in[4]) {
	if(!DXExtractInteger(in[4], &endframe)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10010", "end");
	    goto done;
	}
	p.endframe = &endframe;
    } else
	p.endframe = NULL;
 
    if(in[5]) {
	if(!DXExtractInteger(in[5], &deltaframe)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10010", "delta");
	    goto done;
	}
	p.deltaframe = &deltaframe;
    } else
	p.deltaframe = NULL;
 

    /* if format type was not specified, try to determine the type, first
     * by checking the file extension, and then trying the autodetermination
     * routines in the import table.  at the end, if there still isn't a 
     * match, the data type remains unknown.  (the autodetermination routines
     * usually work by opening the file and trying to read the first few
     * bytes to see if their signature or format is there).
     */
    if (!in[2] && !match) {

	/* is it fred.XXX, where xxx matches something we know?
         *  if not, try each of the autodetermination routines for a match.
         */
	if (!check_extension(p.filename, &entrynum)) {
	    for (it=_dxdImportTable, entrynum = 0, match = 0; 
		                                it->readin; 
		                                it++, entrynum++) {
		if (it->autotype != NULL && ((it->autotype)(&p) == OK)) {
		    match++;
		    break;
		}
	    }
	    if (!match)
		entrynum = -1;
	}
	
    }


    /* now call the actual import routine.  either set an error if the
     *  data type still isn't known, or default to using the 5 (dx)
     *  in the table.  for now, try the latter and see if it is helpful
     *  or just confusing.
     */
#if 0
    if (entrynum < 0) {
	DXSetError(ERROR_BAD_PARAMETER, 
		 "file not found or unrecognized file type");
	return NULL;
    }
#else
    if (entrynum < 0)
	entrynum = 5;
#endif

#if MULTIFILE
    if (entrynum == 3 && multifile) {
	Object o = NULL;
	
	multifile = 0;
	if (!(out[0] = (Object)DXNewGroup()))
	    goto done;

	while (DXExtractNthString(in[0], multifile, &p.filename)) {
	    o = (_dxdImportTable[entrynum].readin)(&p);
	    if (!o) {
		multifile++;
		DXPrintError("0::Import");
		DXResetError();
		continue;
	    }

	    if (!DXSetMember((Group)out[0], NULL, o)) {
		DXDelete(out[0]);
		out[0] = NULL;
		goto done;
	    }
	    multifile++;
	}

    } else
	out[0] = (_dxdImportTable[entrynum].readin)(&p);
#else
    /* import happens here */
    out[0] = (_dxdImportTable[entrynum].readin)(&p);

    /* call code from Verify() to look for loops - like a group member
     *  which contains a pointer to the parent group.  modules further
     *  down the line are likely to loop forever trying to recursivly
     *  traverse the object.
     */
    if (_dxfIsCircular(out[0]) == ERROR) {
	out[0] = NULL;
	goto done;
    }

#ifndef DO_ENDFIELD
    /*  if you don't call endfield on each field as you build it but call
     *   endobject on the entire object, there is code in endobject which
     *   detects which fields share positions and connections arrays, and
     *   builds the neighbors and bounding box only once and makes them
     *   shared as well.  for big irregular fields which are part of a long
     *   series, this is a big time and space win.
     */
    if (out[0])
	if (! DXEndObject(out[0])) {
	    DXDelete(out[0]);
	    out[0] = NULL;
	}
#endif

#endif

  done: 
    DXFree((Pointer)fp);
    return (out[0] ? OK : ERROR);
}



static Error check_extension(char *fname, int *entrynum)
{
    char *cp, **ftmp;
    struct import_table *it;


    /* if no extension, return error */
    if ((cp = strrchr(fname, '.')) == NULL)
	return ERROR;
   
    if (*(++cp) == '\0')
	return ERROR;

    for (it = _dxdImportTable, *entrynum = 0; it->readin; it++, (*entrynum)++) {
	ftmp = it->formatlist;
	while(ftmp && *ftmp) {
	    if(!strcmp(cp, *ftmp))
		return OK;
	    ftmp++;
	}
    }
	
    return ERROR;
}    



/* built in importers */
 
extern Error _dxfstat_netcdf_file(char *filename);
 
Error _dxftry_ncdf(struct parmlist *p)
{
    char *tbuf;
    int frame, rc;

    /* if extension == .cdf or .ncdf, or */
    /* file or file.(n)cdf exists somewhere in curdir or DXDATA path, and */
    /* ncopen() works, then OK */
 
    if (_dxfstat_netcdf_file(p->filename) == OK)
	return OK;

    if (p->startframe == NULL && p->endframe == NULL && p->deltaframe == NULL)
	return ERROR;

    tbuf = (char *)DXAllocate(strlen(p->filename) + 20);
    if (!tbuf)
	return ERROR;

    frame = p->startframe ? *p->startframe : 1;
    sprintf(tbuf, "%s.%d", p->filename, frame);

    rc = _dxfstat_netcdf_file(tbuf);
    DXFree(tbuf);
    return rc;

}
 
Object _dxfget_ncdf(struct parmlist *p)
{
    int i, j, frno;
    int startframe, endframe, deltaframe;
    int incframe = 0;
    char dataset_name[512], numbuf[16];
    int overwrite = 0;
    Error firstrc = OK;
    char *firsterr = NULL;
    Series tsg = NULL;   /* time series group */
    Group grp = NULL;    /* generic group */
    Field f = NULL;
    Object o = NULL, retobj = NULL;
	
 
    /* the start/end frame aren't specified.
     */
    if(!p->startframe && !p->endframe && !p->deltaframe) {
	retobj = DXImportNetCDF(p->filename, p->fieldlist, NULL, NULL, NULL);
	goto done;
    }
 

#if 0 
    /* workaround a bug in DXImportNetCDF - if startframe == endframe, it
     * doesn't import any frames.
     */
    if (p->endframe && p->startframe && (*p->startframe == *p->endframe)) {
	incframe = 1;
        (*p->endframe)++;
    }
#endif
    
    retobj = DXImportNetCDF(p->filename, p->fieldlist, p->startframe, 
			   p->endframe, p->deltaframe);
    if(retobj)
	goto done;
    
    
    /* if that failed, try the old way for backward compability.
     *  the old way required a field name.
     */
    if (!p->fieldlist || !p->fieldlist[0])
	goto done;

    firstrc = DXGetError();
    firsterr = (char *)DXAllocateLocal(strlen(DXGetErrorMessage()) + 1);
    if (!firsterr)
	goto cleanup;

    strcpy(firsterr, DXGetErrorMessage());
    DXResetError();

#if 0
    if (incframe)
        (*p->endframe)--;
#endif
   
    startframe = (p->startframe ? *p->startframe : 1);
    endframe = (p->endframe ? *p->endframe : startframe);
    deltaframe = (p->deltaframe ? *p->deltaframe : 1);
 
    /* for each import field... */
    for(j=0; p->fieldlist[j]; j++) {
	
	/* if more than one member, make a group and put the field you
         *  already have into it as the first member.
	 */
	if(j > 0 && !grp) {
	    grp = DXNewGroup();
	    if(!grp)
		goto cleanup;
	    
	    if(!DXSetMember(grp, p->fieldlist[0], 
			      		(tsg ? (Object)tsg : (Object)f)))
		goto cleanup;
	}
 
	/* only create a series group if there is more than one frame */
	if(endframe > startframe) {
	    tsg = DXNewSeries();  
	    if(!tsg)
		goto cleanup;
	}
	
	for(i=0, frno=startframe; frno<=endframe; frno+=deltaframe, i++) {
	    char *fieldptr[2];
	    
	    strcpy(dataset_name, p->filename);
	    sprintf(numbuf, ".%d", frno);
	    strcat(dataset_name, numbuf);
	    
	    fieldptr[0] = p->fieldlist[j];
	    fieldptr[1] = NULL;
 
	    o = DXImportNetCDF(dataset_name, fieldptr, NULL, NULL, NULL);
	    if(!o) {
		overwrite = 1;
		goto cleanup;
	    }
	    
	    if(tsg)    
		tsg = DXSetSeriesMember(tsg, i, (double)frno, o);
	}
	
	/* add to group if there already is one */
	if(grp && !DXSetMember(grp, p->fieldlist[j], 
					(tsg ? (Object)tsg : (Object)f)))
	    goto cleanup;
    }
    
    /* if there is a group, return that.  if there is a series, return
     *  that.  else it must be a simple field.
     */
    if(grp)
	retobj = (Object)grp;
    else if(tsg)
	retobj = (Object)tsg;
    else
	retobj = (Object)o;
    
    goto done;
 
  cleanup:
    DXDelete((Object)grp);
    DXDelete((Object)tsg);
    DXDelete((Object)f);
    DXDelete((Object)o);

    /* if you failed a second time, restore the first error message 
     */
    if (overwrite == 1)
	DXSetError(firstrc, firsterr);

    /* fall thru */

  done:
    return retobj;
}
 
 
extern Error _dxfstat_hdf(char *filename);
extern int _dxfget_hdfcount(char *filename);
extern int _dxfwhich_hdf(char *filename,char *fieldname);

Error _dxftry_hdf(struct parmlist *p)
{
    char *tbuf;
    int frame, rc;

    /* if extension == .hdf, or 
     * file or file.hdf exists somewhere in curdir or DXDATA path, and
     * dfopen() works, then OK 
     */

    if (_dxfstat_hdf(p->filename) == OK)
	return OK;

    tbuf = (char *)DXAllocate(strlen(p->filename) + 20);
    if (!tbuf)
	return ERROR;

    frame = p->startframe ? *p->startframe : 1;
    sprintf(tbuf, "%s.%d", p->filename, frame);

    rc = _dxfstat_hdf(tbuf);
    DXFree(tbuf);
    return rc;
}
 
Object _dxfget_hdf(struct parmlist *p)
{
    int i, j, frno;
    int startframe, endframe, deltaframe;
    int setseries = 0, numflds=0, digit=0;
    char dataset_name[512], numbuf[16];
    char skip[16];
    Series tsg = NULL;   /* time series group */
    Group grp = NULL;    /* generic group */
    Field f = NULL;
    Object o = NULL, retobj = NULL;

#if 0  /* old code - try the first set in the file if no name given */ 
    /* HDF - no default field names because i don't know how to query
     *  the file for the fields in it.
     */
    
    /* field name is required */
    if(!p->fieldlist) {
	DXSetError(ERROR_BAD_PARAMETER, "variable required for HDF");
	goto done;
    }
#endif

    if (p->startframe || p->endframe || p->deltaframe)
	setseries++;

    startframe = (p->startframe ? *p->startframe : 1);
    endframe = (p->endframe ? *p->endframe : startframe);
    deltaframe = (p->deltaframe ? *p->deltaframe : 1);

    /* how many fields are there */
    if (p->fieldlist){
       for (i=0; p->fieldlist[i]; i++)
          ;
       numflds=i;
       /* is it a number */
       for (j=0; j<strlen(p->fieldlist[0]); j++){
          if (!isdigit((p->fieldlist[0])[j]))
	     break;
       }
       if (j==strlen(p->fieldlist[0])) digit=1;
       /* all members of fieldlist must be either number or not */
       for (i=0; i <numflds; i++){
	  for (j=0; j<strlen(p->fieldlist[i]); j++){
	     if (!isdigit((p->fieldlist[0])[j]))
	     break;
          }
	  if (j==strlen(p->fieldlist[0]) && !digit){
	     DXSetError(ERROR_BAD_PARAMETER,"variable list must be either number or name, not a mix");
	     return  NULL;
	  }
       }

    }
    else{
       numflds = _dxfget_hdfcount(p->filename);
       if (numflds==0)
          return NULL;
    }
    if (numflds>1) grp = DXNewGroup();
 
    /* for each input fieldname */
    for(j=0; j<numflds; j++) {
        if (p->fieldlist && digit)
	   strcpy(skip ,p->fieldlist[j]);
	else if (p->fieldlist){
	   i = _dxfwhich_hdf(p->filename,p->fieldlist[j]);
	   if (i<0)
	      return ERROR;
	   sprintf(skip,"%d",i);
	}
	else
	   sprintf(skip,"%d",j);
	
	/* only create a series group if there is more than one frame */
	if(endframe > startframe) {
	    tsg = DXNewSeries();  
	    if(!tsg)
		goto cleanup;
	} 
	
	/* for each frame of the series, import a field */
	for(i=0, frno=startframe; frno<=endframe; frno+=deltaframe, i++) {
	    
	    strcpy(dataset_name, p->filename);

	    if (setseries) {
		sprintf(numbuf, ".%d", frno);
		strcat(dataset_name, numbuf);
	    }
		
	    f = DXImportHDF(dataset_name, skip);
            if (!f)
              goto cleanup;
	     
	    if(tsg) {   
		tsg = DXSetSeriesMember(tsg, i, (double)frno, (Object)f);
		if(!tsg)
		    goto cleanup;
	    }
	    
	}
	
	/* if there is a group, add this as a member */
	if(grp && !DXSetMember(grp, skip, 
					(tsg ? (Object)tsg : (Object)f)))
	    goto cleanup;
	
    }

    /* if there is a group, return that.  if there is a series, return
     *  that.  else it must be a simple field.
     */
    if(grp)
	retobj = (Object)grp;
    else if(tsg)
	retobj = (Object)tsg;
    else
	retobj = (Object)f;
    
    goto done;
    
    
  cleanup:
    DXDelete((Object)grp);
    DXDelete((Object)tsg);
    DXDelete((Object)f);
    DXDelete((Object)o);
    /* fall thru */
    
  done:
    return retobj;
    
}
 
Error _dxftry_bin(struct parmlist *p)
{
    char *cp;

    /* if extension == .bin, or arraydisk:filename */
    
#ifndef DXD_OS_NON_UNIX
    if (strchr(p->filename, ':') != NULL)
	return OK;
#endif

    if ((cp = strrchr(p->filename, '.')) != NULL) {
	if (!strcmp(".bin", cp))
	    return OK;
    }

    return ERROR;
}
 
Object _dxfget_bin(struct parmlist *p)
{
    return _dxfImportBin(p->filename);
}
 
extern Error _dxftry_dxfile(char *inname);

Error _dxftry_dx(struct parmlist *p)
{
    /* if extension == .dx, or
     * file or file.dx exists somewhere in curdir or DXDATA path, and
     * DXImportDX() works, then OK 
     */

    return _dxftry_dxfile(p->filename);
}
 

Object _dxfget_dx(struct parmlist *p)
{
    /* Data Explorer external data format */
 
    return DXImportDX(p->filename, p->fieldlist, p->startframe,
		   p->endframe, p->deltaframe);
}

extern Error_dxfstat_cdf(char *filename);

Error _dxftry_cdf(struct parmlist *p)
{
    /* if extension == .cdf */
    /* file or file.cdf exists somewhere in curdir or DXDATA path, and */
    /* cdfopen() works, then OK */
    return _dxfstat_cdf(p->filename); 
}
 
Object _dxfget_cdf(struct parmlist *p)
{
    /* CDF importer */

    return DXImportCDF(p->filename,p->fieldlist,p->startframe,
		p->endframe,p->deltaframe);
}

Object _dxfget_cm(struct parmlist *p)
{
   return DXImportCM(p->filename,p->fieldlist);
}

Error  _dxftry_wv(struct parmlist *p) 
{ 
    return ERROR; 
}

Object _dxfget_wv(struct parmlist *p) 
{ 
    DXSetError(ERROR_NOT_IMPLEMENTED, "winvis90 not supported");
    return NULL; 
}

