#include "LibLsp/lsp/textDocument/completion.h"
#include "LibLsp/lsp/textDocument/document_symbol.h"
#include "LibLsp/lsp/lsMarkedString.h"
#include "LibLsp/lsp/textDocument/hover.h"
#include "LibLsp/lsp/textDocument/prepareRename.h"
#include <LibLsp/lsp/textDocument/typeHierarchy.h>

#include "LibLsp/lsp/textDocument/semanticHighlighting.h"
#include "LibLsp/lsp/textDocument/SemanticTokens.h"
#include "LibLsp/JsonRpc/json.h"

constexpr unsigned SemanticTokenEncodingSize = 5;

std::string to_string(SemanticTokenType _type)
{
    switch (_type)
    {

    case ls_namespace:
        return "namespace";
        /**
                         * Represents a generic type. Acts as a fallback for types which
                         * can"t be mapped to a specific type like class or enum.
                         */
    case ls_type:
        return "type";
    case ls_class:
        return "class";
    case ls_enum:
        return "enum";
    case ls_interface:
        return "interface";
    case ls_struct:
        return "struct";
    case ls_typeParameter:
        return "typeParameter";
    case ls_parameter:
        return "parameter";
    case ls_variable:
        return "variable";
    case ls_property:
        return "property";
    case ls_enumMember:
        return "enumMember";
    case ls_event:
        return "event";
    case ls_function:
        return "function";
    case ls_method:
        return "method";
    case ls_macro:
        return "macro";
    case ls_keyword:
        return "keyword";
    case ls_modifier:
        return "modifier";
    case ls_comment:
        return "comment";
    case ls_string:
        return "string";
    case ls_number:
        return "number";
    case ls_regexp:
        return "regexp";
    case ls_operator:
        return "operator";
    default:
        return "unknown";
    }
}

unsigned toSemanticTokenType(std::vector<SemanticTokenType>& modifiers)
{
    unsigned encode_type = 0;
    for (auto bit : modifiers)
    {
        encode_type = encode_type | (0b00000001 << bit);
    }
    return encode_type;
}

std::string to_string(TokenType_JDT _type)
{
    switch (_type)
    {
    case PACKAGE_JDT:
        return "namespace";
    case CLASS_JDT:
        return "class";
    case INTERFACE_JDT:
        return "interface";
    case ENUM_JDT:
        return "enum";
    case ENUM_MEMBER_JDT:
        return "enumMember";
    case TYPE_JDT:
        return "type";
    case TYPE_PARAMETER_JDT:
        return "typeParameter";
    case ANNOTATION_JDT:
        return "annotation";
    case ANNOTATION_MEMBER_JDT:
        return "annotationMember";
    case METHOD_JDT:
        return "function";
    case PROPERTY_JDT:
        return "property";
    case VARIABLE_JDT:
        return "variable";
    case PARAMETER_JDT:
        return "parameter";
    }
    return "unknown";
}

std::string to_string(SemanticTokenModifier modifier)
{
    switch (modifier)
    {
    case ls_declaration:
        return "declaration";
    case ls_definition:
        return "definition";
    case ls_readonly:
        return "readonly";
    case ls_static:
        return "static";
    case ls_deprecated:
        return "deprecated";
    case ls_abstract:
        return "abstract";
    case ls_async:
        return "async";
    case ls_modification:
        return "modification";
    case ls_documentation:
        return "documentation";
    case ls_defaultLibrary:
        return "defaultLibrary";
    default:
        return "unknown";
    }
}

unsigned toSemanticTokenModifiers(std::vector<SemanticTokenModifier>& modifiers)
{
    unsigned encodedModifiers = 0;
    for (auto bit : modifiers)
    {
        encodedModifiers = encodedModifiers | (0b00000001 << bit);
    }
    return encodedModifiers;
}

std::string toSemanticTokenType(HighlightingKind_clangD kind)
{
    switch (kind)
    {
    case HighlightingKind_clangD::Variable:
    case HighlightingKind_clangD::LocalVariable:
    case HighlightingKind_clangD::StaticField:
        return "variable";
    case HighlightingKind_clangD::Parameter:
        return "parameter";
    case HighlightingKind_clangD::Function:
        return "function";
    case HighlightingKind_clangD::Method:
        return "method";
    case HighlightingKind_clangD::StaticMethod:
        // FIXME: better method with static modifier?
        return "function";
    case HighlightingKind_clangD::Field:
        return "property";
    case HighlightingKind_clangD::Class:
        return "class";
    case HighlightingKind_clangD::Interface:
        return "interface";
    case HighlightingKind_clangD::Enum:
        return "enum";
    case HighlightingKind_clangD::EnumConstant:
        return "enumMember";
    case HighlightingKind_clangD::Typedef:
    case HighlightingKind_clangD::Type:
        return "type";
    case HighlightingKind_clangD::Unknown:
        return "unknown"; // nonstandard
    case HighlightingKind_clangD::Namespace:
        return "namespace";
    case HighlightingKind_clangD::TemplateParameter:
        return "typeParameter";
    case HighlightingKind_clangD::Concept:
        return "concept"; // nonstandard
    case HighlightingKind_clangD::Primitive:
        return "type";
    case HighlightingKind_clangD::Macro:
        return "macro";
    case HighlightingKind_clangD::InactiveCode:
        return "comment";
    }
    return ("unhandled HighlightingKind_clangD");
}

