/*
 * A simple synchronous XML-RPC client program written in C, as an example of
 * an Xmlrpc-c client. This invokes the sample.add procedure that the
 * Xmlrpc-c example xmlrpc_sample_add_server.c server provides. I.e. it adds
 * two numbers together, the hard way.
 *
 * This sends the RPC to the server running on the local system ("localhost"),
 * HTTP Port 8080.

 * This program uses the Xmlrpc-c global client, which uses the default
 * client XML transport.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <xmlrpc-c/base.h>
#include <xmlrpc-c/client.h>
#include <math.h>
#include "../include/config.h"  /* information about this build environment */
#include "../include/asa.h"

#define NAME            "Xmlrpc-c Test Client"
#define VERSION         "1.0"
#define FULL_DESC_FREQ  100000000

/*********************************/
/*        Definitions            */
/*********************************/
#define LIBRARY_VERSION_MAJOR   0
#define LIBRARY_VERSION_MINOR   3
#define LIBRARY_VERSION_BUGFIX  4

/*********************************/
/*    Internal functions         */
/*********************************/

static void DieIfFaultOccurred(xmlrpc_env *const envP)
{
  if (envP->fault_occurred)
  {
    fprintf(stderr, "ERROR: %s (%d)\n", envP->fault_string, envP->fault_code);
    exit(1);
  }
}


/*
 * Read value with type double from XMLRPC struct.
 */
static void ReadDoubleStructField(xmlrpc_env *const env, xmlrpc_value *const XMLRPCStruct,
                                  const char *FieldName, double *DoubleValuePtr)
{
  xmlrpc_value *TempXMLValue;
  xmlrpc_double TempXMLDoubleValue;

  xmlrpc_struct_find_value(env, XMLRPCStruct, FieldName, &TempXMLValue);

  if (TempXMLValue)
  {
    xmlrpc_read_double(env, TempXMLValue, &TempXMLDoubleValue);
    DieIfFaultOccurred(env);
    *DoubleValuePtr = TempXMLDoubleValue;
  }
  else
  {
    fprintf(stderr, "ERROR: Missing value in server response\n");
    *DoubleValuePtr = -1;
  }

  xmlrpc_DECREF(TempXMLValue);
}


/*
 * Read value with type int32 from XMLRPC struct.
 */
static void ReadIntStructField(xmlrpc_env *const env, xmlrpc_value *const XMLRPCStruct,
                               const char *FieldName, uint32_t *IntValuePtr)
{
  xmlrpc_value *TempXMLValue;
  xmlrpc_int    TempXMLIntValue;

  xmlrpc_struct_find_value(env, XMLRPCStruct, FieldName, &TempXMLValue);

  if (TempXMLValue)
  {
    xmlrpc_read_int(env, TempXMLValue, &TempXMLIntValue);
    DieIfFaultOccurred(env);
    *IntValuePtr = TempXMLIntValue;
  }
  else
  {
    fprintf(stderr, "ERROR: Missing value in server response\n");
    *IntValuePtr = -1;
  }

  xmlrpc_DECREF(TempXMLValue);
}


/*
 * Read value with type string from XMLRPC struct.
 */
static void ReadStringStructField(xmlrpc_env *const env, xmlrpc_value *const XMLRPCStruct,
                                  const char *FieldName, const char **StrValuePtr)
{
  xmlrpc_value *TempXMLValue;

  xmlrpc_struct_find_value(env, XMLRPCStruct, FieldName, &TempXMLValue);

  if (TempXMLValue)
  {
    xmlrpc_read_string(env, TempXMLValue, StrValuePtr);
    DieIfFaultOccurred(env);
  }
  else
  {
    fprintf(stderr, "ERROR: Missing value in server response\n");
    *StrValuePtr = NULL;
  }

  xmlrpc_DECREF(TempXMLValue);
}


/*
 * Set thining and average factors
 */
