/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

  Copyright (C) 2003-2009 Uwe Schulzweida, Uwe.Schulzweida@zmaw.de
  See COPYING file for copying and redistribution conditions.

  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; version 2 of the License.

  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.
*/

/*
   This module contains the following operators:

      Select2     select         Select fields
*/


#include <string.h>
#include <math.h>   /* fabs */

#include "cdi.h"
#include "cdo.h"
#include "cdo_int.h"
#include "pstream.h"
#include "error.h"
#include "util.h"
#include "functs.h"
#include "list.h"
#include "namelist.h"

void    vlistDefVarTime(int vlistID, int varID, int timeID);


#define  PML_INT         1
#define  PML_FLT         2
#define  PML_WORD        3
#define  PML_DATE        4
#define  PML_TIME        4

typedef struct {
  char *name;
  int maxpar;
  int numpar;
  char *par;
}
PARAMETER;

#define PML_DEF(name, size, txt)      int flag_##name[size]; int npar_##name = 0; int max_##name = size; const char str_##name[] = txt
#define PML_DEF_INT(name, size, txt)  int par_##name[size]; int name = 0; PML_DEF(name, size, txt)
#define PML_DEF_FLT(name, size, txt)  double par_##name[size]; double name = 0; PML_DEF(name, size, txt)
#define PML_DEF_WORD(name, size, txt) char *par_##name[size]; char *name = 0; PML_DEF(name, size, txt)
#define PML_INIT_INT(name)            memset(flag_##name, 0, max_##name * sizeof(int));
#define PML_INIT_FLT(name)            memset(flag_##name, 0, max_##name * sizeof(int));
#define PML_INIT_WORD(name)           memset(flag_##name, 0, max_##name * sizeof(int));
#define PML_ADD_INT(nml, name)  pmlAdd(nml, #name, PML_INT,  0, par_##name, sizeof(par_##name)/sizeof(int))
#define PML_ADD_FLT(nml, name)  pmlAdd(nml, #name, PML_FLT,  0, par_##name, sizeof(par_##name)/sizeof(double))
#define PML_ADD_WORD(nml, name) pmlAdd(nml, #name, PML_WORD, 0, par_##name, sizeof(par_##name)/sizeof(char *))
#define PML_NUM(nml, name)            npar_##name = pmlNum(nml, #name)
#define PML_PAR(name)                 npar_##name, par_##name, name
#define PAR_CHECK_INT_FLAG(name)      par_check_int_flag(npar_##name, par_##name, flag_##name, str_##name)
#define PAR_CHECK_FLT_FLAG(name)      par_check_flt_flag(npar_##name, par_##name, flag_##name, str_##name)
#define PAR_CHECK_WORD_FLAG(name)     par_check_word_flag(npar_##name, par_##name, flag_##name, str_##name)
#define PAR_CHECK_INT(name)           par_check_int(npar_##name, par_##name, flag_##name, name)
#define PAR_CHECK_FLT(name)           par_check_flt(npar_##name, par_##name, flag_##name, name)
#define PAR_CHECK_WORD(name)          par_check_word(npar_##name, par_##name, flag_##name, name)

static PARAMETER Parameter[] =
{
  {"code", 1024, 0, NULL},
};

static int NumParameter = sizeof(Parameter) / sizeof(Parameter[0]);

#define MAX_PML_ENTRY  256

typedef struct
{
  char *name;
  size_t len;
  void *ptr;
  int type;
  int occ;
  int dis;
  size_t size;
} pml_entry_t;


typedef struct
{
  int size;
  int dis;
  char *name;
  /* PML_LINE line; */
  pml_entry_t *entry[MAX_PML_ENTRY];
} pml_t;


static void pml_init(pml_t *pml, const char *name)
{
  static char func[] = "pml_init";
  pml->size = 0;
  pml->dis  = 1;
  pml->name = strdup(name);
}


pml_t *pmlNew(const char *name)
{
  static char func[] = "pmlNew";
  pml_t *pml;

  pml = (pml_t *) malloc(sizeof(pml_t));

  pml_init(pml, name);

  return (pml);
}


