/*
 *   boolean framework for undertaker and satyr
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "PicosatCNF.h"
#include "exceptions/IOException.h"
#include "Logging.h"

#include <fstream>
#include <algorithm>

namespace Picosat {
// include picosat header as C
    extern "C" {
        #include "picosat.h"
    }
} // namespace Picosat

using namespace kconfig;


static bool picosatIsInitalized = false;
static PicosatCNF *currentContext = nullptr;

PicosatCNF::PicosatCNF(Picosat::SATMode defaultPhase) : defaultPhase(defaultPhase) {}

// copy delegate constructor with initializing _picosat and setting default_phase
PicosatCNF::PicosatCNF(const PicosatCNF &cnf, Picosat::SATMode defaultPhase) : PicosatCNF(cnf) {
    this->defaultPhase = defaultPhase;
}

PicosatCNF::~PicosatCNF() {
    if (this == currentContext)
        currentContext = nullptr;
}

void PicosatCNF::readFromFile(const std::string &filename) {
    std::ifstream i(filename);
    if (!i.good()) {
        throw IOException("Could not open CNF-File");
    }
    readFromStream(i);
}

void PicosatCNF::readFromStream(std::istream &i) {
    std::string tmp;
    while (i >> tmp) {
        if (tmp == "c") {
            i >> tmp;
            if (tmp == "var") {
                // beginning of this line matches: ("^c var (.+) (\\d+)$")
                std::string varname;
                int cnfnumber;
                i >> varname >> cnfnumber;
                this->setCNFVar(varname, cnfnumber);
            } else if (tmp == "sym") {
                // beginning of this line matches: ("^c sym (.+) (\\d)$")
                std::string varname;
                int typeId;
                i >> varname >> typeId;
                this->setSymbolType(varname, (kconfig_symbol_type) typeId);
            } else if (tmp == "meta_value") {
                // beginning of this line matches: ("^c meta_value ([^\\s]+)\\s+(.+)$")
                std::string metavalue, item;
                i >> metavalue;
                while (i.peek() != '\n' && i >> item)
                    this->addMetaValue(metavalue, item);
            } else {
                // beginning of this line matches: ("^c ") and will be ignored
                std::getline(i, tmp);
            }
        } else if (tmp == "p") {
            // beginning of this line matches: ("^p cnf (\\d+) (\\d+)$")
            // Which is the CNF dimension descriptor line.
            // After this, only lines in DIMACs CNF Format remain
            i >> tmp;
            if (tmp != "cnf") {
                Logging::error("Invalid DIMACs CNF dimension descriptor.");
                throw IOException("parse error while reading CNF file");
            }
            i >> varcount >> clausecount;
            // minimize reallocation of the clauses vector, each clause has 3-4 variables
            clauses.reserve(4*clausecount);
            int val;
            while(i >> val)
                clauses.emplace_back(val);
        } else {
            Logging::error("Line not starting with c or p.");
            throw IOException("parse error while reading CNF file");
        }
    }
}

void PicosatCNF::toFile(const std::string &filename) const {
    std::ofstream out(filename);
    if (!out.good()) {
        Logging::error("Couldn't write to ", filename);
        return;
    }
    toStream(out);
}

// XXX do not modify the output format without adjusting: readFromStream
void PicosatCNF::toStream(std::ostream &out) const {
    out << "c File Format Version: 2.0" << std::endl;
    out << "c Generated by satyr" << std::endl;
    out << "c Type info:" << std::endl;
    out << "c c sym <symbolname> <typeid>" << std::endl;
    out << "c with <typeid> being an integer out of:"  << std::endl;
    out << "c enum {S_BOOLEAN=1, S_TRISTATE=2, S_INT=3, S_HEX=4, S_STRING=5, S_OTHER=6}"
        << std::endl;
    out << "c variable names:" << std::endl;
    out << "c c var <variablename> <cnfvar>" << std::endl;

    for (const auto &entry : meta_information) {  // pair<string, deque<string>>
        std::stringstream sj;

        sj << "c meta_value " << entry.first;
        for (const std::string &str : entry.second)
            sj << " " << str;

        out << sj.str() << std::endl;
    }
    for (const auto &entry : this->symboltypes) {  // pair<string, kconfig_symbol_type>
        const std::string &sym = entry.first;
        int type = entry.second;
        out << "c sym " << sym << " " << type << std::endl;
    }
    for (const auto &entry : this->cnfvars) {  // pair<string, int>
        const std::string &sym = entry.first;
        int var = entry.second;
        out << "c var " << sym << " " << var << std::endl;
    }
    out << "p cnf " << varcount << " " << this->clausecount << std::endl;

    for (const int &clause : clauses) {
        char sep = (clause == 0) ? '\n' : ' ';
        out << clause << sep;
    }
}

// this method transfers the the state from other to 'this'
void PicosatCNF::incrementWith(const PicosatCNF &other) {
    for (const auto &entry : other.getSymbolTypes())  // pair<string, kconfig_symbol_type>
        setSymbolType(entry.first, entry.second);

    for (const auto &entry : other.getMetaInformation())  // pair<string, deque<string>>
        for (const std::string &item : entry.second)
            addMetaValue(entry.first, item);

    // add all CNFVars which aren't already contained in this and adjust variable-ids of those
    // which are already contained in this to avoid conflicts
    // 'inferenced' contains (positive and negative) variable-ids which have to be changed when the
    // clauses are transfered to 'this'
    std::map<int, int> inferenced;
    // if 'other' has fewer variables than the current cnf-object, we have to take the maximum
    int counter = std::max(other.getVarCount(), varcount);
    const int oldvarcount = varcount;
    for (const auto &entry : other.getSymbolMap()) {  // pair<string, int>
        int cnfvar = getCNFVar(entry.first);
        if (cnfvar) {
            // if 'this' already has the symbol 'entry.first' we need to replace the variable-id
            // from 'other' with the id from 'this'
            inferenced.emplace(entry.second, cnfvar);
            inferenced.emplace(-entry.second, -cnfvar);
        } else if (entry.second <= oldvarcount) {
            // if the variable from entry wasn't already mentioned but the id is smaller than
            // varcount, we have to give it a new id to avoid conflicts
            setCNFVar_fast(entry.first, ++counter);
            inferenced.emplace(entry.second, counter);
            inferenced.emplace(-entry.second, -counter);
        } else {
            setCNFVar_fast(entry.first, entry.second);
        }
    }
    // there are variables in the model which don't have symbols but to avoid conflicts, they need
    // a new variable
    for (int i = 1; i <= oldvarcount; i++)
        if (inferenced.find(i) == inferenced.end()) {
            inferenced[i] = ++counter;
            inferenced[-i] = -counter;
        }
#ifdef DEBUG
    for (const auto &entry : inferenced) {
//        std::cout << entry.first << " " << entry.second << std::endl;
        if (other.getSymbolName(abs(entry.first)) != getSymbolName(abs(entry.second))) {
            std::cout << "ERROR " << other.getSymbolName(entry.first) << " " << entry.first << " "
                      << getSymbolName(entry.second) << " " << entry.second << std::endl;
            std::exit(1);
        }
    }
#endif
    varcount = counter;
    // add all clauses from 'other' to this
    clauses.reserve(4 * (clausecount + other.getClauseCount()));
    for (const int &i : other.getClauses()) {
        if (i == 0) {
            pushClause();
            continue;
        }
        if (inferenced.find(i) != inferenced.end())  // abs(i) is found
            clauses.emplace_back(inferenced[i]);
        else
            clauses.emplace_back(i);
    }
}

kconfig_symbol_type PicosatCNF::getSymbolType(const std::string &name) const {
    const auto &it = this->symboltypes.find(name); // pair<string, kconfig_symbol_type>
    return (it == this->symboltypes.end()) ? K_S_UNKNOWN : it->second;
}

void PicosatCNF::setSymbolType(const std::string &sym, kconfig_symbol_type type) {
    std::string config_sym = "CONFIG_" + sym;
    this->associatedSymbols[config_sym] = sym;

    if (type == K_S_TRISTATE) {
        std::string config_sym_mod = "CONFIG_" + sym + "_MODULE";
        this->associatedSymbols[config_sym_mod] = sym;
    }
    this->symboltypes[sym] = type;
}

int PicosatCNF::getCNFVar(const std::string &var) const {
    const auto &it = this->cnfvars.find(var); // pair<string, int>
    return (it == this->cnfvars.end()) ? 0 : it->second;
}

void PicosatCNF::setCNFVar(const std::string &var, int CNFVar) {
    if (abs(CNFVar) > this->varcount)
        this->varcount = abs(CNFVar);
    setCNFVar_fast(var, CNFVar);
}

void PicosatCNF::setCNFVar_fast(const std::string &var, int CNFVar) {
    this->cnfvars[var] = CNFVar;
    this->boolvars[CNFVar] = var;
}

const std::string PicosatCNF::getSymbolName(int CNFVar) const {
    const auto &it = this->boolvars.find(CNFVar);
    return it == this->boolvars.end() ? "" : it->second;
}

void PicosatCNF::pushVar(int v) {
    if (abs(v) > this->varcount)
        this->varcount = abs(v);
    if (v == 0)
        this->clausecount++;
    clauses.emplace_back(v);
}

void PicosatCNF::pushVar(std::string &v, bool val) {
    int sign = val ? 1 : -1;
    int cnfvar = this->getCNFVar(v);
    this->pushVar(sign * cnfvar);
}

void PicosatCNF::pushClause() {
    this->clausecount++;
    clauses.emplace_back(0);
}

void PicosatCNF::pushClause(int *c) {
    while (*c) {
        this->pushVar(*c);
        c++;
    }
    this->pushClause();
}

void PicosatCNF::pushAssumption(int v) {
    assumptions.emplace_back(v);
}

void PicosatCNF::pushAssumption(const std::string &v, bool val) {
    int cnfvar = this->getCNFVar(v);

    if (cnfvar == 0) {
        Logging::error("Picosat: ignoring variable ", v, "as it has not been registered yet!");
        return;
    }
    if (val)
        this->pushAssumption(cnfvar);
    else
        this->pushAssumption(-cnfvar);
}

bool PicosatCNF::checkSatisfiable() {
    // make sure the current data is stored within picosat
    if (this != currentContext){
        // if not, reset the context....
        if (picosatIsInitalized)
            Picosat::picosat_reset();
        Picosat::picosat_init();
        picosatIsInitalized = true;
        // and load the current context
        currentContext = this;
        Picosat::picosat_set_global_default_phase(defaultPhase);
    }
    if (pushed_clauses_index < clauses.size()) {
        // tell picosat how many different variables it will receive
        Picosat::picosat_adjust(varcount);

        for (unsigned int i = pushed_clauses_index, e = clauses.size(); i < e; ++i)
            Picosat::picosat_add(clauses[i]);
        pushed_clauses_index = clauses.size();
    }
    for (const int &assumption : assumptions)
        Picosat::picosat_assume(assumption);

    assumptions.clear();
    return Picosat::picosat_sat(-1) == PICOSAT_SATISFIABLE;
}

void PicosatCNF::pushAssumptions(std::map<std::string, bool> &a) {
    for (const auto &entry : a) {  // pair<string, bool>
        std::string symbol = entry.first;
        bool value = entry.second;
        this->pushAssumption(symbol, value);
    }
}

bool PicosatCNF::deref(int s) const {
    return Picosat::picosat_deref(s) == 1;
}

bool PicosatCNF::deref(const std::string &s) const {
    int cnfvar = this->getCNFVar(s);
    return this->deref(cnfvar);
}

bool PicosatCNF::deref(const char *c) const {
    int cnfvar = this->getCNFVar(c);
    return this->deref(cnfvar);
}

const std::string *PicosatCNF::getAssociatedSymbol(const std::string &var) const {
    const auto &it = this->associatedSymbols.find(var); // pair<string, string>
    return (it == this->associatedSymbols.end()) ? nullptr : &(it->second);
}

const int *PicosatCNF::failedAssumptions() const {
    return Picosat::picosat_failed_assumptions();
}

void PicosatCNF::addMetaValue(const std::string &key, const std::string &value) {
    std::deque<std::string> &values = meta_information[key];
    if (std::find(values.begin(), values.end(), value) == values.end())
        // value wasn't found within values, add it
        values.push_back(value);
}

const std::deque<std::string> *PicosatCNF::getMetaValue(const std::string &key) const {
    const auto &i = meta_information.find(key); // pair<string, deque<string>>
    if (i == meta_information.end()) // key not found
        return nullptr;
    return &(i->second);
}

int PicosatCNF::newVar() {
    return ++varcount;
}
