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

  Author: Modali Kameswarrao

*/

#ifdef HAVE_CONFIG_H
#include "config.h" /* HAVE_LIBMAGICS */
#endif

#include <cdi.h>

#include "process_int.h"
#include <mpim_grid.h>
#include "printinfo.h"

#ifdef HAVE_LIBMAGICS

#include <magics_api.h>
#include "magics_template_parser.h"
#include "results_template_parser.h"
#include "string_utilities.h"

#define DBG 0

int VECTOR, STREAM;
const char *vector_params[] = { "thin_fac", "unit_vec", "device", "step_freq" };
int vector_param_count = sizeof(vector_params) / sizeof(char *);

/* Default Magics Values */
double THIN_FAC = 2.0, UNIT_VEC = 25.0;
extern int ANIM_FLAG, STEP_FREQ;

extern int checkdevice(char *device_in);

extern char *DEVICE;
extern char *DEVICE_TABLE;
extern int DEVICE_COUNT;

static void
magvector(const char *plotfile, int operatorID, const char *varname, long nlon, long nlat, Varray<double> &grid_center_lon,
          Varray<double> &grid_center_lat, Varray<double> &uarray, Varray<double> &varray, int nparam,
          std::vector<std::string> &params, char *datetime)

{
  long i;
  double dlon = 0, dlat = 0;
  char plotfilename[4096];
  char *temp_str = nullptr;

  (void) varname;

  if (uarray.empty() && varray.empty())
    {
      fprintf(stderr, " No Velocity Components in input file, cannot creaate Vector PLOT!\n");
      return;
    }

  if (uarray.empty() || varray.empty())
    {
      fprintf(stderr, " Found only one Velocity Component in input file, cannot create Vector PLOT!\n");
      return;
    }

  if (DBG)
    {
      fprintf(stderr, "Num params %zu\n", params.size());

      for (i = 0; i < nparam; i++) fprintf(stderr, "Param %s\n", params[i].c_str());
      fflush(stderr);
    }

  for (i = 0; i < nparam; ++i)
    {
      const auto splitStrings = cstr_split_with_seperator(params[i].c_str(), "=");
      const auto &key = splitStrings[0];
      const auto &value = splitStrings[1];

      if (key == "thin_fac")
        {
          THIN_FAC = std::stof(value);
          if (DBG) fprintf(stderr, "THIN FACTOR %g\n", THIN_FAC);
        }

      if (key == "unit_vec")
        {
          UNIT_VEC = std::stof(value);
          if (DBG) fprintf(stderr, "UNIT VECTOR %g\n", UNIT_VEC);
        }

      if (key == "device")
        {
          temp_str = strdup(value.c_str());
          cstr_to_upper_case(temp_str);
          DEVICE = temp_str;
          if (DBG) fprintf(stderr, "DEVICE %s\n", DEVICE);

          mag_setc("output_format", DEVICE);
        }

      if (key == "step_freq")
        {
          STEP_FREQ = std::stoi(value);
          if (DBG) fprintf(stderr, "STEP FREQ %d\n", STEP_FREQ);
        }
    }

  if (nlon > 1)
    {
      for (i = 1; i < nlon; ++i) dlon += (grid_center_lon[i] - grid_center_lon[i - 1]);
      dlon /= (nlon - 1);
    }

  if (nlat > 1)
    {
      for (i = 1; i < nlat; ++i) dlat += (grid_center_lat[nlon * i] - grid_center_lat[nlon * (i - 1)]);
      dlat /= (nlat - 1);
    }

  /* magics_template_parser( magics_node ); */

  /* results_template_parser(results_node, varname ); */

  sprintf(plotfilename, "Velocity Vectors %s", datetime);
  char *titlename = strdup(plotfilename);
  sprintf(plotfilename, "%s", plotfile);

  mag_setc("output_name", plotfilename);
  mag_new("page");

  /* Set the input data */
  mag_setr("input_field_initial_latitude", grid_center_lat[0]);
  mag_setr("input_field_latitude_step", dlat);

  mag_setr("input_field_initial_longitude", grid_center_lon[0]);
  mag_setr("input_field_longitude_step", dlon);

  mag_set2r("input_wind_u_component", uarray.data(), nlon, nlat);
  mag_set2r("input_wind_v_component", varray.data(), nlon, nlat);

  mag_seti("map_label_latitude_frequency", 2);
  mag_seti("map_label_longitude_frequency", 2);
  /*mag_setr ("map_label_height",0.5);*/
  mag_setr("map_label_height", 0.4);

  if (operatorID == VECTOR)
    {
      /* Magics functions for performing vector operation */
      /*
        mag_setc("wind_legend_only", "on" );
        mag_setc("wind_legend_text", "on" );
      */

      mag_setc("legend", "on");
      mag_setc("wind_flag_cross_boundary", "on");
      mag_seti("wind_arrow_thickness", 1);
      mag_coast();

      if (IS_NOT_EQUAL(THIN_FAC, 2.0f)) mag_setr("wind_thinning_factor", THIN_FAC);

      /*wind_arrow_unit_velocity */
      if (IS_NOT_EQUAL(UNIT_VEC, 25.0f)) mag_setr("wind_arrow_unit_velocity", UNIT_VEC);

      mag_wind();

      mag_set1c("text_lines", (const char **) &titlename, 1);
      mag_setc("text_colour", "black");
      mag_setc("text_justification", "centre");
      mag_text();
    }

  free(titlename);
}

