#ifndef AMPL_AMPLEXCEPTION_H
#define AMPL_AMPLEXCEPTION_H

#include <cstdlib>
#include <stdexcept>
#include <string>
#include <sstream> // for stringstream
#include "ampl/cstringref.h"

namespace ampl {
/**
 * Represent an exception generated by %AMPL
 */
class AMPLException : public std::runtime_error {
 public:
  /**
   * Copy constructor
   */
  AMPLException(const AMPLException &e) : std::runtime_error(e.what()) {
    message_ = e.message_;
    line_ = e.line_;
    offset_ = e.offset_;
    filename_ = e.filename_;
  }

  /**
   * Constructor
   * \param cause Cause of the exception
   */
  explicit AMPLException(fmt::CStringRef cause = fmt::CStringRef(""))
      : std::runtime_error(cause.c_str()) {
    line_ = -1;
    offset_ = -1;
    parseMessage(cause);
  }

  /**
   * Constructor for %AMPL errors
   * \param filename Name of the file in which the exception occurred
   * \param row Row where the error is detected
   * \param offset Offset where the error is detected
   * \param message Message to be embedded in the exception
   */
  AMPLException(fmt::CStringRef filename, int row, int offset,
                fmt::CStringRef message)
      : std::runtime_error(
            getWhat(filename.c_str(), row, offset, message.c_str())),
        filename_(filename.c_str()),
        message_(message.c_str()) {
    line_ = row;
    offset_ = offset;
  }

  /**
   * Here to avoid the error looser throw specifier for virtual
   * AMPLException::~AMPLException()
   * in some old c++ compilers
   */
  ~AMPLException() throw() {}

  /**
   * Get the name of the file where the error was detected
   */
  const std::string &getSourceName() const { return filename_; }

  /**
   * Get the row where the error is located
   */
  int getLineNumber() const { return line_; }

  /**
   * Get the offset where the error is located
   */
  int getOffset() const { return offset_; }

  /**
   * Get the error message
   */
  const std::string &getMessage() const { return message_; }

  /**
   * Overwrites the source name for this error
   */
  void setSourceName(const std::string &sourceName) { filename_ = sourceName; }

 private:
  std::string filename_;
  int line_;
  int offset_;
  std::string message_;

  static std::string getWhat(fmt::StringRef filename, int row, int offset,
                             fmt::StringRef message) {
    std::stringstream out;
    if (filename.size() > 0) out << "file " << filename.to_string() << "\n";
    if (row >= 0) {
      out << "line " << row << " offset " << offset << "\n";
    }
    out << message.to_string() << "\n";
    return out.str();
  }
  template <std::size_t SIZE>
  const char *skip(const char *&cursor, const char (&str)[SIZE]) {
    const char *s = std::strstr(cursor, str);
    if (s) cursor = s + SIZE - 1;
    return s;
  }

  template <std::size_t SIZE>
  int getIntegerAfterMarker(const char *&cursor, const char (&marker)[SIZE]) {
    int value = 0;
    char *end = 0;
    if (skip(cursor, marker)) {
      value = std::strtol(cursor, &end, 10);
      cursor = end;
    }
    return value;
  }

  void parseMessage(fmt::CStringRef msg) {
    try {
      const char *cursor = msg.c_str();
      if (skip(cursor, "file ")) {
        if (const char *end = std::strchr(cursor, '\n')) {
          filename_.assign(cursor, end);
          cursor = end + 1;
        }
        line_ = getIntegerAfterMarker(cursor, "line ");
        offset_ = getIntegerAfterMarker(cursor, "offset ");
        skip(cursor, "\n");
      }
      message_.assign(cursor, std::strlen(cursor) - 1);
    } catch (...) {
      message_ = msg.c_str();
    }
  }
};

/**
 * Thrown if AMPL could not be started because of licensing errors
 */
class LicenseException : public std::runtime_error {
 public:
  /**
   * Constructor
   */
  LicenseException(const std::string &cause) : std::runtime_error(cause) {}
};

/**
 * Thrown if a file could not be found
 */
class FileIOException : public std::runtime_error {
 public:
  /**
   * Constructor
   */
  FileIOException(const std::string &cause) : std::runtime_error(cause) {}
};

/**
 * Thrown when accessing entities with the wrong number of indices,
 * e.g. accessing an indexed entity with Entity.get()
 */
class UnsupportedOperationException : public std::runtime_error {
 public:
  /**
   * Constructor
   */
  UnsupportedOperationException(const std::string &cause)
      : std::runtime_error(cause) {}
};

/**
 * Thrown when an invalid subscript is accessed
 */
class InvalidSubscriptException : public AMPLException {
 public:
  /**
   * Constructor
   */
  InvalidSubscriptException(fmt::CStringRef filename, int row, int offset,
                            fmt::CStringRef message)
      : AMPLException(filename, row, offset, message) {}
};

/**
 * Thrown when a syntax error is detected
 */
class SyntaxErrorException : public AMPLException {
 public:
  /**
   * Constructor
   */
  SyntaxErrorException(fmt::CStringRef filename, int row, int offset,
                       fmt::CStringRef message)
      : AMPLException(filename, row, offset, message) {}
};

/**
 * Thrown when an entity is accessed when no data has been
 * assigned yet
 */
class NoDataException : public std::runtime_error {
 public:
  /**
   * Constructor
   */
  NoDataException(fmt::CStringRef msg) : std::runtime_error(msg.c_str()) {}
};

/**
 * Thrown when presolver encounters some difficulties.
 * This often leads to a failed "write" command.
 */
class PresolveException : public std::runtime_error {
 public:
  /**
   * Constructor
   */
  PresolveException(fmt::CStringRef msg) : std::runtime_error(msg.c_str()) {}
};

/**
 * Thrown when the presolver detects an unfeasible model.
 * This leads to a failed "write" command.
 */
class InfeasibilityException : public PresolveException {
 public:
  /**
   * Constructor
   */
  InfeasibilityException(fmt::CStringRef msg) : PresolveException(msg) {}
};
 

}  // namespace ampl

#endif
