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

  Author: Uwe Schulzweida

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdarg.h>
#include <errno.h>
#include <cdi.h>
#include <pthread.h>
#include <string>

#include "cdo_output.h"
#include "mpmo_color.h"

// Debug Switches
int cdoDebug = 0;
int cdoDebugExt = 0;  //  Debug level for the KNMI extensions
                      // Subsystem Debug Switches
int PSTREAM = 0;
int PROCESS = 0;
int PIPE = 0;
int PIPE_STREAM = 0;
int FILE_STREAM = 0;
int PTHREAD = 0;
int PROCESS_MANAGER = 0;

void
print_debug_options()
{
  std::cout << "DebugLevels:" << std::endl
            << "  Cdi:" << std::endl
            << "     0: off " << std::endl
            << "     1: on : std debug: 2 cdi,32 cdo, 512 processes, 1024 process manager" << std::endl
            << "     2: cdi" << std::endl
            << "     3: memory" << std::endl
            << "     4: file" << std::endl
            << "     5: format" << std::endl
            << "  Cdo:" << std::endl
            << "     6: cdo" << std::endl
            << "     7: pipeStream" << std::endl
            << "     8: fileStream" << std::endl
            << "     9: pipe" << std::endl
            << "    10: pthread" << std::endl
            << "    11: process" << std::endl
            << "    12: process manager" << std::endl;
}

namespace cdo
{
void
set_debug(int p_debug_level)
{
  MpMO::DebugLevel = p_debug_level;
  if (p_debug_level == 1 || (p_debug_level & 32)) cdoDebug = 1;
  if (p_debug_level == 1 || (p_debug_level & 64)) FILE_STREAM = 1;
  if (p_debug_level == 1 || (p_debug_level & 128)) PIPE_STREAM = 1;
#ifdef HAVE_LIBPTHREAD
  if ((p_debug_level & 256)) PIPE = 1;
  if ((p_debug_level & 512)) PTHREAD = 1;
#endif
  if (p_debug_level == 1 || (p_debug_level & 1024)) PROCESS = 1;
  if (p_debug_level == 1 || (p_debug_level & 2048)) PROCESS_MANAGER = 1;
}

void default_exit(){exit(EXIT_FAILURE);}
const char* default_context(){return "cdo init";}
void (*exitProgram)(void) = default_exit;
const char *(*getContext)(void) = default_context;

void
set_exit_function(void (*func)(void))
{
  exitProgram = func;
}

void
set_context_function(const char *(*func)(void) )
{
  getContext = func;
}
}  // namespace cdo

char *
getGRB2ErrStr(const char *progname)
{
  const char *format = "To create a %s application with GRIB2 support use: ./configure --with-eccodes=<ECCODES root directory> ...";
  size_t finalSize = snprintf(nullptr, 0, format, progname);
  char *errStr = (char *) malloc(finalSize + 1);
  sprintf(errStr, format, progname);

  return errStr;
}

char *
getNCErrString(int filetype, const char *progname)
{
  const char *ncv = (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C)
                        ? "4"
                        : ((filetype == CDI_FILETYPE_NC2) ? "2" : ((filetype == CDI_FILETYPE_NC5) ? "5" : ""));
#ifdef HAVE_LIBNETCDF
  const char *format = "%s was build with a NetCDF version which doesn't support NetCDF%s data!";
  size_t finalSize = snprintf(nullptr, 0, format, progname, ncv);
  char *errStr = (char *) malloc(finalSize + 1);
  sprintf(errStr, format, progname, ncv);
#else
  const char *format
      = "To create a %s application with NetCDF%s support use: ./configure --with-netcdf=<NetCDF%s root directory> ...";
  size_t finalSize = snprintf(nullptr, 0, format, progname, ncv, ncv);
  char *errStr = (char *) malloc(finalSize + 1);
  sprintf(errStr, format, progname, ncv, ncv);
#endif

  return errStr;
}

static char *
checkForMissingLib(int filetype, const char *progname)
{
  char *errStr = nullptr;

  switch (filetype)
    {
    case CDI_FILETYPE_GRB: break;
    case CDI_FILETYPE_GRB2:
      {
        errStr = getGRB2ErrStr(progname);
        break;
      }
    case CDI_FILETYPE_SRV: break;
    case CDI_FILETYPE_EXT: break;
    case CDI_FILETYPE_IEG: break;
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NC5:
      {
        errStr = getNCErrString(filetype, progname);
        break;
      }
    default: break;
    }

  return errStr;
}
void
cdi_open_error(int cdiErrno, const std::string &format, const char *path)
{
  std::string context = cdo::getContext();
  MpMO::PrintCerr(Red("%s: ") + format + "\n" + std::string(context.size() + 2, ' ') + "%s", context, path,
                  cdiStringError(cdiErrno));
  if (cdiErrno == CDI_ELIBNAVAIL)
    {
      int byteorder;
      int filetype = cdiGetFiletype(path, &byteorder);
      char *errStr = checkForMissingLib(filetype, "CDO");
      if (errStr)
        {
          MpMO::PrintCerr("%s\n", errStr);
          free(errStr);
        }
    }

  if (MpMO::exitOnError)
    {
      cdo::exitProgram();
    }
}

void
query_user_exit(const char *argument)
{
  // modified code from NCO
#define USR_RPL_MAX_LNG 10 /* Maximum length for user reply */
#define USR_RPL_MAX_NBR 10 /* Maximum number of chances for user to reply */
  char usr_rpl[USR_RPL_MAX_LNG];
  int usr_rpl_int;
  short nbr_itr = 0;
  size_t usr_rpl_lng = 0;

  // Initialize user reply string
  usr_rpl[0] = 'z';
  usr_rpl[1] = '\0';

  while (!(usr_rpl_lng == 1 && (*usr_rpl == 'o' || *usr_rpl == 'O' || *usr_rpl == 'e' || *usr_rpl == 'E')))
    {
      if (nbr_itr++ > USR_RPL_MAX_NBR)
        {
          (void) fprintf(stderr, "\n%s: ERROR %d failed attempts to obtain valid interactive input.\n", cdo::getContext(),
                         nbr_itr - 1);
          exit(EXIT_FAILURE);
        }

      if (nbr_itr > 1) (void) fprintf(stdout, "%s: ERROR Invalid response.\n", cdo::getContext());
      (void) fprintf(stdout, "%s: %s exists ---`e'xit, or `o'verwrite (delete existing file) (e/o)? ", cdo::getContext(), argument);
      (void) fflush(stdout);
      if (fgets(usr_rpl, USR_RPL_MAX_LNG, stdin) == nullptr) continue;

      /* Ensure last character in input string is \n and replace that with \0 */
      usr_rpl_lng = strlen(usr_rpl);
      if (usr_rpl_lng >= 1)
        if (usr_rpl[usr_rpl_lng - 1] == '\n')
          {
            usr_rpl[usr_rpl_lng - 1] = '\0';
            usr_rpl_lng--;
          }
    }

  /* Ensure one case statement for each exit condition in preceding while loop */
  usr_rpl_int = (int) usr_rpl[0];
  switch (usr_rpl_int)
    {
    case 'E':
    case 'e': exit(EXIT_SUCCESS); break;
    case 'O':
    case 'o': break;
    default: exit(EXIT_FAILURE); break;
    } /* end switch */
}
