#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <limits.h>

#include "dmemory.h"
#include "cdi.h"
#include "cdi_int.h"
#include "model.h"
#include "namespace.h"
#include "resource_handle.h"
#include "resource_unpack.h"
#include "serialize.h"

#undef  CDI_UNDEFID
#define CDI_UNDEFID -1

typedef struct
{
  int      self;
  int      used;
  int      instID;
  int      modelgribID;
  char    *name;
}
model_t;


static int  MODEL_Debug = 0;   /* If set to 1, debugging */

static void modelInit(void);


static int modelCompareP(void *modelptr1, void *modelptr2);
static void   modelDestroyP ( void * modelptr );
static void   modelPrintP   ( void * modelptr, FILE * fp );
static int    modelGetSizeP ( void * modelptr, void *context);
static void   modelPackP    ( void * modelptr, void * buff, int size,
                              int *position, void *context);
static int    modelTxCode   ( void );

static const resOps modelOps = {
  modelCompareP,
  modelDestroyP,
  modelPrintP,
  modelGetSizeP,
  modelPackP,
  modelTxCode
};

static
void modelDefaultValue ( model_t *modelptr )
{
  modelptr->self        = CDI_UNDEFID;
  modelptr->used        = 0;
  modelptr->instID      = CDI_UNDEFID;
  modelptr->modelgribID = CDI_UNDEFID;
  modelptr->name        = NULL;
}

static model_t *
modelNewEntry(cdiResH resH, int instID, int modelgribID, const char *name)
{
  model_t *modelptr = (model_t *) Malloc(sizeof(model_t));
  modelDefaultValue ( modelptr );
  if (resH == CDI_UNDEFID)
    modelptr->self = reshPut(modelptr, &modelOps);
  else
    {
      modelptr->self = resH;
      reshReplace(resH, modelptr, &modelOps);
    }
  modelptr->used = 1;
  modelptr->instID = instID;
  modelptr->modelgribID = modelgribID;
  if ( name && *name ) modelptr->name = strdupx(name);

  return (modelptr);
}

void modelDefaultEntries ( void )
{
  int instID;
  enum { nDefModels = 10 };
  cdiResH resH[nDefModels];

  instID  = institutInq(  0,   0, "ECMWF", NULL);
  /* (void)    modelDef(instID, 131, "ERA15"); */
  /* (void)    modelDef(instID, 199, "ERA40"); */

  instID  = institutInq( 98, 232, "MPIMET", NULL);
  resH[0] = modelDef(instID,  64, "ECHAM5.4");
  resH[1] = modelDef(instID,  63, "ECHAM5.3");
  resH[2] = modelDef(instID,  62, "ECHAM5.2");
  resH[3] = modelDef(instID,  61, "ECHAM5.1");

  instID  = institutInq( 98, 255, "MPIMET", NULL);
  resH[4] = modelDef(instID,  60, "ECHAM5.0");
  resH[5] = modelDef(instID,  50, "ECHAM4");
  resH[6] = modelDef(instID, 110, "MPIOM1");

  instID  = institutInq(  0,   0, "DWD", NULL);
  resH[7] = modelDef(instID, 149, "GME");

  instID  = institutInq(  0,   0, "MCH", NULL);
  //(void)  = modelDef(instID, 137, "COSMO");
  resH[8] = modelDef(instID, 255, "COSMO");

  instID  = institutInq(  0,   1, "NCEP", NULL);
  resH[9] = modelDef(instID,  80, "T62L28MRF");

  /* pre-defined models are not synchronized */
  for ( int i = 0; i < nDefModels ; i++ )
    reshSetStatus(resH[i], &modelOps, RESH_IN_USE);
}

static
void modelInit(void)
{
  static bool modelInitialized = false;

  if (modelInitialized) return;

  modelInitialized = true;
  char *env = getenv("MODEL_DEBUG");
  if ( env ) MODEL_Debug = atoi(env);
}

struct modelLoc
{
  const char *name;
  int instID, modelgribID, resID;
};

static enum cdiApplyRet
findModelByID(int resID, void *res, void *data)
{
  model_t *modelptr = (model_t*) res;
  struct modelLoc *ret = (struct modelLoc*) data;
  int instID = ret->instID, modelgribID = ret->modelgribID;
  if (modelptr->used
      && modelptr->instID == instID
      && modelptr->modelgribID == modelgribID)
    {
      ret->resID = resID;
      return CDI_APPLY_STOP;
    }
  else
    return CDI_APPLY_GO_ON;
}

static enum cdiApplyRet
findModelByName(int resID, void *res, void *data)
{
  model_t *modelptr = (model_t*) res;
  struct modelLoc *ret = (struct modelLoc*) data;
  int instID = ret->instID, modelgribID = ret->modelgribID;
  const char *name = ret->name;
  if (modelptr->used
      && (instID == -1 || modelptr->instID == instID)
      && (modelgribID == 0 || modelptr->modelgribID == modelgribID)
      && modelptr->name)
    {
      const char *p = name, *q = modelptr->name;
      while (*p != '\0' && *p == *q)
        ++p, ++q;
      if (*p == '\0' || *q == '\0')
        {
          ret->resID = resID;
          return CDI_APPLY_STOP;
        }
    }
  return CDI_APPLY_GO_ON;
}

