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

  Copyright (C) 2003-2018 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.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.
*/
#include <cdo_int.h>
#include "sellist.h"

//#define SELDEBUG 1

int
get_ival(const char *intstr, int idefault, int istart, int iend, int *ilast)
{
  int i;
  int ival = idefault;

  for (i = istart; i < iend; i++)
    {
      if (!(isdigit(intstr[i]) || intstr[i] == '-'))
        {
          if (intstr[i] == '/')
            ival = parameter2intlist(intstr + i + 1);
          else
            fprintf(stderr, "Syntax error in >%.*s<! Character %c not allowed.\n", iend, intstr, intstr[i]);
          break;
        }
    }

  *ilast = i;

  return ival;
}

void
split_intstring(const char *intstr, int *first, int *last, int *inc)
{
  int istrlen = strlen(intstr);
  *first = parameter2intlist(intstr);
  *last = *first;
  *inc = 1;

  int i, start = 1;
  *last = get_ival(intstr, *first, start, istrlen, &i);

  if (i < istrlen)
    {
      start = i + 1;
      *inc = get_ival(intstr, 1, start, istrlen, &i);
    }
}

sellist_t *
sellist_create(list_t *kvlist)
{
  sellist_t *sellist = (sellist_t *) Malloc(sizeof(sellist_t));
  sellist->size = list_size(kvlist);
  sellist->entry = (selentry_t *) Malloc(sellist->size * sizeof(selentry_t));

  int i = 0;
  for (listNode_t *kvnode = kvlist->head; kvnode; kvnode = kvnode->next)
    {
      keyValues_t *kv = *(keyValues_t **) kvnode->data;
      selentry_t *e = &(sellist->entry[i]);
      e->key = kv->key;
      e->values = kv->values;
      e->nvalues = kv->nvalues;
#ifdef SELDEBUG
      printf("%s =", e->key);
      for (int ii = 0; ii < e->nvalues; ++ii) printf(" '%s'", e->values[ii]);
      printf("\n");
#endif
      ++i;
    }

  for (int i = 0; i < sellist->size; ++i)
    {
      selentry_t *e = &(sellist->entry[i]);
      e->flag = NULL;
      e->cvalues = NULL;
#ifdef SELDEBUG
      printf("%s =", e->key);
      for (int ii = 0; ii < e->nvalues; ++ii) printf(" '%s'", e->values[ii]);
      printf("\n");
#endif
    }

  return sellist;
}

void
sellist_destroy(sellist_t *sellist)
{
  if (sellist)
    {
      for (int i = 0; i < sellist->size; ++i)
        {
          selentry_t *e = &(sellist->entry[i]);
          if (e->txt) Free(e->txt);
          if (e->flag) Free(e->flag);
          if (e->cvalues) Free(e->cvalues);
        }

      Free(sellist);
    }
}

void
sellist_verify(sellist_t *sellist)
{
  if (sellist)
    {
      for (int i = 0; i < sellist->size; ++i)
        {
          selentry_t *e = &(sellist->entry[i]);
          if (e->type == 0) cdoAbort("Unsupported selection keyword: '%s'!", e->key);
        }
    }
}