std::string toSemanticTokenModifier(HighlightingModifier_clangD modifier)
{
    switch (modifier)
    {
    case HighlightingModifier_clangD::Declaration:
        return "declaration";
    case HighlightingModifier_clangD::Deprecated:
        return "deprecated";
    case HighlightingModifier_clangD::Readonly:
        return "readonly";
    case HighlightingModifier_clangD::Static:
        return "static";
    case HighlightingModifier_clangD::Deduced:
        return "deduced"; // nonstandard
    case HighlightingModifier_clangD::Abstract:
        return "abstract";
    case HighlightingModifier_clangD::DependentName:
        return "dependentName"; // nonstandard
    case HighlightingModifier_clangD::DefaultLibrary:
        return "defaultLibrary";
    case HighlightingModifier_clangD::FunctionScope:
        return "functionScope"; // nonstandard
    case HighlightingModifier_clangD::ClassScope:
        return "classScope"; // nonstandard
    case HighlightingModifier_clangD::FileScope:
        return "fileScope"; // nonstandard
    case HighlightingModifier_clangD::GlobalScope:
        return "globalScope"; // nonstandard
    }
    return ("unhandled HighlightingModifier_clangD");
}

bool operator==(SemanticToken const& l, SemanticToken const& r)
{
    return std::tie(l.deltaLine, l.deltaStart, l.length, l.tokenType, l.tokenModifiers)
           == std::tie(r.deltaLine, r.deltaStart, r.length, r.tokenType, r.tokenModifiers);
}

std::vector<int32_t> SemanticTokens::encodeTokens(std::vector<SemanticToken>& tokens)
{
    std::vector<int32_t> result;
    result.reserve(SemanticTokenEncodingSize * tokens.size());
    for (auto const& tok : tokens)
    {
        result.push_back(tok.deltaLine);
        result.push_back(tok.deltaStart);
        result.push_back(tok.length);
        result.push_back(tok.tokenType);
        result.push_back(tok.tokenModifiers);
    }
    assert(result.size() == SemanticTokenEncodingSize * tokens.size());
    return result;
}

void Reflect(Reader& visitor, TextDocumentComplete::Either& value)
{
    if (visitor.IsArray())
    {
        Reflect(visitor, value.first);
    }
    else
    {

        Reflect(visitor, value.second);
    }
}
void Reflect(Reader& visitor, TextDocumentDocumentSymbol::Either& value)
{
    if (visitor.HasMember("location"))
    {
        Reflect(visitor, value.first);
    }
    else
    {
        Reflect(visitor, value.second);
    }
}

void Reflect(Reader& visitor, std::pair<optional<std::string>, optional<lsMarkedString>>& value)
{

    if (!visitor.IsString())
    {
        Reflect(visitor, value.second);
    }
    else
    {
        Reflect(visitor, value.first);
    }
}

void Reflect(Reader& visitor, std::pair<optional<std::string>, optional<MarkupContent>>& value)
{
    if (!visitor.IsString())
    {
        Reflect(visitor, value.second);
    }
    else
    {
        Reflect(visitor, value.first);
    }
}
void Reflect(Reader& visitor, TextDocumentHover::Either& value)
{
    JsonReader& reader = dynamic_cast<JsonReader&>(visitor);
    if (reader.IsArray())
    {
        Reflect(visitor, value.first);
    }
    else if (reader.m_->IsObject())
    {
        Reflect(visitor, value.second);
    }
}

void Reflect(Reader& visitor, TextDocumentPrepareRenameResult& value)
{
    if (visitor.HasMember("placeholder"))
    {
        Reflect(visitor, value.second);
    }
    else
    {
        Reflect(visitor, value.first);
    }
}