int modelInq(int instID, int modelgribID, const char *name)
{
  modelInit ();

  struct modelLoc searchState = { .name = name, .instID = instID,
                                  .modelgribID = modelgribID,
                                  .resID = CDI_UNDEFID };
  if (name && *name)
    cdiResHFilterApply(&modelOps, findModelByName, &searchState);
  else
    cdiResHFilterApply(&modelOps, findModelByID, &searchState);
  return searchState.resID;
}


int modelDef(int instID, int modelgribID, const char *name)
{
  model_t *modelptr;

  modelInit ();

  modelptr = modelNewEntry(CDI_UNDEFID, instID, modelgribID, name);

  return modelptr->self;
}


int modelInqInstitut(int modelID)
{
  model_t *modelptr = NULL;

  modelInit ();

  if ( modelID != CDI_UNDEFID )
    modelptr = ( model_t * ) reshGetVal ( modelID, &modelOps );

  return modelptr ? modelptr->instID : CDI_UNDEFID;
}


int modelInqGribID(int modelID)
{
  model_t *modelptr = NULL;

  modelInit ();

  if ( modelID != CDI_UNDEFID )
    modelptr = ( model_t * ) reshGetVal ( modelID, &modelOps );

  return modelptr ? modelptr->modelgribID : CDI_UNDEFID;
}


const char *modelInqNamePtr(int modelID)
{
  model_t *modelptr = NULL;

  modelInit ();

  if ( modelID != CDI_UNDEFID )
    modelptr = ( model_t * ) reshGetVal ( modelID, &modelOps );

  return modelptr ? modelptr->name : NULL;
}


static int
modelCompareP(void *modelptr1, void *modelptr2)
{
  model_t *model1 = (model_t *)modelptr1, *model2 = (model_t *)modelptr2;
  int diff = (namespaceResHDecode(model1->instID).idx
              != namespaceResHDecode(model2->instID).idx)
    | (model1->modelgribID != model2->modelgribID)
    | (strcmp(model1->name, model2->name) != 0);
  return diff;
}


void modelDestroyP ( void * modelptr )
{
  model_t *mp = (model_t*) modelptr;
  if (mp->name)
    Free(mp->name);
  Free(mp);
}


void modelPrintP   ( void * modelptr, FILE * fp )
{
  model_t *mp = (model_t*) modelptr;

  if ( !mp ) return;

  fprintf ( fp, "#\n");
  fprintf ( fp, "# modelID %d\n", mp->self);
  fprintf ( fp, "#\n");
  fprintf ( fp, "self          = %d\n", mp->self );
  fprintf ( fp, "used          = %d\n", mp->used );
  fprintf ( fp, "instID        = %d\n", mp->instID );
  fprintf ( fp, "modelgribID   = %d\n", mp->modelgribID );
  fprintf ( fp, "name          = %s\n", mp->name ? mp->name : "NN" );
}


static int
modelTxCode ( void )
{
  return MODEL;
}

enum {
  model_nints = 4,
};


static int modelGetSizeP(void * modelptr, void *context)
{
  model_t *p = (model_t*)modelptr;
  size_t txsize = (size_t)serializeGetSize(model_nints, CDI_DATATYPE_INT, context)
    + (size_t)serializeGetSize(p->name?(int)strlen(p->name) + 1:0, CDI_DATATYPE_TXT, context);
  xassert(txsize <= INT_MAX);
  return (int)txsize;
}


static void modelPackP(void * modelptr, void * buf, int size, int *position, void *context)
{
  model_t *p = (model_t*) modelptr;
  int tempbuf[model_nints];
  tempbuf[0] = p->self;
  tempbuf[1] = p->instID;
  tempbuf[2] = p->modelgribID;
  tempbuf[3] = p->name ? (int)strlen(p->name) + 1 : 0;
  serializePack(tempbuf, model_nints, CDI_DATATYPE_INT, buf, size, position, context);
  if (p->name)
    serializePack(p->name, tempbuf[3], CDI_DATATYPE_TXT, buf, size, position, context);
}

int
modelUnpack(void *buf, int size, int *position, int originNamespace, void *context,
            int force_id)
{
  int tempbuf[model_nints];
  char *name;
  serializeUnpack(buf, size, position, tempbuf, model_nints, CDI_DATATYPE_INT, context);
  if (tempbuf[3] != 0)
    {
      name = (char *) Malloc((size_t)tempbuf[3]);
      serializeUnpack(buf, size, position,
                      name, tempbuf[3], CDI_DATATYPE_TXT, context);
    }
  else
    {
      name = (char*)"";
    }
  int targetID = namespaceAdaptKey(tempbuf[0], originNamespace);
  model_t *mp = modelNewEntry(force_id?targetID:CDI_UNDEFID,
                              namespaceAdaptKey(tempbuf[1], originNamespace),
                              tempbuf[2], name);
  if (tempbuf[3] != 0)
    Free(name);
  xassert(!force_id
          || (mp->self == namespaceAdaptKey(tempbuf[0], originNamespace)));
  reshSetStatus(mp->self, &modelOps,
                reshGetStatus(mp->self, &modelOps) & ~RESH_SYNC_BIT);
  return mp->self;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
