#ifndef MARISA_ALPHA_BASE_H_
#define MARISA_ALPHA_BASE_H_

// Visual C++ does not provide stdint.h.
#ifndef _MSC_VER
#include <stdint.h>
#endif  // _MSC_VER

#ifdef __cplusplus
#include <cstddef>
#include <new>
#else  // __cplusplus
#include <stddef.h>
#endif  // __cplusplus

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

#ifdef _MSC_VER
typedef unsigned __int8  marisa_alpha_uint8;
typedef unsigned __int16 marisa_alpha_uint16;
typedef unsigned __int32 marisa_alpha_uint32;
typedef unsigned __int64 marisa_alpha_uint64;
#else  // _MSC_VER
typedef uint8_t  marisa_alpha_uint8;
typedef uint16_t marisa_alpha_uint16;
typedef uint32_t marisa_alpha_uint32;
typedef uint64_t marisa_alpha_uint64;
#endif  // _MSC_VER

#define MARISA_ALPHA_UINT8_MAX  ((marisa_alpha_uint8)-1)
#define MARISA_ALPHA_UINT16_MAX ((marisa_alpha_uint16)-1)
#define MARISA_ALPHA_UINT32_MAX ((marisa_alpha_uint32)-1)
#define MARISA_ALPHA_UINT64_MAX ((marisa_alpha_uint64)-1)
#define MARISA_ALPHA_SIZE_MAX   ((size_t)-1)

#define MARISA_ALPHA_ZERO_TERMINATED MARISA_ALPHA_UINT32_MAX
#define MARISA_ALPHA_NOT_FOUND       MARISA_ALPHA_UINT32_MAX
#define MARISA_ALPHA_MISMATCH        MARISA_ALPHA_UINT32_MAX

#define MARISA_ALPHA_MAX_LENGTH     (MARISA_ALPHA_UINT32_MAX - 1)
#define MARISA_ALPHA_MAX_NUM_KEYS   (MARISA_ALPHA_UINT32_MAX - 1)

// marisa_alpha_status provides a list of error codes. Most of functions in
// libmarisa throw or return an error code.
typedef enum marisa_alpha_status_ {
  // MARISA_ALPHA_OK means that a requested operation has succeeded.
  MARISA_ALPHA_OK               = 0,

  // MARISA_ALPHA_HANDLE_ERROR means that a given handle is invalid.
  MARISA_ALPHA_HANDLE_ERROR     = 1,

  // MARISA_ALPHA_STATE_ERROR means that an object is not ready for a requested
  // operation. For example, an operation to modify a fixed container throws
  // an exception with this error code.
  MARISA_ALPHA_STATE_ERROR      = 2,

  // MARISA_ALPHA_PARAM_ERROR means that a given argument is invalid. For
  // example, some functions throw an exception with this error code when an
  // out-of-range value or a NULL pointer is given.
  MARISA_ALPHA_PARAM_ERROR      = 3,

  // MARISA_ALPHA_SIZE_ERROR means that a size exceeds its limit. This error
  // code is used when a building dictionary is too large or std::length_error
  // is catched.
  MARISA_ALPHA_SIZE_ERROR       = 4,

  // MARISA_ALPHA_MEMORY_ERROR means that a memory allocation has failed.
  MARISA_ALPHA_MEMORY_ERROR     = 5,

  // MARISA_ALPHA_IO_ERROR means that an I/O failure.
  MARISA_ALPHA_IO_ERROR         = 6,

  // MARISA_ALPHA_UNEXPECTED_ERROR means that an unexpected error has occurred.
  MARISA_ALPHA_UNEXPECTED_ERROR = 7
} marisa_alpha_status;

// marisa_alpha_strerror() returns a name of an error code.
const char *marisa_alpha_strerror(marisa_alpha_status status);

