#include "debug.hpp"
#include "cgWrappers.hpp"
#include "CgStr.hpp"
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace synth;

static char const* const kLinkageSpellings[] = {
    "Invalid",
    "NoLinkage",
    "Internal",
    "UniqueExternal",
    "External"
};
static char const* const kLangSpellings[] = {
    "Invalid",
    "C",
    "ObjC",
    "C++"
};

// Returns decl
static std::pair<CXType, CXCursor> dumpType(CXType ty, CXCursor ref)
{

    if (ty.kind == CXType_Invalid) {
        return {
            clang_getCursorType(clang_getNullCursor()),
            clang_getNullCursor()
        };
    }
    std::clog << CgStr(clang_getTypeKindSpelling(ty.kind))
        << ' ' << CgStr(clang_getTypeSpelling(ty));
    CXCursor decl = clang_getTypeDeclaration(ty);
    if (!clang_equalCursors(ref, decl))
        std::clog << " @ " << decl;
    std::clog << '\n';
    return {ty, decl};
}

static void dumpAnnotatedToken(
    CXTranslationUnit tu,
    CXToken tok,
    CXCursor cur,
    bool extrainfo,
    CXFile f = nullptr)
{
    writeToken(std::clog, tok, tu, f);
    std::clog << '\t';
    dumpSingleCursor(cur, 0, f);
    if (extrainfo) {
        CgStr usr = clang_getCursorUSR(cur);
        if (!usr.empty())
            std::clog << "    U: " << usr << '\n';
        std::clog << "    T: ";
        auto tyWithDecl = dumpType(clang_getCursorType(cur), cur);
        CXType canonTy = clang_getCanonicalType(tyWithDecl.first);
        if (!clang_equalTypes(tyWithDecl.first, canonTy)) {
            std::clog << "    CT: ";
            dumpType(canonTy, tyWithDecl.second);
        }
        CXLinkageKind linkage = clang_getCursorLinkage(cur);
        if (linkage != CXLinkage_Invalid)
            std::clog << "    Lnk: " << kLinkageSpellings[linkage];
        CXLanguageKind lang = clang_getCursorLanguage(cur);
        if (lang != CXLanguage_Invalid)
            std::clog << "   Lng: " << kLangSpellings[lang];
        if (lang != CXLanguage_Invalid || linkage != CXLinkage_Invalid)
            std::clog << '\n';
        CXCursor canon = clang_getCanonicalCursor(cur);
        if (!clang_equalCursors(cur, canon))
            std::clog << "    Can: " << canon << '\n';
        CXCursor def = clang_getCursorDefinition(cur);
        if (!clang_equalCursors(cur, def))
            std::clog << "    Def: " << def << '\n';
    }
    CXCursor c2 = clang_getCursor(tu, clang_getTokenLocation(tu, tok));
    if (!clang_equalCursors(cur, c2))
        dumpSingleCursor(c2, 2, f);
}

static void dumpTokens(CXCursor root, bool annotate, bool extrainfo)
{
    // TODO: Duplicate from annotate.cpp processFile()

    CXTranslationUnit tu = clang_Cursor_getTranslationUnit(root);
    CXToken* tokens;
    unsigned numTokens;
    CXSourceRange rng = clang_getCursorExtent(root);
    clang_tokenize(tu, rng, &tokens, &numTokens);
    CXFile f;
    clang_getFileLocation(
        clang_getRangeStart(rng), &f, nullptr, nullptr, nullptr);

    CgTokensHandle tokCleanup(tokens, numTokens, tu);

    if (numTokens > 0) {
        if (annotate) {
            std::vector<CXCursor> tokCurs(numTokens);
            clang_annotateTokens(tu, tokens, numTokens, tokCurs.data());
            for (unsigned i = 0; i < numTokens; ++i) {
                CXCursor cur = tokCurs[i];
                dumpAnnotatedToken(tu, tokens[i], cur, extrainfo, f);
            }
        } else {
            for (unsigned i = 0; i < numTokens; ++i) {
                writeToken(std::clog, tokens[i], tu, f);
                std::clog << '\n';
            }
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc < 3) {
        std::cerr << "Usage: " << argv[0] << " <flags> <clang-cmd>\n";
        return EXIT_FAILURE;
    }

    CgIdxHandle hcidx(clang_createIndex(true, true));

    // TODO: Duplicated from annotate.cpp synth::processTu() {{{
    CXTranslationUnit tu = nullptr;
    unsigned options = 0;
    if (std::strchr(argv[1], 'p'))
        options |= CXTranslationUnit_DetailedPreprocessingRecord;

    CXErrorCode err = clang_parseTranslationUnit2FullArgv(
        hcidx.get(),
        /*source_filename:*/ nullptr, // Included in commandline.
        argv + 2,
        argc - 2,
        /*unsaved_files:*/ nullptr,
        /*num_unsaved_files:*/ 0,
        options,
        &tu);
    CgTuHandle htu(tu);
    if (err != CXError_Success) {
        std::cerr << "Failed parsing translation unit (code "
                  << static_cast<int>(err)
                  << ")\n";
        return err + 10;
    }
    // }}}

    if (std::strchr(argv[1], 'a'))
        dumpAst(clang_getTranslationUnitCursor(tu), 0);

    if (std::strchr(argv[1], 't')) {
        dumpTokens(
            clang_getTranslationUnitCursor(tu),
            std::strchr(argv[1], 'c') != nullptr,
            std::strchr(argv[1], 'x') != nullptr);
    }

    return 0;
}