static void SetFactors(uint32_t *Factors, double ProbeSignalFrequency)
{
#define CALC_N_POINTS(ThinningFactor, AveragingFactor) \
    ((FULL_DESC_FREQ) / ((ProbeSignalFrequency) * (ThinningFactor) * (AveragingFactor)))

  uint32_t ThinningFactor      = 64000;
  uint32_t AveragingFactor     = 8160;  // Value is defined experimentally. Max averaging – 65535, but operation is unstable
  uint32_t MinOptimalNumPoints = 100;
  uint32_t NPoints             = (uint32_t)CALC_N_POINTS(ThinningFactor, AveragingFactor);

  while (NPoints < MinOptimalNumPoints)
  {
    ThinningFactor = ThinningFactor / 2;
    if (ThinningFactor < 2)
    {
      ThinningFactor = 1;
      break;
    }
    NPoints = (uint32_t)CALC_N_POINTS(ThinningFactor, AveragingFactor);
  }

  NPoints = (uint32_t)CALC_N_POINTS(ThinningFactor, AveragingFactor);
  while (NPoints < MinOptimalNumPoints)
  {
    AveragingFactor = AveragingFactor / 2;
    if (AveragingFactor < 2)
    {
      AveragingFactor = 1;
      break;
    }
    NPoints = (uint32_t)CALC_N_POINTS(ThinningFactor, AveragingFactor);
  }

  Factors[0] = ThinningFactor;   // thinning factor
  Factors[1] = AveragingFactor;  // average factor
}


static void CleanUp(xmlrpc_env env)
{
  /* Clean up our error-handling environment. */
  xmlrpc_env_clean(&env);

  /* Shutdown our XML-RPC client library. */
  xmlrpc_client_cleanup();
}


/*********************************/
/*      Public functions         */
/*********************************/

version_t GetLibraryVersion(void)
{
  version_t Version = {LIBRARY_VERSION_MAJOR,
                       LIBRARY_VERSION_MINOR,
                       LIBRARY_VERSION_BUGFIX};
  return Version;
}


version_t GetAPIVersion(void)
{
  version_t API_Version = {API_VERSION_MAJOR,
                           API_VERSION_MINOR,
                           API_VERSION_BUGFIX};
  return API_Version;
}


status_t SetSettings(server_t *Server, asa_settings_t *Settings)
{
  // ------ Server connection
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  // ------ Prepare data
  char *DebugModelType;
  switch (Settings->DebugModelType)
  {
    case COMPONENT_MODEL_TYPE_RESISTOR:
      DebugModelType = "Resistor";
      break;
    case COMPONENT_MODEL_TYPE_CAPACITOR:
      DebugModelType = "Capacitor";
      break;
    default:
      DebugModelType = "";
  }

  char *TriggerMode;
  switch (Settings->TriggerMode)
  {
    case MODE_MANUAL:
      TriggerMode = "Manual";
      break;
    default:
      TriggerMode = "Auto";
  }

  xmlrpc_value *XMLSettingsStruct = xmlrpc_build_value(&env, "{s:d,s:d,s:d,s:d,s:s,s:d,s:i,s:i,s:i,s:s}",
                                                       "probe_signal_frequency", Settings->ProbeSignalFrequencyHz,
                                                       "desc_frequency", Settings->SamplingRateHz,
                                                       "max_voltage", Settings->VoltageAmplV,
                                                       "max_current", Settings->MaxCurrentMA,
                                                       "current_model_type", DebugModelType,
                                                       "current_model_nominal", Settings->DebugModelNominal,
                                                       "number_points", Settings->NumberPoints,
                                                       "number_charge_points", Settings->NumberChargePoints,
                                                       "measure_flags", Settings->MeasureFlags,
                                                       "trigger_mode", TriggerMode);

  /* ------ Calling procedure */
  const char *const methodName = "set_settings";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "(S)", XMLSettingsStruct);

  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  xmlrpc_DECREF(XMLSettingsStruct);
  CleanUp(env);
  return Status;
}


status_t GetLastOperationResult(server_t *Server)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */  

  /* Make the remote procedure call */
  const char *const methodName = "get_last_operation_status";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  ReadIntStructField(&env, XMLResult, "operation_status", (uint32_t *)&Status);
  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  CleanUp(env);
  return Status;
}


status_t GetStatusButtons(server_t *Server, asa_buttons_t *Buttons)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */

  /* Make the remote procedure call */
  const char *const methodName = "get_status_buttons";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  ReadIntStructField(&env, XMLResult, "gray_button", &Buttons->GrayProbeButton);
  ReadIntStructField(&env, XMLResult, "blue_button", &Buttons->BlueProbeButton);
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  CleanUp(env);
  return Status;
}