static void
init_MAGICS()

{

  setenv("MAGPLUS_QUIET", "1", 1); /* To suppress magics messages */
  mag_open();

  /* Some standard parameters affectng the magics environment, moved from the xml file  ** begin ** */
  mag_setc("page_id_line", "off");
}

static void
quit_MAGICS()
{
  mag_close();
  if (DBG) fprintf(stdout, "Exiting From MAGICS\n");
}

static void
VerifyVectorParameters(int num_param, std::vector<std::string> &param_names, int opID)
{

  int i, j;
  bool found = false, syntax = true, halt_flag = false;
  int param_count = 0;
  const char **params = nullptr;

  // char  *vector_params[] = {"min","max","count","interval","list","colour","thickness","style","RGB"};

  for (i = 0; i < num_param; ++i)
    {
      found = false;
      syntax = true;
      const auto splitStrings = cstr_split_with_seperator(param_names[i].c_str(), "=");

      if (DBG) fprintf(stderr, "Verifying params!\n");

      if (splitStrings.size() > 1)
        {
          const auto &key = splitStrings[0];
          const auto &value = splitStrings[1];
          if (opID == VECTOR)
            {
              param_count = vector_param_count;
              params = vector_params;
            }

          for (j = 0; j < param_count; ++j)
            {
              if (key == params[j])
                {
                  found = true;

                  if (key == "thin_fac" || key == "unit_vec" || key == "step_freq")
                    {
                      if (!cstr_is_numeric(value.c_str())) syntax = false;
                    }

                  if (key == "device")
                    {
                      if (cstr_is_numeric(value.c_str()))
                        syntax = false;
                      else
                        {
                          if (DBG) fprintf(stderr, "Parameter value '%s'\n", value.c_str());
                          char *deviceCstr = strdup(value.c_str());
                          if (checkdevice(deviceCstr)) syntax = false;

                          // Vector not supported in google earth format
                          if (value == "KML" || value == "kml")
                            {
                              syntax = false;
                              if (DBG) fprintf(stderr, "Parameter value '%s'\n", value.c_str());
                            }
                        }
                    }
                }
            }
        }
      else
        {
          syntax = false;
        }

      if (!found)
        {
          halt_flag = true;
          fprintf(stderr, "Invalid parameter  '%s'\n", param_names[i].c_str());
        }
      if (found && !syntax)
        {
          halt_flag = true;
          fprintf(stderr, "Invalid parameter specification  '%s'\n", param_names[i].c_str());
        }
    }

  if (halt_flag) exit(0);
}
#endif