int
sellist_add(sellist_t *sellist, const char *txt, const char *name, int type)
{
  int idx = -1;

  if (sellist)
    {
      for (int i = 0; i < sellist->size; ++i)
        {
          const char *key = sellist->entry[i].key;
          if (strcmp(key, name) == 0)
            {
              idx = i;
              break;
            }
        }

      if (idx >= 0 && idx < sellist->size)
        {
          selentry_t *e = &(sellist->entry[idx]);
          e->type = type;
          e->txt = strdup(txt);
          if (e->nvalues && e->cvalues == NULL)
            {
              switch (type)
                {
                case SELLIST_INT: e->cvalues = Malloc(e->nvalues * sizeof(int)); break;
                case SELLIST_FLT: e->cvalues = Malloc(e->nvalues * sizeof(double)); break;
                case SELLIST_WORD: e->cvalues = Malloc(e->nvalues * sizeof(char *)); break;
                }
            }

          int j = 0;
          int nvalues = e->nvalues;
          for (int i = 0; i < nvalues; ++i) switch (type)
              {
              case SELLIST_INT:
                {
                  int first, last, inc;
                  split_intstring(e->values[i], &first, &last, &inc);

                  if (first == last)
                    {
                      ((int *) e->cvalues)[j++] = first;
                    }
                  else
                    {
                      int k = 0;
                      if (inc >= 0)
                        for (int ival = first; ival <= last; ival += inc) k++;
                      else
                        for (int ival = first; ival >= last; ival += inc) k++;

                      e->nvalues += k - 1;
                      if (e->nvalues)
                        {
                          e->cvalues = Realloc(e->cvalues, e->nvalues * sizeof(int));

                          if (inc >= 0)
                            {
                              for (int ival = first; ival <= last; ival += inc) ((int *) e->cvalues)[j++] = ival;
                            }
                          else
                            {
                              for (int ival = first; ival >= last; ival += inc) ((int *) e->cvalues)[j++] = ival;
                            }
                        }
                    }

                  break;
                }
              case SELLIST_FLT: ((double *) e->cvalues)[i] = parameter2double(e->values[i]); break;
              case SELLIST_WORD: ((const char **) e->cvalues)[i] = parameter2word(e->values[i]); break;
              }

          if (e->nvalues) e->flag = (bool *) Calloc(e->nvalues, sizeof(bool));
#ifdef SELDEBUG
          printf("%s =", e->key);
          for (int i = 0; i < e->nvalues; ++i) switch (type)
              {
              case SELLIST_INT: printf(" %d", ((int *) e->cvalues)[i]); break;
              case SELLIST_FLT: printf(" %g", ((double *) e->cvalues)[i]); break;
              case SELLIST_WORD: printf(" %s", ((char **) e->cvalues)[i]); break;
              }
          printf("\n");
#endif
        }
    }

  return idx;
}

int
sellist_nvalues(sellist_t *sellist, int idx)
{
  int nvalues = 0;

  if (sellist && idx >= 0 && idx < sellist->size) nvalues = sellist->entry[idx].nvalues;

  return nvalues;
}

void
sellist_check_flag(sellist_t *sellist, int idx)
{
  if (idx < 0 || idx >= sellist->size) return;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      selentry_t *e = &(sellist->entry[idx]);
      for (int i = 0; i < nvalues; ++i)
        if (e->flag[i] == false) switch (e->type)
            {
            case SELLIST_INT: cdoWarning("%s >%d< not found!", e->txt, ((int *) e->cvalues)[i]); break;
            case SELLIST_FLT: cdoWarning("%s >%g< not found!", e->txt, ((double *) e->cvalues)[i]); break;
            case SELLIST_WORD: cdoWarning("%s >%s< not found!", e->txt, ((char **) e->cvalues)[i]); break;
            }
    }
}

bool
sellist_check(sellist_t *sellist, int idx, void *par)
{
  bool found = false;

  if (idx < 0 || idx >= sellist->size) return found;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      selentry_t *e = &(sellist->entry[idx]);
      for (int i = 0; i < nvalues; ++i)
        {
          switch (e->type)
            {
            case SELLIST_INT:
              if (*(int *) par == ((int *) e->cvalues)[i])
                {
                  found = true;
                  e->flag[i] = true;
                }
              break;
            case SELLIST_FLT:
              if (fabs(*(double *) par - ((double *) e->cvalues)[i]) < 1.e-4)
                {
                  found = true;
                  e->flag[i] = true;
                }
              break;
            case SELLIST_WORD:
              if (wildcardmatch(((char **) e->cvalues)[i], *(char **) par) == 0)
                {
                  found = true;
                  e->flag[i] = true;
                }
              break;
            }
        }
    }

  return found;
}

