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

#include "ExternLayer.h"

#include <cstdio>
#include <iostream>
#include "clang/Tooling/Tooling.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CodeGenOptions.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/TargetInfo.h"
#include <llvm/Support/DynamicLibrary.h>
#include <boost/format.hpp>
#include <boost/algorithm/string/join.hpp>

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


class FinderCallbackTypeDecl : public MatchFinder::MatchCallback {
public :
    QualType typeResult;

    virtual void run(const MatchFinder::MatchResult &Result) {
        if (const TypedefDecl* decl = Result.Nodes.getNodeAs<clang::TypedefDecl>("typename")) {
            typeResult = decl->getUnderlyingType();
        }
    }
};

class FinderCallbackFunction : public MatchFinder::MatchCallback {
public :
    QualType typeResult;

    virtual void run(const MatchFinder::MatchResult &Result) {
        if (const FunctionDecl* decl = Result.Nodes.getNodeAs<clang::FunctionDecl>("function")) {
            typeResult = decl->getType();
        }
    }
};

void
ExternData::addLibrary(Atom<Identifier_t>&& name, Atom<String_t>&& package)
{
    __dictLibraries.emplace(name.get(), package.get());
}

void
ExternData::addIncludeDecl(Expression&& e)
{
    assert(e.op == Operator::LIST_NAMED);

        //TODO  ?? implement Expression parsing(Array of Expr as vector<string>);
    for(size_t i=0, size=e.operands.size(); i<size; ++i)
    {
        std::string library = e.bindings.at(i);
        assert(__dictLibraries.count(library));
        std::string package = __dictLibraries.at(library);

        Expression listHeaders = e.operands.at(i);
        assert(listHeaders.op == Operator::LIST);

        std::vector<std::string> headers;
        std::transform(listHeaders.operands.begin(), listHeaders.operands.end(), std::inserter(headers, headers.end()),
                       [](const Expression& o){
                           assert(o.__state == Expression::STRING);
                           return o.getValueString();
        });

        entries.emplace_back(ExternEntry{package, std::move(headers)});
    }
}

void
ExternLayer::addExternalData(const std::vector<ExternEntry>& data){
    entries.insert(entries.end(), data.begin(), data.end());
}

ExternLayer::ExternLayer(LLVMLayer *llvm)
    : __datalayout(llvm->module), __llvm(llvm)
{}


std::vector<string>
ExternLayer::fetchPackageFlags(const ExternEntry& entry){
    std::vector<string> args;
    FILE* flags = popen((string("pkg-config --cflags ") + entry.package).c_str(), "r");
    size_t linesize=0;
    char* linebuf=0;
    ssize_t linelen=0;
    while ((linelen=getdelim(&linebuf, &linesize, ' ', flags))>0) {
        if (linebuf[0]=='\n') continue;
        if (linelen==1 && linebuf[0]==' ') continue;
        if (linebuf[linelen-1 ] == ' ')
                linebuf[linelen-1] = 0;

        llvm::outs() << '<' << linebuf << "> ";
        args.push_back(linebuf);

        free(linebuf);
        linebuf = 0;
    }
    pclose(flags);
    return (args);
}

std::vector<string>
ExternLayer::fetchPackageLibs(const ExternEntry& entry){
	std::vector<string> libs;

	FILE* flags = popen((string("pkg-config --libs ") + entry.package).c_str(), "r");
	size_t linesize=0;
	char* linebuf=0;
	ssize_t linelen=0;
	while ((linelen=getdelim(&linebuf, &linesize, ' ', flags))>0) {
		if (linebuf[0]=='\n') continue;
        if (linelen==1 && linebuf[0]==' ') continue;
        if (linebuf[linelen-1 ] == ' ')
                linebuf[linelen-1] = 0;
        if (linelen>=2 && linebuf[0] == '-' && linebuf[1] == 'l'){
            libs.push_back(linebuf + 2);
        } else {
            libs.push_back(linebuf);
        }

		llvm::outs() << '<' << linebuf << "> ";
		free(linebuf);
		linebuf = 0;
	}
	pclose(flags);
	return (libs);
}

void
ExternLayer::loadLibraries(vector<string>&& libs){
	string msgErr;
    for (const string& lib: libs) {
        const string& libName = string("lib")+lib+".so";
        if (!llvm::sys::DynamicLibrary::LoadLibraryPermanently(libName.c_str(), &msgErr)){
            llvm::errs()<<"\n"<<"Loading library "<<lib<<". " <<msgErr<<"\n";
    	}
    }
}

