/**
* Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
#ifndef IOTDB_COMMON_H
#define IOTDB_COMMON_H

#include <cstdint>
#include <ctime>
#include <exception>
#include <map>
#include <string>
#include <vector>

#include "Date.h"
#include "Endpoint.h"
#include "Export.h"
#include "Optional.h"
#include "Status.h"

using namespace std;

std::string getTimePrecision(int32_t timeFactor);

std::string formatDatetime(const std::string& format, const std::string& precision,
                           int64_t timestamp, const std::string& zoneId);

std::tm convertToTimestamp(int64_t value, int32_t timeFactor);
std::tm int32ToDate(int32_t value);

namespace Version {
enum Version { V_0_12, V_0_13, V_1_0 };
}

namespace CompressionType {
enum CompressionType {
  UNCOMPRESSED = (char)0,
  SNAPPY = (char)1,
  GZIP = (char)2,
  LZO = (char)3,
  SDT = (char)4,
  PAA = (char)5,
  PLA = (char)6,
  LZ4 = (char)7,
  ZSTD = (char)8,
  LZMA2 = (char)9,
};
}

namespace TSDataType {
enum TSDataType {
  BOOLEAN = (char)0,
  INT32 = (char)1,
  INT64 = (char)2,
  FLOAT = (char)3,
  DOUBLE = (char)4,
  TEXT = (char)5,
  VECTOR = (char)6,
  UNKNOWN = (char)7,
  TIMESTAMP = (char)8,
  DATE = (char)9,
  BLOB = (char)10,
  STRING = (char)11,
  OBJECT = (char)12
};
}

TSDataType::TSDataType getDataTypeByStr(const std::string& typeStr);

namespace TSEncoding {
enum TSEncoding {
  PLAIN = (char)0,
  DICTIONARY = (char)1,
  RLE = (char)2,
  DIFF = (char)3,
  TS_2DIFF = (char)4,
  BITMAP = (char)5,
  GORILLA_V1 = (char)6,
  REGULAR = (char)7,
  GORILLA = (char)8,
  ZIGZAG = (char)9,
  FREQ = (char)10,
  INVALID_ENCODING = (char)255
};
}

namespace TSStatusCode {
enum TSStatusCode {
  SUCCESS_STATUS = 200,

  INCOMPATIBLE_VERSION = 201,
  CONFIGURATION_ERROR = 202,
  START_UP_ERROR = 203,
  SHUT_DOWN_ERROR = 204,

  UNSUPPORTED_OPERATION = 300,
  EXECUTE_STATEMENT_ERROR = 301,
  MULTIPLE_ERROR = 302,
  ILLEGAL_PARAMETER = 303,
  OVERLAP_WITH_EXISTING_TASK = 304,
  INTERNAL_SERVER_ERROR = 305,

  REDIRECTION_RECOMMEND = 400,

  DATABASE_NOT_EXIST = 500,
  DATABASE_ALREADY_EXISTS = 501,
  SERIES_OVERFLOW = 502,
  TIMESERIES_ALREADY_EXIST = 503,
  TIMESERIES_IN_BLACK_LIST = 504,
  ALIAS_ALREADY_EXIST = 505,
  PATH_ALREADY_EXIST = 506,
  METADATA_ERROR = 507,
  PATH_NOT_EXIST = 508,
  ILLEGAL_PATH = 509,
  CREATE_TEMPLATE_ERROR = 510,
  DUPLICATED_TEMPLATE = 511,
  UNDEFINED_TEMPLATE = 512,
  TEMPLATE_NOT_SET = 513,
  DIFFERENT_TEMPLATE = 514,
  TEMPLATE_IS_IN_USE = 515,
  TEMPLATE_INCOMPATIBLE = 516,
  SEGMENT_NOT_FOUND = 517,
  PAGE_OUT_OF_SPACE = 518,
  RECORD_DUPLICATED = 519,
  SEGMENT_OUT_OF_SPACE = 520,
  PBTREE_FILE_NOT_EXISTS = 521,
  OVERSIZE_RECORD = 522,
  PBTREE_FILE_REDO_LOG_BROKEN = 523,
  TEMPLATE_NOT_ACTIVATED = 524,

  SYSTEM_READ_ONLY = 600,
  STORAGE_ENGINE_ERROR = 601,
  STORAGE_ENGINE_NOT_READY = 602,

  PLAN_FAILED_NETWORK_PARTITION = 721
};
}

class Field {
public:
  TSDataType::TSDataType dataType = TSDataType::UNKNOWN;
  Optional<bool> boolV;
  Optional<int> intV;
  Optional<IoTDBDate> dateV;
  Optional<int64_t> longV;
  Optional<float> floatV;
  Optional<double> doubleV;
  Optional<std::string> stringV;

  explicit Field(TSDataType::TSDataType a) {
    dataType = a;
  }

  Field() = default;

  bool isNull() const {
    switch (dataType) {
    case TSDataType::BOOLEAN:
      return !boolV.is_initialized();
    case TSDataType::INT32:
      return !intV.is_initialized();
    case TSDataType::INT64:
    case TSDataType::TIMESTAMP:
      return !longV.is_initialized();
    case TSDataType::FLOAT:
      return !floatV.is_initialized();
    case TSDataType::DOUBLE:
      return !doubleV.is_initialized();
    case TSDataType::TEXT:
    case TSDataType::STRING:
    case TSDataType::BLOB:
      return !stringV.is_initialized();
    case TSDataType::DATE:
      return !dateV.is_initialized() || dateV.value().is_not_a_date();
    default:
      return true;
    }
  }
};

enum class ColumnCategory { TAG, FIELD, ATTRIBUTE };

class MyStringBuffer {
public:
  MyStringBuffer();
  explicit MyStringBuffer(const std::string& str);

  void reserve(size_t n);
  void clear();
  bool hasRemaining();
  int getInt();
  IoTDBDate getDate();
  int64_t getInt64();
  float getFloat();
  double getDouble();
  char getChar();
  bool getBool();
  std::string getString();

  void putInt(int ins);
  void putDate(IoTDBDate date);
  void putInt64(int64_t ins);
  void putFloat(float ins);
  void putDouble(double ins);
  void putChar(char ins);
  void putBool(bool ins);
  void putString(const std::string& ins);
  void concat(const std::string& ins);

public:
  std::string str;
  size_t pos;

private:
  void checkBigEndian();
  const char* getOrderedByte(size_t len);
  void putOrderedByte(char* buf, int len);

private:
  bool isBigEndian{};
  char numericBuf[8]{};
};

class BitMap {
public:
  explicit BitMap(size_t size = 0);
  void resize(size_t size);
  bool mark(size_t position);
  bool unmark(size_t position);
  void markAll();
  void reset();
  bool isMarked(size_t position) const;
  bool isAllUnmarked() const;
  bool isAllMarked() const;
  const std::vector<char>& getByteArray() const;
  size_t getSize() const;

private:
  size_t size;
  std::vector<char> bits;
};

class IoTDBException : public std::exception {
public:
  IoTDBException() = default;

  explicit IoTDBException(const std::string& m) : message(m) {}

  explicit IoTDBException(const char* m) : message(m) {}

  virtual const char* what() const noexcept override {
    return message.c_str();
  }

private:
  std::string message;
};

std::string extractExceptionMessage(const std::exception& exception);
std::string extractExceptionMessage(const std::exception_ptr& exceptionPtr);

class DateTimeParseException : public IoTDBException {
private:
  std::string parsedString;
  int errorIndex;

public:
  explicit DateTimeParseException(const std::string& message, std::string parsedData,
                                  int errorIndex)
      : IoTDBException(message), parsedString(std::move(parsedData)), errorIndex(errorIndex) {}

  explicit DateTimeParseException(const std::string& message, std::string parsedData,
                                  int errorIndex, const std::exception& cause)
      : IoTDBException(message + " [Caused by: " + cause.what() + "]"),
        parsedString(std::move(parsedData)), errorIndex(errorIndex) {}

  const std::string& getParsedString() const noexcept {
    return parsedString;
  }

  int getErrorIndex() const noexcept {
    return errorIndex;
  }

  const char* what() const noexcept override {
    static std::string fullMsg;
    fullMsg = std::string(IoTDBException::what()) + "\nParsed data: " + parsedString +
              "\nError index: " + std::to_string(errorIndex);
    return fullMsg.c_str();
  }
};

class IoTDBConnectionException : public IoTDBException {
public:
  IoTDBConnectionException() {}

  explicit IoTDBConnectionException(const char* m) : IoTDBException(m) {}

  explicit IoTDBConnectionException(const std::string& m) : IoTDBException(m) {}
};

class ExecutionException : public IoTDBException {
public:
  ExecutionException() {}

  explicit ExecutionException(const char* m) : IoTDBException(m) {}

  explicit ExecutionException(const std::string& m) : IoTDBException(m) {}

  explicit ExecutionException(const std::string& m, const Status& tsStatus)
      : IoTDBException(m), status(tsStatus) {}

  Status status;
};

class BatchExecutionException : public IoTDBException {
public:
  BatchExecutionException() {}

  explicit BatchExecutionException(const char* m) : IoTDBException(m) {}

  explicit BatchExecutionException(const std::string& m) : IoTDBException(m) {}

  explicit BatchExecutionException(const std::vector<Status>& statusList)
      : statusList(statusList) {}

  BatchExecutionException(const std::string& m, const std::vector<Status>& statusList)
      : IoTDBException(m), statusList(statusList) {}

  std::vector<Status> statusList;
};

class RedirectException : public IoTDBException {
public:
  RedirectException() {}

  explicit RedirectException(const char* m) : IoTDBException(m) {}

  explicit RedirectException(const std::string& m) : IoTDBException(m) {}

  RedirectException(const std::string& m, const Endpoint& endPoint)
      : IoTDBException(m), endPoint(endPoint) {}

  RedirectException(const std::string& m, const map<string, Endpoint>& deviceEndPointMap)
      : IoTDBException(m), deviceEndPointMap(deviceEndPointMap) {}

  RedirectException(const std::string& m, const vector<Endpoint>& endPointList)
      : IoTDBException(m), endPointList(endPointList) {}

  Endpoint endPoint;
  map<string, Endpoint> deviceEndPointMap;
  vector<Endpoint> endPointList;
};

class UnSupportedDataTypeException : public IoTDBException {
public:
  UnSupportedDataTypeException() {}

  explicit UnSupportedDataTypeException(const char* m) : IoTDBException(m) {}

  explicit UnSupportedDataTypeException(const std::string& m)
      : IoTDBException("UnSupported dataType: " + m) {}
};

class SchemaNotFoundException : public IoTDBException {
public:
  SchemaNotFoundException() {}

  explicit SchemaNotFoundException(const char* m) : IoTDBException(m) {}

  explicit SchemaNotFoundException(const std::string& m) : IoTDBException(m) {}
};

class StatementExecutionException : public IoTDBException {
public:
  StatementExecutionException() {}

  explicit StatementExecutionException(const char* m) : IoTDBException(m) {}

  explicit StatementExecutionException(const std::string& m) : IoTDBException(m) {}
};

enum LogLevelType { LEVEL_DEBUG = 0, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR };

extern IOTDB_SESSION_API LogLevelType LOG_LEVEL;

#define log_debug(fmt, ...)                                                                        \
  do {                                                                                             \
    if (LOG_LEVEL <= LEVEL_DEBUG) {                                                                \
      string s = string("[DEBUG] %s:%d (%s) - ") + fmt + "\n";                                     \
      printf(s.c_str(), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);                          \
    }                                                                                              \
  } while (0)
#define log_info(fmt, ...)                                                                         \
  do {                                                                                             \
    if (LOG_LEVEL <= LEVEL_INFO) {                                                                 \
      string s = string("[INFO]  %s:%d (%s) - ") + fmt + "\n";                                     \
      printf(s.c_str(), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);                          \
    }                                                                                              \
  } while (0)
#define log_warn(fmt, ...)                                                                         \
  do {                                                                                             \
    if (LOG_LEVEL <= LEVEL_WARN) {                                                                 \
      string s = string("[WARN]  %s:%d (%s) - ") + fmt + "\n";                                     \
      printf(s.c_str(), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);                          \
    }                                                                                              \
  } while (0)
#define log_error(fmt, ...)                                                                        \
  do {                                                                                             \
    if (LOG_LEVEL <= LEVEL_ERROR) {                                                                \
      string s = string("[ERROR] %s:%d (%s) - ") + fmt + "\n";                                     \
      printf(s.c_str(), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);                          \
    }                                                                                              \
  } while (0)

#endif