namespace RefactorProposalUtility
{
char const* APPLY_REFACTORING_COMMAND_ID = "java.action.applyRefactoringCommand";
char const* EXTRACT_VARIABLE_ALL_OCCURRENCE_COMMAND = "extractVariableAllOccurrence";
char const* EXTRACT_VARIABLE_COMMAND = "extractVariable";
char const* EXTRACT_CONSTANT_COMMAND = "extractConstant";
char const* EXTRACT_METHOD_COMMAND = "extractMethod";
char const* EXTRACT_FIELD_COMMAND = "extractField";
char const* CONVERT_VARIABLE_TO_FIELD_COMMAND = "convertVariableToField";
char const* MOVE_FILE_COMMAND = "moveFile";
char const* MOVE_INSTANCE_METHOD_COMMAND = "moveInstanceMethod";
char const* MOVE_STATIC_MEMBER_COMMAND = "moveStaticMember";
char const* MOVE_TYPE_COMMAND = "moveType";
}; // namespace RefactorProposalUtility
namespace QuickAssistProcessor
{

char const* SPLIT_JOIN_VARIABLE_DECLARATION_ID =
    "org.eclipse.jdt.ls.correction.splitJoinVariableDeclaration.assist"; //$NON-NLS-1$
char const* CONVERT_FOR_LOOP_ID = "org.eclipse.jdt.ls.correction.convertForLoop.assist"; //$NON-NLS-1$
char const* ASSIGN_TO_LOCAL_ID = "org.eclipse.jdt.ls.correction.assignToLocal.assist"; //$NON-NLS-1$
char const* ASSIGN_TO_FIELD_ID = "org.eclipse.jdt.ls.correction.assignToField.assist"; //$NON-NLS-1$
char const* ASSIGN_PARAM_TO_FIELD_ID = "org.eclipse.jdt.ls.correction.assignParamToField.assist"; //$NON-NLS-1$
char const* ASSIGN_ALL_PARAMS_TO_NEW_FIELDS_ID =
    "org.eclipse.jdt.ls.correction.assignAllParamsToNewFields.assist"; //$NON-NLS-1$
char const* ADD_BLOCK_ID = "org.eclipse.jdt.ls.correction.addBlock.assist"; //$NON-NLS-1$
char const* EXTRACT_LOCAL_ID = "org.eclipse.jdt.ls.correction.extractLocal.assist"; //$NON-NLS-1$
char const* EXTRACT_LOCAL_NOT_REPLACE_ID =
    "org.eclipse.jdt.ls.correction.extractLocalNotReplaceOccurrences.assist"; //$NON-NLS-1$
char const* EXTRACT_CONSTANT_ID = "org.eclipse.jdt.ls.correction.extractConstant.assist"; //$NON-NLS-1$
char const* INLINE_LOCAL_ID = "org.eclipse.jdt.ls.correction.inlineLocal.assist"; //$NON-NLS-1$
char const* CONVERT_LOCAL_TO_FIELD_ID = "org.eclipse.jdt.ls.correction.convertLocalToField.assist"; //$NON-NLS-1$
char const* CONVERT_ANONYMOUS_TO_LOCAL_ID =
    "org.eclipse.jdt.ls.correction.convertAnonymousToLocal.assist"; //$NON-NLS-1$
char const* CONVERT_TO_STRING_BUFFER_ID = "org.eclipse.jdt.ls.correction.convertToStringBuffer.assist"; //$NON-NLS-1$
char const* CONVERT_TO_MESSAGE_FORMAT_ID = "org.eclipse.jdt.ls.correction.convertToMessageFormat.assist"; //$NON-NLS-1$;
char const* EXTRACT_METHOD_INPLACE_ID = "org.eclipse.jdt.ls.correction.extractMethodInplace.assist"; //$NON-NLS-1$;

char const* CONVERT_ANONYMOUS_CLASS_TO_NESTED_COMMAND = "convertAnonymousClassToNestedCommand";
}; // namespace QuickAssistProcessor

void Reflect(Reader& reader, TypeHierarchyDirection& value)
{
    if (!reader.IsString())
    {
        value = TypeHierarchyDirection::Both;
        return;
    }
    std::string v = reader.GetString();
    if (v == "Children")
    {
        value = TypeHierarchyDirection::Both;
    }
    else if (v == "Parents")
    {
        value = TypeHierarchyDirection::Parents;
    }
    else if (v == "Both")
    {
        value = TypeHierarchyDirection::Both;
    }
}

void Reflect(Writer& writer, TypeHierarchyDirection& value)
{
    switch (value)
    {
    case TypeHierarchyDirection::Children:
        writer.String("Children");
        break;
    case TypeHierarchyDirection::Parents:
        writer.String("Parents");
        break;
    case TypeHierarchyDirection::Both:
        writer.String("Both");
        break;
    }
}
