/*
 *   rsf2cnf - converts rsf files to dimacs cnf files
 *
 * Copyright (C) 2012 Ralf Hackner <rh@ralf-hackner.de>
 * Copyright (C) 2012-2013 Reinhard Tartler <tartler@informatik.uni-erlangen.de>
 * Copyright (C) 2013-2014 Stefan Hengelein <stefan.hengelein@fau.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PicosatCNF.h"
#include "CNFBuilder.h"
#include "exceptions/IOException.h"
#include "RsfReader.h"
#include "KconfigWhitelist.h"
#include "Logging.h"
#include "bool.h"

#include <boost/regex.hpp>

using namespace kconfig;


static void usage(void){
    std::cerr << "rsf2cnf [-v] [-q] -m <model> [-W <file>] [-B <file>] [-r <rsf>] [-c <cnf>]" << std::endl;
    std::cerr << "  -v           increase verbosity" << std::endl;
    std::cerr << "  -q           decrease verbosity" << std::endl;
    std::cerr << "  -m <model>   file with inferences from golem, or a version 1.0 model file generated by rsf2model" << std::endl;
    std::cerr << "  -r <rsf>     (optional) original *.rsf file generated by dumpconf" << std::endl;
    std::cerr << "  -c <cnf>     (optional) merges constraints from given .cnf file" << std::endl;
    std::cerr << "  -W <file>    (optional) file with a whitelist of options that are always enabled" << std::endl;
    std::cerr << "  -B <file>    (optional) file with a blacklist of options that are always disabled" << std::endl;
    exit(1);
}

static void addTypeInfo(kconfig::PicosatCNF &cnf, const std::string &rsf_file) {
    // add all CONFIG_* items
    for (const auto &entry : ItemRsfReader(rsf_file)) {  // pair<string, string>
        const std::string symbolname = entry.first;
        const std::string nameOfType = entry.second;

        if (nameOfType == "boolean") {
            cnf.setSymbolType(symbolname, K_S_BOOLEAN);
        } else if (nameOfType == "tristate") {
            cnf.setSymbolType(symbolname, K_S_TRISTATE);
        } else if (nameOfType == "integer") {
            cnf.setSymbolType(symbolname, K_S_INT);
        } else if (nameOfType == "hex") {
            cnf.setSymbolType(symbolname, K_S_HEX);
        } else if (nameOfType == "string") {
            cnf.setSymbolType(symbolname, K_S_STRING);
        } else {
            cnf.setSymbolType(symbolname, K_S_OTHER);
        }
    }
}

static void addClauses(kconfig::CNFBuilder &builder, RsfReader &model) {
    boost::regex isconfig = boost::regex("^(CONFIG|FILE)_[^ ]+$");
    // add all CONFIG_* items
    for (const auto &entry : model) {  // pair<string, string>
        if (boost::regex_match(entry.first, isconfig)) {
            std::string clause = entry.first;
            builder.addVar(clause);

            if (!entry.second.empty()) {
                // CONFIG_FOO depends on EXPR
                clause += " -> (" + entry.second + ")";
                BoolExp *exp = BoolExp::parseString(clause);
                if (exp) {
                    builder.pushClause(exp);
                    delete exp;
                } else {
                    Logging::error("failed to parse '", clause, "'");
                }
            } else {
                // CONFIG_FOO depnends on Y
                // can be ignored
                clause += " -> 1";
            }
        }
    }
}

static void addAlwaysOnOff(kconfig::CNFBuilder &builder, RsfReader &model) {
    const std::string magic_on("ALWAYS_ON");
    const std::string magic_off("ALWAYS_OFF");

    for (const std::string &str : KconfigWhitelist::getWhitelist())
        model.addMetaValue(magic_on, str);

    for (const std::string &str : KconfigWhitelist::getBlacklist())
        model.addMetaValue(magic_off, str);

    if (model.getMetaValue(magic_on)) {
        for (const std::string &clause : *model.getMetaValue(magic_on)) {
            BoolExp *exp = BoolExp::parseString(clause);
            builder.pushClause(exp);
            delete exp;
            builder.cnf->addMetaValue(magic_on, clause);
        }
    }
    if (model.getMetaValue(magic_off)) {
        for (const std::string &str : *model.getMetaValue(magic_off)) {
            std::string clause = "! " + str;
            BoolExp *exp = BoolExp::parseString(clause);
            builder.pushClause(exp);
            delete exp;
            builder.cnf->addMetaValue(magic_off, str);
        }
    }
}

int main(int argc, char **argv) {
    int opt;
    std::string model_file;
    std::string rsf_file;
    std::string cnf_file;

    int loglevel = Logging::getLogLevel();

    while ((opt = getopt(argc, argv, "m:r:c:W:B:vh")) != -1) {
        switch (opt) {
            int n;
        case 'm':
            model_file = optarg;
            break;
        case 'r':
            rsf_file = optarg;
            break;
        case 'c':
            cnf_file = optarg;
            break;
        case 'q':
            loglevel = loglevel + 10;
            Logging::setLogLevel(loglevel);
            break;
        case 'v':
            loglevel = loglevel - 10;
            if (loglevel < 0)
                loglevel = Logging::LOG_EVERYTHING;
            Logging::setLogLevel(loglevel);
            break;
        case 'W':
            n = KconfigWhitelist::getWhitelist().loadWhitelist(optarg);
            if (n >= 0) {
                Logging::info("loaded ", n, " items to whitelist");
            } else {
                Logging::error("couldn't load whitelist");
                exit(-1);
            }
            break;
        case 'B':
            n = KconfigWhitelist::getBlacklist().loadWhitelist(optarg);
            if (n >= 0) {
                Logging::info("loaded ", n, " items to blacklist");
            } else {
                Logging::error("couldn't load blacklist");
                exit(-1);
            }
            break;
        case 'h':
            usage();
        default:
            break;
        }
    }
    if (model_file == "")
        usage();

    PicosatCNF cnf;
    if (cnf_file != "")
        cnf.readFromFile(cnf_file);

    kconfig::CNFBuilder builder(&cnf);

    RsfReader model(model_file);
    addClauses(builder, model);
    addAlwaysOnOff(builder, model);

    if (rsf_file != "")
        addTypeInfo(cnf, rsf_file);

    std::string magic_inc("CONFIGURATION_SPACE_INCOMPLETE");
    if (model.getMetaValue(magic_inc))
        cnf.addMetaValue(magic_inc, "True");
    cnf.toStream(std::cout);
}