// Flags and masks for dictionary settings are defined as follows. Please note
// that unspecified value/flags will be replaced with default value/flags.
typedef enum marisa_alpha_flags_ {
  // A dictionary consinsts of 3 tries in default. If you want to change the
  // number of tries, please give it with other flags.
  MARISA_ALPHA_MIN_NUM_TRIES     = 0x00001,
  MARISA_ALPHA_MAX_NUM_TRIES     = 0x000FF,
  MARISA_ALPHA_DEFAULT_NUM_TRIES = 0x00003,

  // MARISA_ALPHA_PATRICIA_TRIE is usually a better choice. MARISA_ALPHA_PREFIX_TRIE is
  // provided for comparing prefix/patricia tries.
  MARISA_ALPHA_PATRICIA_TRIE     = 0x00100,
  MARISA_ALPHA_PREFIX_TRIE       = 0x00200,
  MARISA_ALPHA_DEFAULT_TRIE      = MARISA_ALPHA_PATRICIA_TRIE,

  // There are 3 kinds of TAIL implementations.
  // - MARISA_ALPHA_WITHOUT_TAIL:
  //   builds a dictionary without a TAIL. Its last trie has only 1-byte
  //   labels.
  // - MARISA_ALPHA_BINARY_TAIL:
  //   builds a dictionary with a binary-mode TAIL. Its last labels are stored
  //   as binary data.
  // - MARISA_ALPHA_TEXT_TAIL:
  //   builds a dictionary with a text-mode TAIL if its last labels do not
  //   contain NULL characters. The last labels are stored as zero-terminated
  //   string. Otherwise, a dictionary is built with a binary-mode TAIL.
  MARISA_ALPHA_WITHOUT_TAIL      = 0x01000,
  MARISA_ALPHA_BINARY_TAIL       = 0x02000,
  MARISA_ALPHA_TEXT_TAIL         = 0x04000,
  MARISA_ALPHA_DEFAULT_TAIL      = MARISA_ALPHA_TEXT_TAIL,

  // libmarisa arranges nodes in ascending order of their labels
  // (MARISA_ALPHA_LABEL_ORDER) or in descending order of their weights
  // (MARISA_ALPHA_WEIGHT_ORDER). MARISA_ALPHA_WEIGHT_ORDER is generally a
  // better choice because it enables faster lookups, but
  // MARISA_ALPHA_LABEL_ORDER is still useful if an application needs to
  // predict keys in label order.
  MARISA_ALPHA_LABEL_ORDER       = 0x10000,
  MARISA_ALPHA_WEIGHT_ORDER      = 0x20000,
  MARISA_ALPHA_DEFAULT_ORDER     = MARISA_ALPHA_WEIGHT_ORDER,

  // The default settings. 0 is equivalent to MARISA_ALPHA_DEFAULT_FLAGS.
  MARISA_ALPHA_DEFAULT_FLAGS     = MARISA_ALPHA_DEFAULT_NUM_TRIES
      | MARISA_ALPHA_DEFAULT_TRIE | MARISA_ALPHA_DEFAULT_TAIL | MARISA_ALPHA_DEFAULT_ORDER,

  MARISA_ALPHA_NUM_TRIES_MASK    = 0x000FF,
  MARISA_ALPHA_TRIE_MASK         = 0x00F00,
  MARISA_ALPHA_TAIL_MASK         = 0x0F000,
  MARISA_ALPHA_ORDER_MASK        = 0xF0000,
  MARISA_ALPHA_FLAGS_MASK        = 0xFFFFF
} marisa_alpha_flags;

#ifdef __cplusplus
}  // extern "C"
#endif  // __cplusplus

#ifdef __cplusplus
namespace marisa_alpha {

typedef ::marisa_alpha_uint8  UInt8;
typedef ::marisa_alpha_uint16 UInt16;
typedef ::marisa_alpha_uint32 UInt32;
typedef ::marisa_alpha_uint64 UInt64;

typedef ::marisa_alpha_status Status;

// An exception object stores a filename, a line number and an error code.
class Exception {
 public:
  Exception(const char *filename, int line, Status status)
      : filename_(filename), line_(line), status_(status) {}
  Exception(const Exception &ex)
      : filename_(ex.filename_), line_(ex.line_), status_(ex.status_) {}

  Exception &operator=(const Exception &rhs) {
    filename_ = rhs.filename_;
    line_ = rhs.line_;
    status_ = rhs.status_;
    return *this;
  }

  const char *filename() const {
    return filename_;
  }
  int line() const {
    return line_;
  }
  Status status() const {
    return status_;
  }

  // Same as std::exception, what() returns an error message.
  const char *what() const {
    return ::marisa_alpha_strerror(status_);
  }

 private:
  const char *filename_;
  int line_;
  Status status_;
};

// MARISA_ALPHA_THROW adds a filename and a line number to an exception.
#define MARISA_ALPHA_THROW(status) \
  (throw Exception(__FILE__, __LINE__, status))

// MARISA_ALPHA_THROW_IF throws an exception with `status' if `cond' is true.
#define MARISA_ALPHA_THROW_IF(cond, status) \
  (void)((!(cond)) || (MARISA_ALPHA_THROW(status), 0))

// MARISA_ALPHA_DEBUG_IF is used for debugging. For example,
// MARISA_ALPHA_DEBUG_IF is used to find out-of-range accesses in
// marisa::Vector, marisa::IntVector, etc.
#ifdef _DEBUG
#define MARISA_ALPHA_DEBUG_IF(cond, status) \
  MARISA_ALPHA_THROW_IF(cond, status)
#else
#define MARISA_ALPHA_DEBUG_IF(cond, status)
#endif

// To not include <algorithm> only for std::swap().
template <typename T>
void Swap(T *lhs, T *rhs) {
  MARISA_ALPHA_THROW_IF((lhs == NULL) || (rhs == NULL),
                        MARISA_ALPHA_PARAM_ERROR);
  T temp = *lhs;
  *lhs = *rhs;
  *rhs = temp;
}

}  // namespace marisa_alpha
#endif  // __cplusplus

#endif  // MARISA_ALPHA_BASE_H_