//
// Created by pgess on 4/16/15.
//

#include "gtest/gtest.h"

#include <llvm/IR/DerivedTypes.h>
#include "clang/Driver/Options.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/CodeGen/CodeGenAction.h"
#include "clang/CodeGen/CodeGenABITypes.h"
#include "llvm/IR/LLVMContext.h"
#include "clang/Basic/TargetInfo.h"
#include <boost/format.hpp>

#include "../../vendors/clang-codegen-private-3.8/CodeGenModule.h"


using namespace std;
using namespace clang;
using namespace clang::driver;
using namespace clang::tooling;
using namespace clang::ast_matchers;
using namespace llvm;

/*
class PrintFunctionsConsumer : public ASTConsumer {
public:
    bool HandleTopLevelDecl(DeclGroupRef DG) override {
        for (DeclGroupRef::iterator i = DG.begin(), e = DG.end(); i != e; ++i) {
            const Decl *D = *i;
            if (const NamedDecl *ND = dyn_cast<NamedDecl>(D))
                llvm::errs() << "top-level-decl: \"" << ND->getNameAsString() << "\"\n";
        }

        return true;
    }
};

class PFAction : public ASTFrontendAction {
public:
    virtual std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) {
        return llvm::make_unique<PrintFunctionsConsumer>();
    }
};
*/

class PrinterType : public MatchFinder::MatchCallback {
public :
    virtual void run(const MatchFinder::MatchResult &Result) {
        ASTContext* C = Result.Context;
        llvm::Module* M = new llvm::Module("module1", llvm::getGlobalContext());
        if (const TypedefDecl* decl = Result.Nodes.getNodeAs<clang::TypedefDecl>("typename")) {
            QualType Ty = decl->getUnderlyingType();

            llvm::errs() << "<" << Ty.getAsString()  << ">" ;

            M->setDataLayout(C->getTargetInfo().getDataLayoutString());
            CodeGen::CodeGenModule *CGM = new CodeGen::CodeGenModule(
                *C, 
                *(new HeaderSearchOptions()),
                *(new PreprocessorOptions()),
                *(new CodeGenOptions()), 
                *M,
                C->getDiagnostics());

            llvm::Type *rawTy =  CGM->getTypes().ConvertType(Ty);

            rawTy->dump();
            results.push_back(rawTy);
        }
    }

    std::vector<llvm::Type*> results;
};

class PrinterFunction:  public MatchFinder::MatchCallback {
public :
	virtual void run(const MatchFinder::MatchResult &Result) {
		if (const FunctionDecl* decl = Result.Nodes.getNodeAs<clang::FunctionDecl>("function")) {
                    QualType Ty = decl->getType();

                    llvm::errs() << "<" << Ty.getAsString()  << ">" ;
                    ASTContext* C = Result.Context;
                    llvm::Module* M = new llvm::Module("module1", llvm::getGlobalContext());

                    CodeGen::CodeGenModule *CGM = new CodeGen::CodeGenModule(
                        *C, 
                        *(new HeaderSearchOptions()),
                        *(new PreprocessorOptions()),
                        *(new CodeGenOptions()), 
                        *M,
                        C->getDiagnostics());

                    llvm::Type *rawTy =  CGM->getTypes().ConvertType(Ty);

                    //const clang::FunctionType* Ty = decl->getType()->getAs<clang::FunctionType>();
                    llvm::FunctionType* rawFuncTy = llvm::dyn_cast<llvm::FunctionType>(rawTy);
                    rawFuncTy->dump();
                    results.push_back(rawFuncTy);

                    /*
                    llvm::Function* fDeclaration = llvm::Function::Create(rawFuncTy, llvm::GlobalValue::ExternalLinkage, "xxxx", M);
                    fDeclaration->dump();
                    */
		}
	}

	std::vector<llvm::Type*> results;
};

//DEBT fix dependency on clasp/gcc version
vector<string> argv = {
 "-I/usr/include/libxml2"
        ,"-I/usr/local/include"
 ,"-I/usr/lib/llvm-3.6/lib/clang/3.6.0/include"
 ,"-I/usr/include"
};

TEST(ClangAPI, testExternalType)
{

    //,, "-I/usr/include/linux"
//    "-cc1", "-emit-pch", "-disable-free", "-disable-llvm-verifier",  "-mrelocation-model", "static",
//            "-mthread-model", "posix", "-mdisable-fp-elim", "-fmath-errno", "-masm-verbose",
//            "-mconstructor-aliases", "-munwind-tables", "-fuse-init-array", "-target-cpu", "x86-64", "-target-linker-version", "2.25",
    //"-dwarf-column-info", "-resource-dir", "/usr/lib/llvm-3.7/bin/../lib/clang/3.7.0",
    //"clang", "--",


    /*
    int argc= argv.size();

    llvm::cl::OptionCategory cat("aaaa");
    LLVMInitializeNativeTarget();
    CommonOptionsParser op(argc, &*(argv.begin()), cat);
    ClangTool tool(op.getCompilations(), op.getSourcePathList());
     */

    auto matcherType =
            typedefDecl(hasName("xmlNodePtr")).bind("typename");

    MatchFinder finder;
    PrinterType printer;
    finder.addMatcher(matcherType, &printer);

    std::string code = (boost::format("#include \"%1%\"") % ("libxml/tree.h")).str();
    //runToolOnCodeWithArgs(newFrontendActionFactory(&finder).get()->create(), code, argv);

    std::unique_ptr<ASTUnit> ast = buildASTFromCodeWithArgs(code, argv);
    ASTContext & context = ast->getASTContext();
    finder.matchAST(context);

    string signatureExpected = "%struct._xmlNode*";
    ASSERT_EQ(1, printer.results.size());

    llvm::Type* tyActual = printer.results.at(0);
    string strActual;
    llvm::raw_string_ostream ss(strActual);
    tyActual->print(ss);
    ASSERT_EQ(signatureExpected, ss.str());

    //int x = tool.run(newFrontendActionFactory(&finder).get());
}

TEST(ClangAPI, testExternalFunction){
	auto matcherType = functionDecl(hasName("arc4random")).bind("function");

	MatchFinder finder;
	PrinterFunction printer;
	finder.addMatcher(matcherType, &printer);

    std::string code = (boost::format("#include \"%1%\"") % ("bsd/stdlib.h")).str();
	std::unique_ptr<ASTUnit> ast = buildASTFromCodeWithArgs(code, argv);
	ASTContext & context = ast->getASTContext();
	finder.matchAST(context);

    string signatureExpected = "i32 ()";
    ASSERT_EQ(1, printer.results.size());

    llvm::Type* tyActual = printer.results.at(0);
    string strActual;
    llvm::raw_string_ostream ss(strActual);
    tyActual->print(ss);
    ASSERT_EQ(signatureExpected, ss.str());
}
