//
// 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 "/opt/llvm-toolchain-3.6-3.6/clang/lib/CodeGen/CodeGenTypes.h"
#include "/opt/llvm-toolchain-3.6-3.6/clang/lib/CodeGen/CodeGenModule.h"
#include "clang/CodeGen/CodeGenABITypes.h"
#include "llvm/IR/LLVMContext.h"
#include "clang/Basic/TargetInfo.h"
#include <boost/format.hpp>



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()  << ">" ;

            CodeGenOptions *CGO = new CodeGenOptions;

            const llvm::DataLayout& TD  = llvm::DataLayout(C->getTargetInfo().getTargetDescription());
            CodeGen::CodeGenModule *CGM = new CodeGen::CodeGenModule(*C, *CGO, *M, TD, 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());
			CodeGenOptions *CGO = new CodeGenOptions;

			const llvm::DataLayout& TD  = llvm::DataLayout(C->getTargetInfo().getTargetDescription());
			CodeGen::CodeGenModule *CGM = new CodeGen::CodeGenModule(*C, *CGO, *M, TD, 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.0*";
    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());
}