uint32_t GetNumberPointsForSinglePeriod(asa_settings_t *Settings)
{
  uint32_t *factors = (uint32_t *)malloc(2 * sizeof(uint32_t));
  SetFactors(factors, Settings->ProbeSignalFrequencyHz);
  uint32_t OnePeriodPoints = (uint32_t)(FULL_DESC_FREQ / (factors[0] * factors[1] * Settings->ProbeSignalFrequencyHz));
  //printf("f = %lf, f0 = %d, f1 = %d, n = %d\n", Settings->ProbeSignalFrequencyHz, factors[0], factors[1], OnePeriodPoints);
  free(factors);
  if (Settings->ProbeSignalFrequencyHz == 12000000)
  {
    return (uint32_t)ceil(3 * FULL_DESC_FREQ / Settings->ProbeSignalFrequencyHz);
  }
  return OnePeriodPoints;
}


status_t GetTempProbes(server_t *Server, asa_temperature_t *Temperature)
{
  /*------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */

  /* Make the remote procedure call */
  const char *const methodName = "get_temp_probes";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  ReadIntStructField(&env, XMLResult, "overheat_gray", &Temperature->GrayOverheat);
  ReadIntStructField(&env, XMLResult, "gray_temp", &Temperature->GrayTemperature);
  ReadIntStructField(&env, XMLResult, "overheat_blue", &Temperature->BlueOverheat);
  ReadIntStructField(&env, XMLResult, "blue_temp", &Temperature->BlueTemperature);
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */
  
  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  CleanUp(env);
  return Status;
}


status_t GetSettings(server_t *Server, asa_settings_t *Settings)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);
  
  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */  

  /* Make the remote procedure call */
  const char *const methodName = "get_settings";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  ReadDoubleStructField(&env, XMLResult, "probe_signal_frequency", &Settings->ProbeSignalFrequencyHz);
  ReadDoubleStructField(&env, XMLResult, "desc_frequency", &Settings->SamplingRateHz);
  ReadDoubleStructField(&env, XMLResult, "max_voltage", &Settings->VoltageAmplV);
  ReadDoubleStructField(&env, XMLResult, "max_current", &Settings->MaxCurrentMA);
  ReadIntStructField(&env, XMLResult, "number_points", &Settings->NumberPoints);
  ReadIntStructField(&env, XMLResult, "number_charge_points", &Settings->NumberChargePoints);
  ReadIntStructField(&env, XMLResult, "measure_flags", &Settings->MeasureFlags);
  const char *StrValue = NULL;
  ReadStringStructField(&env, XMLResult, "current_model_type", &StrValue);
  if (strcmp(StrValue, "Resistor") == 0)
  {
    Settings->DebugModelType = COMPONENT_MODEL_TYPE_RESISTOR;
  }
  else if (strcmp(StrValue, "Capacitor") == 0)
  {
    Settings->DebugModelType = COMPONENT_MODEL_TYPE_CAPACITOR;
  }
  else
  {
    Settings->DebugModelType = COMPONENT_MODEL_TYPE_NONE;
  }
  ReadStringStructField(&env, XMLResult, "trigger_mode", &StrValue);
  if (strcmp(StrValue, "Manual") == 0)
  {
    Settings->TriggerMode = MODE_MANUAL;
  }
  else
  {
    Settings->TriggerMode = MODE_AUTO;
  }
  ReadDoubleStructField(&env, XMLResult, "current_model_nominal", &Settings->DebugModelNominal);
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }
  
  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  CleanUp(env);
  return Status;
}