bool
sellist_check_date(sellist_t *sellist, int idx, const char *par)
{
  bool found = false;

  if (idx < 0 || idx >= sellist->size) return found;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      char wcdate[512];
      selentry_t *e = &(sellist->entry[idx]);

      if (*par == ' ') ++par;

      for (int i = 0; i < nvalues; ++i)
        {
          strcpy(wcdate, e->values[i]);
          strcat(wcdate, "*");
          if (wildcardmatch(wcdate, par) == 0)
            {
              found = true;
              e->flag[i] = true;
            }
        }
    }

  return found;
}

void season_to_months(const char *season, int *imonths);

bool
sellist_check_season(sellist_t *sellist, int idx, int month)
{
  assert(month >= 1 && month <= 12);
  bool found = false;

  if (idx < 0 || idx >= sellist->size) return found;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      int imon[13]; /* 1-12 ! */
      selentry_t *e = &(sellist->entry[idx]);

      for (int i = 0; i < nvalues; ++i)
        {
          for (int m = 0; m < 13; ++m) imon[m] = 0;
          season_to_months(e->values[i], imon);
          if (imon[month])
            {
              found = true;
              e->flag[i] = true;
            }
        }
    }

  return found;
}

void
sellist_def_flag(sellist_t *sellist, int idx, int vindex, bool flag)
{
  if (idx < 0 || idx >= sellist->size) return;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      selentry_t *e = &(sellist->entry[idx]);
      if (vindex >= 0 && vindex < nvalues) e->flag[vindex] = flag;
    }
}

void
sellist_get_val(sellist_t *sellist, int idx, int vindex, void *val)
{
  if (idx < 0 || idx >= sellist->size) return;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      selentry_t *e = &(sellist->entry[idx]);
      if (vindex >= 0 && vindex < nvalues)
        {
          switch (e->type)
            {
            case SELLIST_INT: *(int *) val = ((int *) e->cvalues)[vindex]; break;
            case SELLIST_FLT: *(double *) val = ((double *) e->cvalues)[vindex]; break;
            case SELLIST_WORD: *(const char **) val = ((const char **) e->cvalues)[vindex]; break;
            }
        }
    }
}

void
sellist_def_val(sellist_t *sellist, int idx, int vindex, void *val)
{
  if (idx < 0 || idx >= sellist->size) return;

  int nvalues = sellist_nvalues(sellist, idx);

  if (nvalues)
    {
      selentry_t *e = &(sellist->entry[idx]);
      if (vindex >= 0 && vindex < nvalues)
        {
          switch (e->type)
            {
            case SELLIST_INT: ((int *) e->cvalues)[vindex] = *(int *) val; break;
            case SELLIST_FLT: ((double *) e->cvalues)[vindex] = *(double *) val; break;
            case SELLIST_WORD: ((const char **) e->cvalues)[vindex] = *(const char **) val; break;
            }
        }
    }
}

static void
sellist_print_val(int type, cvalues_t *cvalues, int i)
{
  switch (type)
    {
    case SELLIST_INT: printf(" %d", ((int *) cvalues)[i]); break;
    case SELLIST_FLT: printf(" %g", ((double *) cvalues)[i]); break;
    case SELLIST_WORD: printf(" %s", ((char **) cvalues)[i]); break;
    }
}

void
sellist_print(sellist_t *sellist)
{
  if (sellist)
    {
      // printf("Parameter list: %s\n", sellist->
      printf("Num  Name             Type  Size  Entries\n");
      for (int idx = 0; idx < sellist->size; ++idx)
        {
          selentry_t *e = &(sellist->entry[idx]);
          printf("%3d  %-16s %4d  %4d ", idx + 1, e->key, e->type, e->nvalues);
          int nvalues = e->nvalues;
          if (nvalues > 12) nvalues = 11;
          for (int i = 0; i < nvalues; ++i) sellist_print_val(e->type, (cvalues_t *) e->cvalues, i);
          if (nvalues < e->nvalues)
            {
              printf(" ...");
              sellist_print_val(e->type, (cvalues_t *) e->cvalues, e->nvalues - 1);
            }
          printf("\n");
        }
    }
}