void *
Magvector(void *process)
{
  cdo_initialize(process);

#ifdef HAVE_LIBMAGICS
  char varname[CDI_MAX_NAME];
  char datetimestr[64];

  const auto nparam = cdo_operator_argc();
  auto pnames = cdo_get_oper_argv();

  VECTOR = cdo_operator_add("vector", 0, 0, nullptr);
  STREAM = cdo_operator_add("stream", 0, 0, nullptr);

  const auto operatorID = cdo_operator_id();

  if (nparam)
    {
      if (DBG)
        for (int i = 0; i < nparam; i++) fprintf(stderr, "Param %d is %s!\n", i + 1, pnames[i].c_str());

      VerifyVectorParameters(nparam, pnames, operatorID);
    }

  const auto streamID = cdo_open_read(0);

  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);

  int found = 0;
  auto gridID = vlistInqVarGrid(vlistID, 0);
  // int zaxisID = vlistInqVarZaxis(vlistID, 0);

  if (gridInqType(gridID) == GRID_GME) cdo_abort("GME grid unsupported!");
  if (gridInqType(gridID) == GRID_UNSTRUCTURED) cdo_abort("Unstructured grid unsupported!");

  if (gridInqType(gridID) != GRID_CURVILINEAR) gridID = gridToCurvilinear(gridID, 1);

  const auto gridsize = gridInqSize(gridID);
  int nlon = gridInqXsize(gridID);
  int nlat = gridInqYsize(gridID);
  // int nlev     = zaxisInqSize(zaxisID);

  Varray<double> uarray(gridsize), varray(gridsize);
  Varray<double> grid_center_lat(gridsize), grid_center_lon(gridsize);

  gridInqYvals(gridID, grid_center_lat.data());
  gridInqXvals(gridID, grid_center_lon.data());

  // Convert lat/lon units if required
  cdo_grid_to_degree(gridID, CDI_XAXIS, gridsize, grid_center_lon.data(), "grid center lon");
  cdo_grid_to_degree(gridID, CDI_YAXIS, gridsize, grid_center_lat.data(), "grid center lat");

  int tsID = 0;

  /* HARDCODED THE FILE NAME .. TO BE SENT AS COMMAND LINE ARGUMENT FOR THE MAGICS OPERATOR */
  /*
  init_XML_template_parser( Filename );
  updatemagics_and_results_nodes( );
  */

  init_MAGICS();

  while (true)
    {
      const auto nrecs = cdo_stream_inq_timestep(streamID, tsID);
      if (nrecs == 0) break;

      if (ANIM_FLAG)
        {
          if (tsID % STEP_FREQ)
            {
              tsID++;
              continue;
            }
        }
      else
        {
          if (!STEP_FREQ && tsID)
            {
              cdo_warning("File has values at more than one time step! Image created for first time step!!!");
              break;
            }
        }

      const auto vdate = taxisInqVdate(taxisID);
      const auto vtime = taxisInqVtime(taxisID);
      sprintf(datetimestr, "%s %s", date_to_string(vdate).c_str(), time_to_string(vtime).c_str());

      for (int recID = 0; recID < nrecs; recID++)
        {
          int varID, levelID;
          cdo_inq_record(streamID, &varID, &levelID);

          vlistInqVarName(vlistID, varID, varname);

          if (operatorID == VECTOR)
            {
              if (cdo_cmpstr(varname, "var131") || cdo_cmpstr(varname, "u"))  // U Velocity as per GRIB is var131, as per NC 'u'
                {
                  if (DBG) fprintf(stderr, "Found U VEL in Varname %s\n", varname);
                  size_t nmiss;
                  cdo_read_record(streamID, uarray.data(), &nmiss);
                  if (nmiss) cdo_set_nan(vlistInqVarMissval(vlistID, varID), gridsize, uarray.data());
                  found++;
                }
              if (cdo_cmpstr(varname, "var132") || cdo_cmpstr(varname, "v"))  // V Velocity as per GRIB  is var132, as per NC 'v'
                {
                  if (DBG) fprintf(stderr, "Found V VEL in Varname %s\n", varname);
                  size_t nmiss;
                  cdo_read_record(streamID, varray.data(), &nmiss);
                  if (nmiss) cdo_set_nan(vlistInqVarMissval(vlistID, varID), gridsize, varray.data());
                  found++;
                }
              if (found == 2) break;
            }
          else if (operatorID == STREAM)
            fprintf(stderr, " Stream Operator Un-Supported!\n");
          else
            fprintf(stderr, " Operator Un-Supported!\n");
        }

      if (operatorID == VECTOR)
        {
          if (found == 2)
            {
              if (DBG) fprintf(stderr, "Found Both U & V VEL, Creating vector fields! \n");
              magvector(cdo_get_stream_name(1), operatorID, varname, nlon, nlat, grid_center_lon, grid_center_lat, uarray, varray,
                        nparam, pnames, datetimestr);
            }
          else if (found == 1)
            {
              fprintf(stderr, "Found only one Velocity Component in input file, cannot create Vector PLOT!\n");
              break;
            }
          else if (found == 0)
            {
              fprintf(stderr, "No Velocity Components in input file, cannot create Vector PLOT!\n");
              break;
            }
        }

      tsID++;

      /*
      if( ANIM_FLAG )
        tsID++;
      else
        {
           cdo_warning("File has values at more than one time step! Image created for first time step!!!");
           if( STEP_FREQ > 1 ) cdo_warning("Step frequency parameter ignored!!!"); break;
        }
      */
    }

  cdo_stream_close(streamID);

  /*   quit_XML_template_parser( ); */

  quit_MAGICS();

#else

  cdo_abort("MAGICS support not compiled in!");

#endif

  cdo_finish();

  return nullptr;
}
