/*
  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.
*/
#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::unique_lock<std::mutex> locked_mutex(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) cdoWarning("Udunits: Couldn't parse unit database!");
        }
      if (status == UT_OPEN_ENV || status == UT_OPEN_DEFAULT || status == UT_OS)
        {
          if (Options::cdoVerbose) cdoWarning("Udunits: %s", strerror(errno));
        }
      errno = 0;
      if (status != UT_SUCCESS)
        {
          if (Options::cdoVerbose) cdoWarning("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) cdoWarning("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) cdoWarning("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) cdoWarning("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) cdoWarning("Udunits: Error freeing units [%s]", src_unit_str);
      return nullptr;
    }

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

  return (void *) ut_units_converter;
}

void
cdoConvertFree(void *ut_converter)
{
  UDUNITS_LOCK();
  cv_free((cv_converter *) ut_converter);
}

void
cdoConvertDestroy()
{
  if (ut_read)
    {
      UDUNITS_LOCK();
      ut_free_system(ut_read);
      ut_read = nullptr;
    }
}
#endif

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