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

  Copyright (C) 2003-2020 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 <cassert>
#include <cmath>

#include "param_conversion.h"
#include "sellist.h"
#include "util_wildcards.h"
#include "cdo_output.h"

// #define SELDEBUG 1

void
SelectInfo::init(const KVList &kvlist)
{
  selList.resize(kvlist.size());

  int i = 0;
  for (const auto &kv : kvlist)
    {
      auto &e = selList[i];
      e.type = SelType::UNDEF;
      e.key = kv.key;
      e.nvalues = kv.nvalues;
      e.values.resize(kv.nvalues);
      for (int k = 0; k < kv.nvalues; ++k) e.values[k] = kv.values[k];
#ifdef SELDEBUG
      printf("%s =", e.key.c_str());
      for (const auto &value : e.values) printf(" '%s'", value.c_str());
      printf("\n");
#endif
      ++i;
    }

#ifdef SELDEBUG
  for (const auto &e : selList)
    {
      printf("%s =", e.key.c_str());
      for (const auto &value : e.values) printf(" '%s'", value.c_str());
      printf("\n");
    }
#endif
}

void
SelectInfo::verify() const
{
  for (const auto &e : selList)
    {
      if (e.type == SelType::UNDEF) cdoAbort("Unsupported selection keyword: '%s'!", e.key.c_str());
    }
}

int
selinfo_add(SelectInfo &selInfo, const char *txt, const char *name, SelType type)
{
  auto &selList = selInfo.selList;
  int listIdx = -1;

  for (int i = 0; i < (int) selList.size(); ++i)
    {
      if (selList[i].key == name)
        {
          listIdx = i;
          break;
        }
    }

  if (selInfo.isValidListIdx(listIdx))
    {
      auto &e = selList[listIdx];
      e.type = type;
      e.txt = txt;
      if (e.nvalues)
        {
          switch (type)
            {
            case SelType::INT: e.ivalues.resize(e.nvalues); break;
            case SelType::FLT: e.dvalues.resize(e.nvalues); break;
            case SelType::WORD: e.cvalues.resize(e.nvalues); break;
            }
        }

      int j = 0;
      const auto nvalues = e.nvalues;
      for (int i = 0; i < nvalues; ++i) switch (type)
          {
          case SelType::INT:
            {
              int first, last, inc;
              split_intstring(e.values[i].c_str(), first, last, inc);

              if (first == last)
                {
                  e.ivalues[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.ivalues.resize(e.nvalues);

                      if (inc >= 0)
                        for (int ival = first; ival <= last; ival += inc) e.ivalues[j++] = ival;
                      else
                        for (int ival = first; ival >= last; ival += inc) e.ivalues[j++] = ival;
                    }
                }

              break;
            }
          case SelType::FLT: e.dvalues[i] = parameter2double(e.values[i].c_str()); break;
          case SelType::WORD: e.cvalues[i] = parameter2word(e.values[i].c_str()); break;
          }

      if (e.nvalues) e.flag.resize(e.nvalues, false);
#ifdef SELDEBUG
      printf("%s =", e.key.c_str());
      for (int i = 0; i < e.nvalues; ++i) switch (type)
          {
          case SelType::INT: printf(" %d", e.ivalues[i]); break;
          case SelType::FLT: printf(" %g", e.dvalues[i]); break;
          case SelType::WORD: printf(" %s", e.cvalues[i]); break;
          }
      printf("\n");
#endif
    }

  return listIdx;
}

int
SelectInfo::nvalues(const int listIdx) const
{
  return isValidListIdx(listIdx) ? selList[listIdx].nvalues : 0;
}

void
selinfo_check_flag(const SelectInfo &selInfo, const int listIdx)
{
  const auto &selList = selInfo.selList;
  if (!selInfo.isValidListIdx(listIdx)) return;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      const auto &e = selList[listIdx];
      for (int i = 0; i < nvalues; ++i)
        if (!e.flag[i]) switch (e.type)
            {
            case SelType::INT: cdoWarning("%s >%d< not found!", e.txt.c_str(), e.ivalues[i]); break;
            case SelType::FLT: cdoWarning("%s >%g< not found!", e.txt.c_str(), e.dvalues[i]); break;
            case SelType::WORD: cdoWarning("%s >%s< not found!", e.txt.c_str(), e.cvalues[i]); break;
            }
    }
}