status_t GetIVCurve(server_t *Server, iv_curve_t *IVC, uint32_t size)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  if (size > MAX_NUM_POINTS)
  {
    fprintf(stderr, "ERROR: Maximum of number of points less then size of curve!: %d < %d\n", MAX_NUM_POINTS, size);
    Status = SERVER_RESPONSE_ERROR;
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */
  xmlrpc_value *XMLCurrents;
  xmlrpc_value *XMLVoltages;

  /* Make the remote procedure call */
  const char *const methodName = "get_ivc";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Get our sum and print it out. */
  xmlrpc_struct_find_value(&env, XMLResult, "current", &XMLCurrents);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  xmlrpc_struct_find_value(&env, XMLResult, "voltage", &XMLVoltages);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  uint32_t NumPoints = xmlrpc_array_size(&env, XMLCurrents);
  if (NumPoints != size)
  {
    Status = SERVER_RESPONSE_ERROR;
  }

  NumPoints = xmlrpc_array_size(&env, XMLVoltages);
  if (NumPoints != size)
  {
    Status = SERVER_RESPONSE_ERROR;
  }

  if (Status == ASA_OK)
  {
    uint32_t i;
    xmlrpc_double XMLDoubleCurrent, XMLDoubleVoltage;
    xmlrpc_value *XMLCurrent = NULL, *XMLVoltage = NULL;
    for (i = 0; i < size; i++)
    {
      xmlrpc_array_read_item(&env, XMLCurrents, i, &XMLCurrent);
      xmlrpc_array_read_item(&env, XMLVoltages, i, &XMLVoltage);
      xmlrpc_read_double(&env, XMLCurrent, &XMLDoubleCurrent);
      xmlrpc_read_double(&env, XMLVoltage, &XMLDoubleVoltage);
      IVC->Currents[i] = XMLDoubleCurrent;
      IVC->Voltages[i] = XMLDoubleVoltage;
    }
    xmlrpc_DECREF(XMLCurrent);
    xmlrpc_DECREF(XMLVoltage);
  }

  /* ------ Finalize session*/

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  xmlrpc_DECREF(XMLCurrents);
  xmlrpc_DECREF(XMLVoltages);
  CleanUp(env);
  return Status;
}


status_t TriggerMeasurement(server_t *Server)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */
  const char *const methodName = "trigger_measurement";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  
  /* Parse result */
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  CleanUp(env);
  return Status;
}


status_t Calibrate(server_t *Server, uint8_t Type)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);
  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */
  xmlrpc_value *ProbesState = xmlrpc_build_value(&env, "{s:i}", "type", Type);
  const char *const methodName = "calibrate";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "(S)", ProbesState);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */
  
  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  xmlrpc_DECREF(ProbesState);
  CleanUp(env);
  return Status;
}


void LoadCoefficientTable(char *FileName, asa_coefficients_t *C)
{
  char buff[255];
  FILE *fp = fopen(FileName, "r");
  if (fp == NULL)
  {
    printf("File %s not found\n", FileName);
    // TODO: add status or zeros in asa_coefficients_t to let the caller know about errors.
    return;
  }

  int q = 0;
  for (int i = 0; i < NUM_ALL_COMBINATIONS + 1; i++)
  {
    if (i > 0)
    {
      q = i - 1;
    }
    fscanf(fp, "%s", buff);
    C->ProbeFrequencyArray[q] = atof(buff);
    fscanf(fp, "%s", buff);
    C->VoltageAmplArray[q] = atof(buff);
    fscanf(fp, "%s", buff);
    C->MaxCurrentArray[q] = atof(buff);
    fscanf(fp, "%s", buff);
    C->LimitResistorArray[q] = atoi(buff);
    fscanf(fp, "%s", buff);
    C->SenseResistorArray[q] = atoi(buff);
    fscanf(fp, "%s", buff);
    C->CurCalibrationArray[q] = atoi(buff);
    fscanf(fp, "%s", buff);
    C->VoltCalibrationArray[q] = atoi(buff);
    fscanf(fp, "%s", buff);
    C->CurCalibrationKZArray[q] = atoi(buff);
    fscanf(fp, "%s", buff);
    C->VoltCalibrationKZArray[q] = atoi(buff);
  }
  fclose(fp);
}


void SaveCoefficientTable(char *FileName, asa_coefficients_t *C)
{
  FILE *fp = fopen(FileName, "w");
  if (fp == NULL) {
      printf("File %s not found\n", FileName);
      return;
  }
  fprintf(fp, "frequency voltage current limit_mode sense_mode current_calibration "
              "voltage_calibration current_calibration_kz voltage_calibration_kz\n");
  for (int i = 0; i < NUM_ALL_COMBINATIONS; i++)
  {
    fprintf(fp, "%.1f    %.1f    %.1f    %d    %d    %d    %d    %d   %d\n",
            C->ProbeFrequencyArray[i], C->VoltageAmplArray[i], C->MaxCurrentArray[i],
            C->LimitResistorArray[i], C->SenseResistorArray[i], C->CurCalibrationArray[i],
            C->VoltCalibrationArray[i], C->CurCalibrationKZArray[i], C->VoltCalibrationKZArray[i]);
  }
  fclose(fp);
}