void
ExternLayer::init(const AST* root) {
    addExternalData(root->__externdata);

    	// TODO use default include path from 'clang -xc++ -E'
    list<string> code;
    std::vector<string> args{
        "-I/usr/include"
        ,"-I/usr/local/include"
        ,"-I/usr/lib/llvm-3.6/lib/clang/3.6.2/include"
//        ,"-I/usr/lib/gcc/x86_64-linux-gnu/4.9/include"
//        ,"-I/usr/include/x86_64-linux-gnu"
    };

    std::vector<string> libs;

    boost::format formatInclude("#include \"%1%\"");
    for(const ExternEntry& entry: entries)
    {
        llvm::outs()<<"[ExternC] Processing package: "<< entry.package << "\n";
        llvm::outs()<<"[ExternC]    args: ";

        vector<string>&& args2 = fetchPackageFlags(entry);
        args.insert(args.end(), args2.begin(), args2.end());
        for(const string arg: args2) {
        	llvm::outs()<< "<" << arg << "> ";
        }

        llvm::outs()<<"\n[ExternC]    libs: ";
        args2 = fetchPackageLibs(entry);
        for(const string arg: args2) {
        	llvm::outs()<< "<" << arg << "> ";
        }
        libs.insert(libs.end(), args2.begin(), args2.end());


        llvm::outs()<<"\n[ExternC]    headers: ";
        std::transform(entry.headers.begin(), entry.headers.end(), std::inserter(code, code.begin()),
            [&formatInclude](const string header ) {
                string line = boost::str(formatInclude % header);
                llvm::outs()<< "<" << line << "> ";

                return line;
        });

        llvm::outs() << '\n';
    }

    loadLibraries(move(libs));
    ast = buildASTFromCodeWithArgs(boost::algorithm::join(code, "\n"), args);
    __cgo.reset(new CodeGenOptions);
    __datalayout = llvm::DataLayout(ast->getASTContext().getTargetInfo().getTargetDescription());
    __cgm.reset(new CodeGen::CodeGenModule(ast->getASTContext(), *__cgo, *__llvm->module, __datalayout, ast->getASTContext().getDiagnostics()));
};



bool
ExternLayer::isPointer(const clang::QualType &t) {
    const clang::Type * tInfo = t.getTypePtr();
    assert(tInfo);

    return tInfo->isAnyPointerType();
}

llvm::Type*
ExternLayer::toLLVMType(const clang::QualType& t){
    return __cgm->getTypes().ConvertType(t);
}

std::vector<std::string>
ExternLayer::getStructFields(const clang::QualType& ty)
{
    clang::QualType t = ty;
    if (isPointer(ty)){
        const clang::PointerType* tPtr = ty->getAs<clang::PointerType>();
        t = tPtr->getPointeeType();
    }

    assert(t.getTypePtr()->isRecordType());

    const RecordType *record = t->getAsStructureType();
    assert(record);

    std::vector<std::string> result;
    //FieldDecl* field: record->getDecl()->fields()
    for (auto i=record->getDecl()->field_begin(); i!= record->getDecl()->field_end(); ++i){
        result.push_back(i->getName());
    }

    return result;
}

clang::QualType
ExternLayer::lookupType(const std::string& id){
    MatchFinder finder;
    FinderCallbackTypeDecl callbackTypeDecl;
    auto matcherTypeDecl = typedefDecl(hasName(id)).bind("typename");

    finder.addMatcher(matcherTypeDecl, &callbackTypeDecl);
    finder.matchAST(ast->getASTContext());

    assert(! callbackTypeDecl.typeResult.isNull());
    return callbackTypeDecl.typeResult;
}


llvm::Function*
ExternLayer::lookupFunction(const std::string& name){
	if (__functions.count(name)){
		return __functions.at(name);
	}

    MatchFinder finder;
    FinderCallbackFunction callback;
    auto matcher = functionDecl(hasName(name)).bind("function");

    finder.addMatcher(matcher, &callback);
    finder.matchAST(ast->getASTContext());

    if (callback.typeResult.isNull()){
    	cout <<"[External Layer] "  << "Unknown function: "<<name << endl;
    	assert(false && "Unknown external function");
    }
    const QualType& tyFuncQual = callback.typeResult;
    llvm::Type *tyRaw =  __cgm->getTypes().ConvertType(tyFuncQual);
    llvm::FunctionType* tyRawFunc = llvm::dyn_cast<llvm::FunctionType>(tyRaw);

    llvm::Function* function = llvm::Function::Create(tyRawFunc, llvm::GlobalValue::ExternalLinkage, name, __llvm->module);

    __functions.emplace(name, function);
    return function;
}
