#ifndef AMPL_ERRORINFO_H_
#define AMPL_ERRORINFO_H_

#include <stdexcept>

#include "ampl/amplexception.h"
#include "ampl/ep/declarations.h"
#include "ampl/ep/error_information.h"

namespace ampl {
namespace internal {
extern "C" {
AMPLAPI void AMPL_ErrorInformation_delete(ErrorInformation *info);
}  // extern C

#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \
    (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || (_MSC_VER >= 1900)
#define AMPL_THROWSEX noexcept(false)
#else
#define AMPL_THROWSEX
#endif

inline void throwException(ErrorInformation *info) {
  /**
  Represents a generic system exception with message
  */
  class StdException : public std::exception {
    std::string message_;

   public:
    ~StdException() FMT_NOEXCEPT {}
    StdException(fmt::CStringRef cause) : message_(cause.c_str()) {}
    const char *what() const FMT_NOEXCEPT { return message_.c_str(); }
  };
  switch (info->errorType) {
    case error::LicenseException:
      throw LicenseException(info->message);
    case error::FileIOException:
      throw FileIOException(info->message);
    case error::UnsupportedOperationException:
      throw UnsupportedOperationException(info->message);
    case error::InvalidSubscriptException:
      throw InvalidSubscriptException(info->source, info->line, info->offset,
                                      info->message);
    case error::SyntaxErrorException:
      throw SyntaxErrorException(info->source, info->line, info->offset,
                                 info->message);
    case error::NoDataException:
      throw NoDataException(info->message);
    case error::AMPLException:
      throw AMPLException(info->source, info->line, info->offset,
                          info->message);
    case error::Runtime_Error:
      throw std::runtime_error(info->message);
    case error::Logic_Error:
      throw std::logic_error(info->message);
    case error::Out_Of_Range:
      throw std::out_of_range(info->message);
    case error::Invalid_Argument:
      throw std::invalid_argument(info->message);
    case error::Std_Exception: {
      throw StdException(info->message);
    }
    case error::OK: {
    }
  }
}

class ErrorInfo {
 private:
  ErrorInformation result_;

 public:
  operator ErrorInformation *() { return &result_; }

  ~ErrorInfo() AMPL_THROWSEX {
    if (result_.errorType != error::OK) {
      try {
        throwException(&result_);
      } catch (...) {
        AMPL_ErrorInformation_delete(&result_);
        throw;
      }
    }
  }
};
}  // namespace internal
}  // namespace ampl
#endif  // AMPL_ERRORINFO_H_