void pmlPrint(pml_t *pml)
{
  pml_entry_t *entry;
  int i, j, nout;

  if ( pml == NULL ) return;

  fprintf(stdout, "Parameter list: %s\n", pml->name);
  fprintf(stdout, " Num  Name             Type  Size   Dis   Occ  Entries\n");

  for ( i = 0; i < pml->size; ++i )
    {
      entry = pml->entry[i];
      fprintf(stdout, "%4d  %-16s %4d  %4d  %4d  %4d ",
	      i+1, pml->entry[i]->name, pml->entry[i]->type, (int)pml->entry[i]->size,
	      pml->entry[i]->dis, pml->entry[i]->occ);
      nout = pml->entry[i]->occ;
      if ( nout > 8 ) nout = 8;

      if      ( entry->type == PML_WORD )
	for ( j = 0; j < nout; j++ )
	  fprintf(stdout, " %s", ((char **)entry->ptr)[j]);
      else if ( entry->type == PML_INT )
	for ( j = 0; j < nout; j++ )
	  fprintf(stdout, " %d", ((int *)entry->ptr)[j]);
      else if ( entry->type == PML_FLT )
	for ( j = 0; j < nout; j++ )
	  fprintf(stdout, " %g", ((double *)entry->ptr)[j]);
      
      fprintf(stdout, "\n");
    }
}


int pmlAdd(pml_t *pml, const char *name, int type, int dis, void *ptr, size_t size)
{
  static char func[] = "pmlAdd";
  pml_entry_t *pml_entry;
  int entry = 0;

  if ( pml->size >= MAX_PML_ENTRY )
    {
      fprintf(stderr, "Too many entries in parameter list %s! (Max = %d)\n", pml->name, MAX_PML_ENTRY);
      return (-1);
    }

  pml_entry = (pml_entry_t *) malloc(sizeof(pml_entry_t));

  pml_entry->name = strdup(name);
  pml_entry->len  = strlen(name);
  pml_entry->type = type;
  pml_entry->ptr  = ptr;
  pml_entry->size = size;
  pml_entry->dis  = dis;
  pml_entry->occ  = 0;

  entry = pml->size;
  pml->entry[pml->size++] = pml_entry;

  return (entry);
}


int pmlNum(pml_t *pml, const char *name)
{
  pml_entry_t *entry;
  int i, nocc = 0;

  if ( pml == NULL ) return (nocc);

  for ( i = 0; i < pml->size; i++ )
    {
      entry = pml->entry[i];
      if ( strcmp(name, entry->name) == 0 )
	{
	  nocc = entry->occ;
	  break;
	}
    }

  if ( i == pml->size )
    fprintf(stderr, "Parameter list entry %s not found in %s\n", name, pml->name);

  return (nocc);
}


int pml_add_entry(pml_entry_t *entry, char *arg)
{
  static char func[] = "pml_add_entry";
  int status = 0;

  if ( entry->type == PML_INT )
    {
      if ( entry->occ < (int) entry->size )
	((int *) entry->ptr)[entry->occ++] = atoi(arg);
    }
  else if ( entry->type == PML_FLT )
    {
      if ( entry->occ < (int) entry->size )
	((double *) entry->ptr)[entry->occ++] = atof(arg);
    }
  else if ( entry->type == PML_WORD )
    {
      if ( entry->occ < (int) entry->size )
	((char **) entry->ptr)[entry->occ++] = strdupx(arg);
    }
  else
    {
      fprintf(stderr, "unsupported type!\n");
    }

  return (status);
}


void pmlProcess(pml_entry_t *entry, int argc, char **argv)
{
  int i;
  int len;
  char *parg;
  char *epos;

  for ( i = 0; i < argc; ++i )
    {
      parg = argv[i];
      if ( i == 0 )
	{
	  epos = strchr(parg, '=');
	  if ( epos == NULL )
	    {
	      fprintf(stderr, "internal problem, keyword not found!\n");
	    }
	  parg += epos-parg+1;
	}

      pml_add_entry(entry, parg);
    }
}