status_t SetCoefficients(server_t *Server, asa_coefficients_t *C)
{
  // ------ Server connection
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);

  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  xmlrpc_value *FreqArrayP      = xmlrpc_array_new(&env);
  xmlrpc_value *VoltArrayP      = xmlrpc_array_new(&env);
  xmlrpc_value *CurArrayP       = xmlrpc_array_new(&env);
  xmlrpc_value *LimArrayP       = xmlrpc_array_new(&env);
  xmlrpc_value *SenseArrayP     = xmlrpc_array_new(&env);
  xmlrpc_value *CCalibrArrayP   = xmlrpc_array_new(&env);
  xmlrpc_value *VCalibrArrayP   = xmlrpc_array_new(&env);
  xmlrpc_value *CCalibrKzArrayP = xmlrpc_array_new(&env);
  xmlrpc_value *VCalibrKzArrayP = xmlrpc_array_new(&env);
  for (int i = 0; i < NUM_ALL_COMBINATIONS; i++)
  {
    xmlrpc_value *FreqP      = xmlrpc_double_new(&env, C->ProbeFrequencyArray[i]);
    xmlrpc_value *VoltP      = xmlrpc_double_new(&env, C->VoltageAmplArray[i]);
    xmlrpc_value *CurP       = xmlrpc_double_new(&env, C->MaxCurrentArray[i]);
    xmlrpc_value *LimP       = xmlrpc_int_new(&env, C->LimitResistorArray[i]);
    xmlrpc_value *SenseP     = xmlrpc_int_new(&env, C->SenseResistorArray[i]);
    xmlrpc_value *CCalibrP   = xmlrpc_int_new(&env, C->CurCalibrationArray[i]);
    xmlrpc_value *VCalibrP   = xmlrpc_int_new(&env, C->VoltCalibrationArray[i]);
    xmlrpc_value *CCalibrKzP = xmlrpc_int_new(&env, C->CurCalibrationKZArray[i]);
    xmlrpc_value *VCalibrKzP = xmlrpc_int_new(&env, C->VoltCalibrationKZArray[i]);

    xmlrpc_array_append_item(&env, FreqArrayP, FreqP);
    xmlrpc_array_append_item(&env, VoltArrayP, VoltP);
    xmlrpc_array_append_item(&env, CurArrayP, CurP);
    xmlrpc_array_append_item(&env, LimArrayP, LimP);
    xmlrpc_array_append_item(&env, SenseArrayP, SenseP);
    xmlrpc_array_append_item(&env, CCalibrArrayP, CCalibrP);
    xmlrpc_array_append_item(&env, VCalibrArrayP, VCalibrP);
    xmlrpc_array_append_item(&env, CCalibrKzArrayP, CCalibrKzP);
    xmlrpc_array_append_item(&env, VCalibrKzArrayP, VCalibrKzP);

    xmlrpc_DECREF(FreqP);
    xmlrpc_DECREF(VoltP);
    xmlrpc_DECREF(CurP);
    xmlrpc_DECREF(LimP);
    xmlrpc_DECREF(SenseP);
    xmlrpc_DECREF(CCalibrP);
    xmlrpc_DECREF(VCalibrP);
    xmlrpc_DECREF(CCalibrKzP);
    xmlrpc_DECREF(VCalibrKzP);
  }
  xmlrpc_value *XMLCoefficientStruct = xmlrpc_build_value(&env, "{s:A,s:A,s:A,s:A,s:A,s:A,s:A,s:A,s:A}",
                                                          "probe_signal_frequency_list", FreqArrayP,
                                                          "max_voltage_list", VoltArrayP,
                                                          "max_current_list", CurArrayP,
                                                          "limit_mode_list", LimArrayP,
                                                          "sense_mode_list", SenseArrayP,
                                                          "current_calibration_list", CCalibrArrayP,
                                                          "voltage_calibration_list", VCalibrArrayP,
                                                          "current_calibration_kz_list", CCalibrKzArrayP,
                                                          "voltage_calibration_kz_list", VCalibrKzArrayP);

  /* ------ Calling procedure */
  const char *const methodName = "set_coefficients";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "(S)", XMLCoefficientStruct);
  
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Parse result */
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);
  
  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  /* ------ Finalize session */

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  xmlrpc_DECREF(XMLCoefficientStruct);
  xmlrpc_DECREF(FreqArrayP);
  xmlrpc_DECREF(VoltArrayP);
  xmlrpc_DECREF(CurArrayP);
  xmlrpc_DECREF(LimArrayP);
  xmlrpc_DECREF(SenseArrayP);
  xmlrpc_DECREF(CCalibrArrayP);
  xmlrpc_DECREF(VCalibrArrayP);
  xmlrpc_DECREF(CCalibrKzArrayP);
  xmlrpc_DECREF(VCalibrKzArrayP);
  CleanUp(env);
  return Status;
}