bool
selinfo_check(SelectInfo &selInfo, int listIdx, void *par)
{
  auto &selList = selInfo.selList;
  bool found = false;

  if (!selInfo.isValidListIdx(listIdx)) return found;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      auto &e = selList[listIdx];
      for (int i = 0; i < nvalues; ++i)
        {
          switch (e.type)
            {
            case SelType::INT:
              if (*(int *) par == e.ivalues[i])
                {
                  found = true;
                  e.flag[i] = true;
                }
              break;
            case SelType::FLT:
              if (std::fabs(*(double *) par - e.dvalues[i]) < 1.e-4)
                {
                  found = true;
                  e.flag[i] = true;
                }
              break;
            case SelType::WORD:
              if (wildcardmatch(e.cvalues[i], *(char **) par) == 0)
                {
                  found = true;
                  e.flag[i] = true;
                }
              break;
            }
        }
    }

  return found;
}

bool
selinfo_check_date(SelectInfo &selInfo, int listIdx, const char *par)
{
  auto &selList = selInfo.selList;
  bool found = false;

  if (!selInfo.isValidListIdx(listIdx)) return found;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      char wcdate[512];
      auto &e = selList[listIdx];

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

      for (int i = 0; i < nvalues; ++i)
        {
          strcpy(wcdate, e.values[i].c_str());
          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
selinfo_check_season(SelectInfo &selInfo, int listIdx, int month)
{
  auto &selList = selInfo.selList;
  assert(month >= 1 && month <= 12);
  bool found = false;

  if (!selInfo.isValidListIdx(listIdx)) return found;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      int imon[13]; // 1-12 !
      auto &e = selList[listIdx];

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

  return found;
}

void
selinfo_def_flag(SelectInfo &selInfo, int listIdx, int valIdx, bool flag)
{
  auto &selList = selInfo.selList;
  if (!selInfo.isValidListIdx(listIdx)) return;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      auto &e = selList[listIdx];
      if (valIdx >= 0 && valIdx < nvalues) e.flag[valIdx] = flag;
    }
}

void
selinfo_get_val(const SelectInfo &selInfo, int listIdx, int valIdx, void *val)
{
  const auto &selList = selInfo.selList;
  if (!selInfo.isValidListIdx(listIdx)) return;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      const auto &e = selList[listIdx];
      if (valIdx >= 0 && valIdx < nvalues)
        {
          switch (e.type)
            {
            case SelType::INT: *(int *) val = e.ivalues[valIdx]; break;
            case SelType::FLT: *(double *) val = e.dvalues[valIdx]; break;
            case SelType::WORD: *(const char **) val = e.cvalues[valIdx]; break;
            }
        }
    }
}

void
selinfo_def_val(SelectInfo &selInfo, int listIdx, int valIdx, void *val)
{
  auto &selList = selInfo.selList;
  if (!selInfo.isValidListIdx(listIdx)) return;

  const auto nvalues = selInfo.nvalues(listIdx);
  if (nvalues)
    {
      auto &e = selList[listIdx];
      if (valIdx >= 0 && valIdx < nvalues)
        {
          switch (e.type)
            {
            case SelType::INT: e.ivalues[valIdx] = *(int *) val; break;
            case SelType::FLT: e.dvalues[valIdx] = *(double *) val; break;
            case SelType::WORD: e.cvalues[valIdx] = *(const char **) val; break;
            }
        }
    }
}

static void
selinfo_print_val(const SelectEntry &e, const int valIdx)
{
  switch (e.type)
    {
    case SelType::INT: printf(" %d", e.ivalues[valIdx]); break;
    case SelType::FLT: printf(" %g", e.dvalues[valIdx]); break;
    case SelType::WORD: printf(" %s", e.cvalues[valIdx]); break;
    }
}

void
SelectInfo::print() const
{
  if (selList.size() > 0)
    {
      printf("Num  Name             Type  Size  Entries\n");
      for (int listIdx = 0; listIdx < (int) selList.size(); ++listIdx)
        {
          const auto &e = selList[listIdx];
          printf("%3d  %-16s %4d  %4d ", listIdx + 1, e.key.c_str(), (int)e.type, e.nvalues);
          auto nvalues = e.nvalues;
          if (nvalues > 12) nvalues = 11;
          for (int valIdx = 0; valIdx < nvalues; ++valIdx) selinfo_print_val(e, valIdx);
          if (nvalues < e.nvalues)
            {
              printf(" ...");
              selinfo_print_val(e, e.nvalues - 1);
            }
          printf("\n");
        }
    }
}