int pmlRead(pml_t *pml, int argc, char **argv)
{
  static char func[] = "pmlRead";
  pml_entry_t *entry = NULL;
  pml_entry_t *pentry[MAX_PML_ENTRY];
  int params[MAX_PML_ENTRY];
  int num_par[MAX_PML_ENTRY];
  int len_par[MAX_PML_ENTRY];
  int nparams = 0;
  int i, istart;
  char *epos;
  size_t len;
  char *parbuf;
  int bufsize = 0;
  int status = 0;

  if ( cdoVerbose )
    for ( i = 0; i < argc; ++i ) printf("pmlRead: %d %s\n", i, argv[i]);

  for ( i = 0; i < argc; ++i )
    {
      len = strlen(argv[i]);
      len_par[i] = (int)len;
      bufsize += len+1;
    }

  parbuf = (char *) malloc(bufsize*sizeof(char));
  memset(parbuf, 0, bufsize*sizeof(char));

  istart = 0;
  while ( istart < argc )
    {

      epos = strchr(argv[istart], '=');
      if ( epos == NULL )
	{
	  fprintf(stderr, "Parameter >%s< has no keyword!\n", argv[istart]);
	  status = 1;
	  goto END_LABEL;
	}

      len = epos - argv[istart];
      for ( i = 0; i < pml->size; ++i )
	{
	  entry = pml->entry[i];
	  if ( entry->len == len )
	    if ( memcmp(entry->name, argv[istart], len) == 0 ) break;
	}

      if ( i == pml->size )
	{
	  fprintf(stderr, "Parameter >%s< not available!\n", argv[istart]);
	  status = 2;
	  goto END_LABEL;
	}

      num_par[nparams] = 0;
      pentry[nparams]  = entry;
      params[nparams]  = istart;
      num_par[nparams] = 1;
      
      istart++;
      for ( i = istart; i < argc; ++i )
	{
	  epos = strchr(argv[i], '=');
	  if ( epos != NULL ) break;

	  num_par[nparams]++;
	}

      istart = i;

      nparams++;
    }

  for ( i = 0; i < nparams; ++i )
    {
      pmlProcess(pentry[i], num_par[i], &argv[params[i]]);
    }


 END_LABEL:

  free(parbuf);

  return (status);
}


int par_check_int(int npar, int *parlist, int *flaglist, int par)
{
  int i, found;

  found = 0;
  for ( i = 0; i < npar; i++ )
    if ( par == parlist[i] ) { found = 1; flaglist[i] = TRUE; break; }

  return (found);
}


int par_check_flt(int npar, double *parlist, int *flaglist, double par)
{
  int i, found;

  found = 0;
  for ( i = 0; i < npar; i++ )
    if ( fabs(par - parlist[i]) < 1.e-4 ) { found = 1; flaglist[i] = TRUE; break; }

  return (found);
}


int par_check_word(int npar, char **parlist, int *flaglist, char *par)
{
  int i, found;

  found = 0;
  for ( i = 0; i < npar; i++ )
    if ( strcmp(par, parlist[i]) == 0 ) { found = 1; flaglist[i] = TRUE; break; }

  return (found);
}


void par_check_int_flag(int npar, int *parlist, int *flaglist, const char *txt)
{
  int i;

  for ( i = 0; i < npar; ++i )
    if ( flaglist[i] == FALSE )
      cdoWarning("%s %d not found!", txt, parlist[i]);
}


void par_check_flt_flag(int npar, double *parlist, int *flaglist, const char *txt)
{
  int i;

  for ( i = 0; i < npar; ++i )
    if ( flaglist[i] == FALSE )
      cdoWarning("%s %g not found!", txt, parlist[i]);
}


void par_check_word_flag(int npar, char **parlist, int *flaglist, const char *txt)
{
  int i;

  for ( i = 0; i < npar; ++i )
    if ( flaglist[i] == FALSE )
      cdoWarning("%s %s not found!", txt, parlist[i]);
}


