/*
  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 "convert_units.h"
#include "cdo_output.h"
#include "cdo_options.h"

#ifdef HAVE_UDUNITS2

#include <mutex>
static std::mutex udunitsMutex;
#define UDUNITS_LOCK() std::scoped_lock lock(udunitsMutex)

static ut_system *ut_read = nullptr;

static void *
get_converter(char *src_unit_str, char *tgt_unit_str, int *rstatus)
{
  cv_converter *ut_units_converter = nullptr;
  int status;

  *rstatus = -1;

  if (ut_read == nullptr)
    {
      ut_set_error_message_handler(ut_ignore);

      errno = 0;
      ut_read = ut_read_xml(nullptr);
      status = ut_get_status();
      if (status == UT_PARSE)
        {
          if (Options::cdoVerbose) cdo_warning("Udunits: Couldn't parse unit database!");
        }
      if (status == UT_OPEN_ENV || status == UT_OPEN_DEFAULT || status == UT_OS)
        {
          if (Options::cdoVerbose) cdo_warning("Udunits: %s", strerror(errno));
        }
      errno = 0;
      if (status != UT_SUCCESS)
        {
          if (Options::cdoVerbose) cdo_warning("Udunits: Error reading units system!");
          return nullptr;
        }
    }

  ut_trim(src_unit_str, UT_ASCII);
  ut_unit *src_unit = ut_parse(ut_read, src_unit_str, UT_ASCII);
  if (ut_get_status() != UT_SUCCESS)
    {
      if (Options::cdoVerbose) cdo_warning("Udunits: Error parsing units: [%s]", src_unit_str);
      return nullptr;
    }

  ut_trim(tgt_unit_str, UT_ASCII);
  ut_unit *tgt_unit = ut_parse(ut_read, tgt_unit_str, UT_ASCII);
  if (ut_get_status() != UT_SUCCESS)
    {
      if (Options::cdoVerbose) cdo_warning("Udunits: Error parsing units: [%s]", tgt_unit_str);
      return nullptr;
    }

  status = ut_compare(src_unit, tgt_unit);
  if (status == 0) *rstatus = -2;

  if (*rstatus == -1)
    {
      status = ut_are_convertible(src_unit, tgt_unit);
      if (status == 0) *rstatus = -3;
    }

  if (*rstatus == -1)
    {
      ut_units_converter = ut_get_converter(src_unit, tgt_unit);
      if (ut_units_converter == nullptr || ut_get_status() != UT_SUCCESS)
        {
          if (Options::cdoVerbose) cdo_warning("Udunits: Error getting converter from [%s] to [%s]", src_unit_str, tgt_unit_str);
        }
      else
        *rstatus = 0;
    }

  ut_free(src_unit);
  if (ut_get_status() != UT_SUCCESS)
    {
      if (Options::cdoVerbose) cdo_warning("Udunits: Error freeing units [%s]", src_unit_str);
      return nullptr;
    }

  ut_free(tgt_unit);
  if (ut_get_status() != UT_SUCCESS)
    {
      if (Options::cdoVerbose) cdo_warning("Udunits: Error freeing units [%s]", tgt_unit_str);
      return nullptr;
    }

  return (void *) ut_units_converter;
}

namespace cdo
{
void
convert_free(void *ut_converter)
{
  UDUNITS_LOCK();
  cv_free((cv_converter *) ut_converter);
}

void
convert_destroy()
{
  if (ut_read)
    {
      UDUNITS_LOCK();
      ut_free_system(ut_read);
      ut_read = nullptr;
    }
}
}  // namespace cdo
#endif

namespace cdo
{
void
convert_units(void **ut_converter, bool *changeunits, char *units, char *unitsOld, const std::string &name)
{
  (void) ut_converter;  // removes wrong warning, caused by ifdef

  if (*changeunits)
    {
#ifdef HAVE_UDUNITS2
      int status;
      {
        UDUNITS_LOCK();
        *ut_converter = get_converter(unitsOld, units, &status);
      }
      if (*ut_converter == nullptr)
        {
          if (status == -2)
            {
              if (Options::cdoVerbose) cdo_print("%s - not converted from  [%s] to [%s], units are equal!", name, unitsOld, units);
            }
          else if (status == -3)
            {
              cdo_warning("%s - converting units from [%s] to [%s] failed, not convertible!", name, unitsOld, units);
            }
          else { cdo_warning("%s - converting units from [%s] to [%s] failed!", name, unitsOld, units); }
          *changeunits = false;
        }
      else
        {
          // if ( Options::cdoVerbose )
          {
            char buf[64];
            cv_get_expression((const cv_converter *) *ut_converter, buf, sizeof(buf), name.c_str());
            cdo_print("%s - convert units from [%s] to [%s] (expression: %s).", name, unitsOld, units, buf);
          }
        }
#else
      static auto printWarning = true;
      if (printWarning)
        {
          cdo_warning("%s - converting units from [%s] to [%s] failed, UDUNITS2 support not compiled in!", name, unitsOld, units);
          *changeunits = false;
          printWarning = false;
        }
#endif
    }
}
}  // namespace cdo