status_t GetCoefficients(server_t *Server, asa_coefficients_t *C)
{
  /* ------ Server connection */
  status_t   Status = ASA_OK;
  xmlrpc_env env;
  char       serverUrl[128];
  sprintf(serverUrl, "http://%s:%s", Server->Host, Server->Port);
  
  /* Initialize our error-handling environment. */
  xmlrpc_env_init(&env);

  /* Create the global XML-RPC client object. */
  xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* ------ Calling procedure */

  /* Make the remote procedure call */
  const char *const methodName = "get_coefficients";
  xmlrpc_value *XMLResult = xmlrpc_client_call(&env, serverUrl, methodName, "()");
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  /* Get our sum and print it out. */
  xmlrpc_value *XMLProbeFreq;
  xmlrpc_struct_find_value(&env, XMLResult, "probe_signal_frequency_list", &XMLProbeFreq);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLVoltAmpl;
  xmlrpc_struct_find_value(&env, XMLResult, "max_voltage_list", &XMLVoltAmpl);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLMaxCur;
  xmlrpc_struct_find_value(&env, XMLResult, "max_current_list", &XMLMaxCur);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLLimMode;
  xmlrpc_struct_find_value(&env, XMLResult, "limit_mode_list", &XMLLimMode);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLSenseMode;
  xmlrpc_struct_find_value(&env, XMLResult, "sense_mode_list", &XMLSenseMode);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  
  xmlrpc_value *XMLCCalibr;
  xmlrpc_struct_find_value(&env, XMLResult, "current_calibration_list", &XMLCCalibr);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLVCalibr;
  xmlrpc_struct_find_value(&env, XMLResult, "voltage_calibration_list", &XMLVCalibr);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }

  xmlrpc_value *XMLCCalibrKz;
  xmlrpc_struct_find_value(&env, XMLResult, "current_calibration_kz_list", &XMLCCalibrKz);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  
  xmlrpc_value *XMLVCalibrKz;
  xmlrpc_struct_find_value(&env, XMLResult, "voltage_calibration_kz_list", &XMLVCalibrKz);
  if (env.fault_occurred)
  {
    Status = SERVER_RESPONSE_ERROR;
    fprintf(stderr, "ERROR: %s (%d)\n", env.fault_string, env.fault_code);
    CleanUp(env);
    return Status;
  }
  
  const char *StatusMsg;
  ReadIntStructField(&env, XMLResult, "status_code", (uint32_t *)&Status);
  ReadStringStructField(&env, XMLResult, "status_msg", &StatusMsg);

  if (Status != ASA_OK)
  {
    fprintf(stderr, "ERROR: %s\n", StatusMsg);
    CleanUp(env);
    return Status;
  }

  uint32_t size_f    = xmlrpc_array_size(&env, XMLProbeFreq);
  uint32_t size_v    = xmlrpc_array_size(&env, XMLVoltAmpl);
  uint32_t size_c    = xmlrpc_array_size(&env, XMLMaxCur);
  uint32_t size_lm   = xmlrpc_array_size(&env, XMLLimMode);
  uint32_t size_sm   = xmlrpc_array_size(&env, XMLSenseMode);
  uint32_t size_cc   = xmlrpc_array_size(&env, XMLCCalibr);
  uint32_t size_vc   = xmlrpc_array_size(&env, XMLVCalibr);
  uint32_t size_cckz = xmlrpc_array_size(&env, XMLCCalibrKz);
  uint32_t size_vckz = xmlrpc_array_size(&env, XMLVCalibrKz);
  if (size_f != NUM_ALL_COMBINATIONS || size_v != NUM_ALL_COMBINATIONS || size_c != NUM_ALL_COMBINATIONS ||
      size_lm != NUM_ALL_COMBINATIONS || size_sm != NUM_ALL_COMBINATIONS || size_cc != NUM_ALL_COMBINATIONS ||
      size_vc != NUM_ALL_COMBINATIONS || size_cckz != NUM_ALL_COMBINATIONS || size_vckz != NUM_ALL_COMBINATIONS)
  {
    Status = SERVER_RESPONSE_ERROR;
  }

  if (Status == ASA_OK)
  {
    xmlrpc_double XMLDoubleFreq, XMLDoubleMaxVolt, XMLDoubleMaxCur;
    xmlrpc_int XMLIntLimMode, XMLIntSenseMode, XMLIntCCalibr, XMLIntVCalibr, XMLIntCCalibrKz, XMLIntVCalibrKz;
    xmlrpc_value *XMLFreqItem, *XMLVoltItem, *XMLCurItem, *XMLLimItem, *XMLSenseItem, *XMLCCalibrItem, *XMLVCalibrItem, *XMLCCalibrKzItem, *XMLVCalibrKzItem;
    for (uint32_t i = 0; i < NUM_ALL_COMBINATIONS; i++)
    {
      xmlrpc_array_read_item(&env, XMLProbeFreq, i, &XMLFreqItem);
      xmlrpc_array_read_item(&env, XMLVoltAmpl, i, &XMLVoltItem);
      xmlrpc_array_read_item(&env, XMLMaxCur, i, &XMLCurItem);
      xmlrpc_array_read_item(&env, XMLLimMode, i, &XMLLimItem);
      xmlrpc_array_read_item(&env, XMLSenseMode, i, &XMLSenseItem);
      xmlrpc_array_read_item(&env, XMLCCalibr, i, &XMLCCalibrItem);
      xmlrpc_array_read_item(&env, XMLVCalibr, i, &XMLVCalibrItem);
      xmlrpc_array_read_item(&env, XMLCCalibrKz, i, &XMLCCalibrKzItem);
      xmlrpc_array_read_item(&env, XMLVCalibrKz, i, &XMLVCalibrKzItem);

      xmlrpc_read_double(&env, XMLFreqItem, &XMLDoubleFreq);
      xmlrpc_read_double(&env, XMLVoltItem, &XMLDoubleMaxVolt);
      xmlrpc_read_double(&env, XMLCurItem, &XMLDoubleMaxCur);
      xmlrpc_read_int(&env, XMLLimItem, &XMLIntLimMode);
      xmlrpc_read_int(&env, XMLSenseItem, &XMLIntSenseMode);
      xmlrpc_read_int(&env, XMLCCalibrItem, &XMLIntCCalibr);
      xmlrpc_read_int(&env, XMLVCalibrItem, &XMLIntVCalibr);
      xmlrpc_read_int(&env, XMLCCalibrKzItem, &XMLIntCCalibrKz);
      xmlrpc_read_int(&env, XMLVCalibrKzItem, &XMLIntVCalibrKz);

      C->ProbeFrequencyArray[i] = XMLDoubleFreq;
      C->VoltageAmplArray[i] = XMLDoubleMaxVolt;
      C->MaxCurrentArray[i] = XMLDoubleMaxCur;
      C->LimitResistorArray[i] = XMLIntLimMode;
      C->SenseResistorArray[i] = XMLIntSenseMode;
      C->CurCalibrationArray[i] = XMLIntCCalibr;
      C->VoltCalibrationArray[i] = XMLIntVCalibr;
      C->CurCalibrationKZArray[i] = XMLIntCCalibrKz;
      C->VoltCalibrationKZArray[i] = XMLIntVCalibrKz;
    }
    xmlrpc_DECREF(XMLFreqItem);
    xmlrpc_DECREF(XMLVoltItem);
    xmlrpc_DECREF(XMLCurItem);
    xmlrpc_DECREF(XMLLimItem);
    xmlrpc_DECREF(XMLSenseItem);
    xmlrpc_DECREF(XMLCCalibrItem);
    xmlrpc_DECREF(XMLVCalibrItem);
    xmlrpc_DECREF(XMLCCalibrKzItem);
    xmlrpc_DECREF(XMLVCalibrKzItem);
  }

  /* ------ Finalize session*/

  /* Dispose of our result value. */
  xmlrpc_DECREF(XMLResult);
  xmlrpc_DECREF(XMLProbeFreq);
  xmlrpc_DECREF(XMLVoltAmpl);
  xmlrpc_DECREF(XMLMaxCur);
  xmlrpc_DECREF(XMLLimMode);
  xmlrpc_DECREF(XMLSenseMode);
  xmlrpc_DECREF(XMLCCalibr);
  xmlrpc_DECREF(XMLVCalibr);
  xmlrpc_DECREF(XMLCCalibrKz);
  xmlrpc_DECREF(XMLVCalibrKz);
  CleanUp(env);
  return Status;
}