void *Select2(void *argument)
{
  const char func[] = "Select2";
  int SELECT;
  int operatorID;
  int streamID1, streamID2 = CDI_UNDEFID;
  int tsID1, tsID2, nrecs;
  int nvars, nlevs;
  int zaxisID, levID;
  int varID2, levelID2;
  int recID, varID, levelID;
  int nsel;
  char varname[256];
  char stdname[256];
  char **argnames = NULL;
  int vlistID0 = -1, vlistID1 = -1, vlistID2 = -1;
  int i;
  int lcopy = FALSE;
  int gridsize;
  int nmiss;
  int streamCnt, nfiles, indf;
  double *array = NULL;
  int taxisID1, taxisID2 = CDI_UNDEFID;
  int ntsteps;
  int npar;
  int *vars = NULL;
  pml_t *pml;
  PML_DEF_INT(code,    1024, "Code number");
  PML_DEF_FLT(level,   1024, "Level");
  PML_DEF_WORD(param,  1024, "Parameter");

  PML_INIT_INT(code);
  PML_INIT_FLT(level);
  PML_INIT_WORD(param);

  cdoInitialize(argument);

  SELECT  = cdoOperatorAdd("select", 0, 0, "parameter list");

  if ( UNCHANGED_RECORD ) lcopy = TRUE;

  operatorID = cdoOperatorID();

  operatorInputArg(cdoOperatorEnter(operatorID));

  nsel     = operatorArgc();
  argnames = operatorArgv();

  if ( cdoVerbose )
    for ( i = 0; i < nsel; i++ )
      printf("name %d = %s\n", i+1, argnames[i]);

  pml = pmlNew("SELECT");

  PML_ADD_INT(pml, code);
  PML_ADD_FLT(pml, level);
  PML_ADD_WORD(pml, param);

  pmlRead(pml, nsel, argnames);

  if ( cdoVerbose ) pmlPrint(pml);

  PML_NUM(pml, code);
  PML_NUM(pml, level);
  PML_NUM(pml, param);
  /*
  pmlDelete(pml);
  */

  streamCnt = cdoStreamCnt();
  nfiles = streamCnt - 1;

  tsID2 = 0;
  for ( indf = 0; indf < nfiles; indf++ )
    {
      if ( cdoVerbose ) cdoPrint("Process file: %s", cdoStreamName(indf));

      streamID1 = streamOpenRead(cdoStreamName(indf));
      if ( streamID1 < 0 ) cdiError(streamID1, "Open failed on %s", cdoStreamName(indf));

      vlistID1 = streamInqVlist(streamID1);
      taxisID1 = vlistInqTaxis(vlistID1);

      if ( indf == 0 )
	{
	  vlistClearFlag(vlistID1);
	  nvars = vlistNvars(vlistID1);
	  vars = (int *) malloc(nvars*sizeof(int));

	  for ( varID = 0; varID < nvars; varID++ )
	    {
	      vlistInqVarName(vlistID1, varID, varname);
	      param = varname;
	      vlistInqVarStdname(vlistID1, varID, stdname);
	      code    = vlistInqVarCode(vlistID1, varID);
	      vars[varID] = FALSE;
	      
	      if ( npar_code  && PAR_CHECK_INT(code) )   vars[varID] = TRUE;
	      if ( npar_param && PAR_CHECK_WORD(param) ) vars[varID] = TRUE;
	    }

	  for ( varID = 0; varID < nvars; varID++ )
	    {
	      if ( vars[varID] )
		{
		  zaxisID = vlistInqVarZaxis(vlistID1, varID);
		  nlevs   = zaxisInqSize(zaxisID);

		  for ( levID = 0; levID < nlevs; levID++ )
		    {
		      level = zaxisInqLevel(zaxisID, levID);
		      
		      if ( nlevs == 1 && IS_EQUAL(level, 0) )
			{
			  vlistDefFlag(vlistID1, varID, levID, TRUE);
			}
		      else
			{
			  if ( npar_level )
			    {
			      if ( PAR_CHECK_FLT(level) )
				vlistDefFlag(vlistID1, varID, levID, TRUE);
			    }
			  else
			    {
			      vlistDefFlag(vlistID1, varID, levID, TRUE);
			    }
			}
		    }
		}
	    }

	  PAR_CHECK_INT_FLAG(code);
	  PAR_CHECK_FLT_FLAG(level);
	  PAR_CHECK_WORD_FLAG(param);

	  npar = 0;
	  for ( varID = 0; varID < nvars; varID++ )
	    {
	      zaxisID = vlistInqVarZaxis(vlistID1, varID);
	      nlevs   = zaxisInqSize(zaxisID);

	      for ( levID = 0; levID < nlevs; levID++ )
		if ( vlistInqFlag(vlistID1, varID, levID) == TRUE ) break;
	      
	      if ( levID < nlevs ) npar++;
	    }
      

	  if ( npar == 0 )
	    cdoAbort("No parameter selected!");


	  if ( cdoVerbose ) vlistPrint(vlistID1);

	  vlistID0 = vlistDuplicate(vlistID1);
	  for ( varID = 0; varID < nvars; varID++ )
	    {
	      zaxisID = vlistInqVarZaxis(vlistID1, varID);
	      nlevs   = zaxisInqSize(zaxisID);
	      for ( levID = 0; levID < nlevs; levID++ )
		if ( vlistInqFlag(vlistID1, varID, levID) == TRUE )
		  vlistDefFlag(vlistID0, varID, levID, TRUE);
	    }

	  if ( cdoVerbose ) vlistPrint(vlistID0);

	  vlistID2 = vlistCreate();
	  vlistCopyFlag(vlistID2, vlistID1);

	  if ( cdoVerbose ) vlistPrint(vlistID2);

	  taxisID2 = taxisDuplicate(taxisID1);
	  vlistDefTaxis(vlistID2, taxisID2);

	  ntsteps = vlistNtsteps(vlistID1);
	  if ( ntsteps == 0 && nfiles > 1 )
	    {	      
	      for ( varID = 0; varID < nvars; ++varID )
		vlistDefVarTime(vlistID2, varID, TIME_VARIABLE);
	    }

	  streamID2 = streamOpenWrite(cdoStreamName(nfiles), cdoFiletype());
	  if ( streamID2 < 0 ) cdiError(streamID2, "Open failed on %s", cdoStreamName(nfiles));

	  streamDefVlist(streamID2, vlistID2);

	  if ( ! lcopy )
	    {
	      gridsize = vlistGridsizeMax(vlistID1);
	      array = (double *) malloc(gridsize*sizeof(double));
	    }
	}
      else
	{
	  vlistCompare(vlistID0, vlistID1, func_sft);
	  /* vlistCompare(vlistID1, vlistID2, func_hrd); */
	}

      tsID1 = 0;
      while ( (nrecs = streamInqTimestep(streamID1, tsID1)) )
	{
	  taxisCopyTimestep(taxisID2, taxisID1);

	  streamDefTimestep(streamID2, tsID2);
     
	  for ( recID = 0; recID < nrecs; recID++ )
	    {
	      streamInqRecord(streamID1, &varID, &levelID);
	      if ( vlistInqFlag(vlistID0, varID, levelID) == TRUE )
		{
		  varID2   = vlistFindVar(vlistID2, varID);
		  levelID2 = vlistFindLevel(vlistID2, varID, levelID);

		  streamDefRecord(streamID2, varID2, levelID2);
		  if ( lcopy )
		    {
		      streamCopyRecord(streamID2, streamID1);
		    }
		  else
		    {
		      streamReadRecord(streamID1, array, &nmiss);
		      streamWriteRecord(streamID2, array, nmiss);
		    }
		}
     	    }

	  tsID1++;
	  tsID2++;
	}
      
      streamClose(streamID1);
    }

  streamClose(streamID2);
 
  vlistDestroy(vlistID0);
  vlistDestroy(vlistID2);

  if ( array ) free(array);
  if ( vars ) free(vars);

  cdoFinish();

  return (NULL);
}
