/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * modules.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on July 22, 2017, 5:13 PM
 */

/**
 * \file    modules.h
 * \brief   Modules support
 */

#include "modules.h"
#include "modules/Parser.h"
#include "analysis/aux.h"
#include <clingo/clingocontrol.hh>
#include <boost/format.hpp>
#include <regex>
#include <boost/filesystem.hpp>
#include <fstream>

namespace fs = boost::filesystem;

namespace xreate { namespace modules{
void
ModuleRecord::addModuleQuery(const Expression& query){
    __queries.push_back(query);
}

void
ModuleRecord::addControllerPath(const std::string& path){
    __controllers.push_back(path);
}

void
ModuleRecord::addDiscoveryPath(const std::string& path){
    __discoveries.push_back(path);
}

void
ModuleRecord::addProperty(const Expression& prop){
    __properties.push_back(prop);
}

void
ModulesSolver::loadControllers(const ModuleRecord& module){
    for (const std::string& pathController: module.__controllers){
        std::fstream fileContent(pathController);
        __program << fileContent.rdbuf();
    }
}

void
ModulesSolver::extractProperties(const ModuleRecord& module){
    unsigned int moduleId = __registry->getModuleHash(module.__path);
    const std::string atomProperty = "bind_module";
    boost::format formatProperty(atomProperty + "(%1%, %2%).");

    for (const Expression& property: module.__properties){
        std::list<std::string> reprProp = xreate::analysis::compile(property);
        assert(reprProp.size()== 1);

        __program  << (formatProperty % moduleId % reprProp.front())
                << std::endl;
    }
}

void
ModulesSolver::discoverModules(const ModuleRecord& moduleClient){
    std::regex extXreate("\\.xreate$", std::regex::basic);

    for(const std::string& path: moduleClient.__discoveries){
        for(fs::directory_entry e: fs::recursive_directory_iterator(path)) {
            if (fs::is_regular_file(e.status())){
                if (!std::regex_search(e.path().string(), extXreate)) continue;

                FILE* script = fopen(e.path().c_str(), "r");
                Scanner scanner(script);
                Parser parser(&scanner);
                parser.Parse();
                assert(!parser.errors->count && "Discovery errors");
                parser.module.__path = e.path().c_str();

                extractProperties(parser.module);
                fclose(script);
            }
        }
    }
}

void
ModulesSolver::extractRequirements(const ModuleRecord& module){
    const std::string atomQuery = "module_require";
    boost::format formatProperty(atomQuery + "(%1%, %2%).");
    unsigned int moduleId = __registry->getModuleHash(module.__path);

    for (const Expression& query: module.__queries){
        std::list<std::string> reprQuery = xreate::analysis::compile(query);
        assert(reprQuery.size()== 1);

        __program  << (formatProperty % moduleId % reprQuery.front())
                << std::endl;
    }
}
void

ModulesSolver::add(const std::string& base){
    __program << base;
}

void
ModulesSolver::init(const std::string& programBase, const ModuleRecord& module){
    add(programBase);
    extractRequirements(module);
    extractProperties(module);
    loadControllers(module);
    discoverModules(module);

    std::cout << __program.str() << std::endl;
}

std::list<std::string>
ModulesSolver::run(const ModuleRecord& module){
    const std::string atomDecision = "module_include";
    unsigned int moduleId = __registry->getModuleHash(module.__path);

    std::list<std::string> result;
    std::vector<char const *> args{"clingo", nullptr};
    DefaultGringoModule moduleDefault;
    Gringo::Scripts scriptsDefault(moduleDefault);
    ClingoLib ctl(scriptsDefault, 0, args.data(), {}, 0);

    ctl.add("base", {}, __program.str());
    ctl.ground({{"base", {}}}, nullptr);
    ctl.solve([&atomDecision, this, &result, moduleId](Gringo::Model const &model) {
        for (Gringo::Symbol atom : model.atoms(clingo_show_type_atoms)) {
            std::cout << atom << std::endl;

            if (std::strcmp(atom.name().c_str(), atomDecision.c_str())==0){
                auto rowDecision = ClaspLayer::parse<unsigned int, Gringo::Symbol>(atom);

                unsigned int moduleIdActual = std::get<0>(rowDecision);
                if (moduleIdActual == moduleId){
                    Gringo::Symbol moduleDecided = std::get<1>(rowDecision);
                    switch (moduleDecided.type()) {
                        case Gringo::SymbolType::Str:
                            result.push_back(moduleDecided.string().c_str());
                            break;

                        case Gringo::SymbolType::Num:
                            result.push_back(__registry->getModuleNameByHash(moduleDecided.num()));
                            break;

                        default: assert(false && "Inappropriate symbol type");
                    }
                }
            }
        }
        return true;
    }, {});

    return result;
}

const std::string&
ModulesRegistry::getModuleNameByHash(unsigned int hash){
    auto result = __registry.right.find(hash);
    assert(result != __registry.right.end());

    return result->second;
}

unsigned int
ModulesRegistry::getModuleHash(const std::string& moduleName){
    auto result = __registry.left.insert(Hash::left_value_type(moduleName, __registry.size()));

    return result.first->second;
}

}} //namespace xreate::modules
