/* validator.cc
 * libxml++ and this file are copyright (C) 2000 by Ari Johnson
 * (C) 2002-2004 by the libxml dev team and
 * are covered by the GNU Lesser General Public License, which should be
 * included with libxml++ as the file COPYING.
 */

#include "libxml++/exceptions/wrapped_exception.h"
#include "libxml++/validators/validator.h"

#include <libxml/parser.h>

#include <cstdarg> //For va_list.
#include <memory> //For unique_ptr.

namespace
{
// C++ linkage
using ErrorOrWarningFuncType = void (*)(bool error, void* ctx,
                                        const char* msg, va_list var_args);
ErrorOrWarningFuncType p_callback_error_or_warning;

extern "C"
{
static void c_callback_validity_error(void* ctx, const char* msg, ...)
{
  va_list var_args;
  va_start(var_args, msg);
  p_callback_error_or_warning(true, ctx, msg, var_args);
  va_end(var_args);
}

static void c_callback_validity_warning(void* ctx, const char* msg, ...)
{
  va_list var_args;
  va_start(var_args, msg);
  p_callback_error_or_warning(false, ctx, msg, var_args);
  va_end(var_args);
}
} // extern "C"
} // anonymous namespace

namespace xmlpp {

Validator::Validator() noexcept
: exception_(nullptr)
{
}

Validator::~Validator()
{
  release_underlying();
}

void Validator::initialize_context()
{
  //Clear these temporary buffers:
  validate_error_.erase();
  validate_warning_.erase();
}

void Validator::release_underlying()
{
}

void Validator::on_validity_error(const ustring& message)
{
  //Throw an exception later when the whole message has been received:
  validate_error_ += message;
}

void Validator::on_validity_warning(const ustring& message)
{
  //Throw an exception later when the whole message has been received:
  validate_warning_ += message;
}

void Validator::check_for_validity_messages()
{
  ustring msg(exception_ ? exception_->what() : "");
  bool validity_msg = false;

  if (!validate_error_.empty())
  {
    validity_msg = true;
    msg += "\nValidity error:\n" + validate_error_;
    validate_error_.erase();
  }

  if (!validate_warning_.empty())
  {
    validity_msg = true;
    msg += "\nValidity warning:\n" + validate_warning_;
    validate_warning_.erase();
  }

  if (validity_msg)
    exception_ = std::make_unique<validity_error>(msg);
}

#ifndef LIBXMLXX_DISABLE_DEPRECATED
void Validator::callback_validity_error(void* valid_, const char* msg, ...)
{
  auto validator = static_cast<Validator*>(valid_);

  if(validator)
  {
    //Convert the ... to a string:
    va_list arg;
    va_start(arg, msg);
    const ustring buff = format_printf_message(msg, arg);
    va_end(arg);

    try
    {
      validator->on_validity_error(buff);
    }
    catch (...)
    {
      validator->handle_exception();
    }
  }
}

void Validator::callback_validity_warning(void* valid_, const char* msg, ...)
{
  auto validator = static_cast<Validator*>(valid_);

  if(validator)
  {
    //Convert the ... to a string:
    va_list arg;
    va_start(arg, msg);
    const ustring buff = format_printf_message(msg, arg);
    va_end(arg);

    try
    {
      validator->on_validity_warning(buff);
    }
    catch (...)
    {
      validator->handle_exception();
    }
  }
}
#endif // LIBXMLXX_DISABLE_DEPRECATED

//static
ValidatorCallbackCFuncType Validator::get_callback_validity_error_cfunc()
{
  p_callback_error_or_warning = &callback_error_or_warning;
  return &c_callback_validity_error;
}

//static
ValidatorCallbackCFuncType Validator::get_callback_validity_warning_cfunc()
{
  p_callback_error_or_warning = &callback_error_or_warning;
  return &c_callback_validity_warning;
}

//static
void Validator::callback_error_or_warning(bool error, void* ctx,
                                          const char* msg, va_list var_args)
{
  // The caller of a libxml2 function with a callback is assumed to have
  // specified that the validation context is a xmlpp::Validator instance.
  auto validator = static_cast<Validator*>(ctx);

  if (validator)
  {
    // Convert msg and var_args to a string:
    const ustring buff = format_printf_message(msg, var_args);

    try
    {
      if (error)
        validator->on_validity_error(buff);
      else
        validator->on_validity_warning(buff);
    }
    catch (...)
    {
      validator->handle_exception();
    }
  }
}

void Validator::handle_exception()
{
  try
  {
    throw; // Re-throw current exception
  }
  catch (const exception& e)
  {
    exception_.reset(e.clone());
  }
#ifdef LIBXMLXX_HAVE_EXCEPTION_PTR
  catch (...)
  {
    exception_ = std::make_unique<wrapped_exception>(std::current_exception());
  }
#else
  catch (const std::exception& e)
  {
    exception_ = std::make_unique<exception>(e.what());
  }
  catch (...)
  {
    exception_ = std::make_unique<exception>("An exception was thrown that is not derived from std::exception or xmlpp::exception.\n"
      "It could not be caught and rethrown because this platform does not support std::exception_ptr.");
  }
#endif

  // Don't delete the DTD validation context or schema validation context
  // while validating. It would cause accesses to deallocated memory in libxml2
  // functions after the return from Validator::callback_validity_...().
  // Parser::handle_exception() calls xmlStopParser(), but there is no
  // xmlStopValidator() or similar function to call here.
  // We don't throw the exception here, since it would have to pass through
  // C functions. That's not guaranteed to work. It might work, but it depends
  // on the C compiler and the options used when building libxml2.

  //release_underlying();
}

void Validator::check_for_exception()
{
  check_for_validity_messages();

  if (exception_)
  {
    std::unique_ptr<exception> tmp(std::move(exception_));
    tmp->raise();
  }
}

} // namespace xmlpp
