// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "config.hh"
#include "serializer.hh"
#include "utils.hh"

#include <rapidjson/fwd.h>

#include <chrono>
#include <iosfwd>
#include <string>

namespace ccls {
struct RequestId {
  // The client can send the request id as an int or a string. We should output
  // the same format we received.
  enum Type { kNone, kInt, kString };
  Type type = kNone;

  std::string value;

  bool valid() const { return type != kNone; }
};
void reflect(JsonReader &visitor, RequestId &value);
void reflect(JsonWriter &visitor, RequestId &value);

struct InMessage {
  RequestId id;
  std::string method;
  std::unique_ptr<char[]> message;
  std::unique_ptr<rapidjson::Document> document;
  std::chrono::steady_clock::time_point deadline;
  std::string backlog_path;
};

enum class ErrorCode {
  // Defined by JSON RPC
  ParseError = -32700,
  InvalidRequest = -32600,
  MethodNotFound = -32601,
  InvalidParams = -32602,
  InternalError = -32603,
  serverErrorStart = -32099,
  serverErrorEnd = -32000,
  ServerNotInitialized = -32002,
  UnknownErrorCode = -32001,

  // Defined by the protocol.
  RequestCancelled = -32800,
};

struct ResponseError {
  ErrorCode code;
  std::string message;
};

constexpr char ccls_xref[] = "ccls.xref";
constexpr char window_showMessage[] = "window/showMessage";

struct DocumentUri {
  static DocumentUri fromPath(const std::string &path);

  bool operator==(const DocumentUri &o) const { return raw_uri == o.raw_uri; }
  bool operator<(const DocumentUri &o) const { return raw_uri < o.raw_uri; }

  void setPath(const std::string &path);
  std::string getPath() const;

  std::string raw_uri;
};

struct Position {
  int line = 0;
  int character = 0;
  bool operator==(const Position &o) const { return line == o.line && character == o.character; }
  bool operator<(const Position &o) const { return line != o.line ? line < o.line : character < o.character; }
  bool operator<=(const Position &o) const { return line != o.line ? line < o.line : character <= o.character; }
  std::string toString() const;
};

struct lsRange {
  Position start;
  Position end;
  bool operator==(const lsRange &o) const { return start == o.start && end == o.end; }
  bool operator<(const lsRange &o) const { return !(start == o.start) ? start < o.start : end < o.end; }
  bool includes(const lsRange &o) const { return start <= o.start && o.end <= end; }
  bool intersects(const lsRange &o) const { return start < o.end && o.start < end; }
};

struct Location {
  DocumentUri uri;
  lsRange range;
  bool operator==(const Location &o) const { return uri == o.uri && range == o.range; }
  bool operator<(const Location &o) const { return !(uri == o.uri) ? uri < o.uri : range < o.range; }
};

struct LocationLink {
  std::string targetUri;
  lsRange targetRange;
  lsRange targetSelectionRange;
  explicit operator bool() const { return targetUri.size(); }
  explicit operator Location() && { return {DocumentUri{std::move(targetUri)}, targetSelectionRange}; }
  bool operator==(const LocationLink &o) const {
    return targetUri == o.targetUri && targetSelectionRange == o.targetSelectionRange;
  }
  bool operator<(const LocationLink &o) const {
    return !(targetUri == o.targetUri) ? targetUri < o.targetUri : targetSelectionRange < o.targetSelectionRange;
  }
};

enum class SymbolKind : uint8_t {
  Unknown = 0,

  File = 1,
  Module = 2,
  Namespace = 3,
  Package = 4,
  Class = 5,
  Method = 6,
  Property = 7,
  Field = 8,
  Constructor = 9,
  Enum = 10,
  Interface = 11,
  Function = 12,
  Variable = 13,
  Constant = 14,
  String = 15,
  Number = 16,
  Boolean = 17,
  Array = 18,
  Object = 19,
  Key = 20,
  Null = 21,
  EnumMember = 22,
  Struct = 23,
  Event = 24,
  Operator = 25,

  // For C++, this is interpreted as "template parameter" (including
  // non-type template parameters).
  TypeParameter = 26,
  FirstNonStandard,

  // ccls extensions
  // See also https://github.com/Microsoft/language-server-protocol/issues/344
  // for new SymbolKind clang/Index/IndexSymbol.h clang::index::SymbolKind
  TypeAlias = 252,
  Parameter = 253,
  StaticMethod = 254,
  Macro = 255,
  FirstExtension = TypeAlias,
  LastExtension = Macro,
};

struct SymbolInformation {
  std::string_view name;
  SymbolKind kind;
  Location location;
  std::optional<std::string_view> containerName;
};

struct TextDocumentIdentifier {
  DocumentUri uri;
};

struct VersionedTextDocumentIdentifier {
  DocumentUri uri;
  // The version number of this document.  number | null
  std::optional<int> version;
};

struct TextEdit {
  lsRange range;
  std::string newText;
};

struct TextDocumentItem {
  DocumentUri uri;
  std::string languageId;
  int version;
  std::string text;
};

struct TextDocumentContentChangeEvent {
  // The range of the document that changed.
  std::optional<lsRange> range;
  // The length of the range that got replaced.
  std::optional<int> rangeLength;
  // The new text of the range/document.
  std::string text;
};

struct TextDocumentDidChangeParam {
  VersionedTextDocumentIdentifier textDocument;
  std::vector<TextDocumentContentChangeEvent> contentChanges;
};

struct WorkDoneProgress {
  const char *kind;
  std::optional<std::string> title;
  std::optional<std::string> message;
  std::optional<int> percentage;
};
struct WorkDoneProgressParam {
  const char *token;
  WorkDoneProgress value;
};

struct WorkspaceFolder {
  DocumentUri uri;
  std::string name;
};

enum class MessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 };
REFLECT_UNDERLYING(MessageType)

struct DiagnosticRelatedInformation {
  Location location;
  std::string message;
};

struct Diagnostic {
  lsRange range;
  int severity = 0;
  int code = 0;
  std::string source = "ccls";
  std::string message;
  std::vector<DiagnosticRelatedInformation> relatedInformation;
  std::vector<TextEdit> fixits_;
};

struct ShowMessageParam {
  MessageType type = MessageType::Error;
  std::string message;
};

// Used to identify the language at a file level. The ordering is important, as
// a file previously identified as `C`, will be changed to `Cpp` if it
// encounters a c++ declaration.
enum class LanguageId {
  Unknown = -1,
  C = 0,
  Cpp = 1,
  ObjC = 2,
  ObjCpp = 3,
  Cuda = 4,
};

} // namespace ccls
