#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <unordered_map>
#include <vector>

static inline uint64_t splitmix64(uint64_t x) {
    x += 0x9e3779b97f4a7c15ULL;
    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
    x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
    return x ^ (x >> 31);
}

static inline void hash_combine(uint64_t& h, uint64_t x) {
    h ^= splitmix64(x + 0x9e3779b97f4a7c15ULL);
    h *= 1099511628211ULL;
}

static inline int lit_idx(int lit) {
    int v = std::abs(lit) - 1;
    return (v << 1) ^ (lit < 0);
}

struct Formula {
    int nvars = 0;
    int nclauses = 0;
    std::vector<int> offsets;
    std::vector<int> lits;
};

static bool parse_dimacs(const char* path, Formula& f) {
    FILE* in = std::fopen(path, "r");
    if (!in) {
        std::perror(path);
        return false;
    }
    char buf[1 << 20];
    std::vector<int> cur;
    while (std::fgets(buf, sizeof(buf), in)) {
        if (buf[0] == 'c' || buf[0] == '\n' || buf[0] == '\r') continue;
        if (buf[0] == 'p') {
            char a[16], b[16];
            if (std::sscanf(buf, "%15s %15s %d %d", a, b, &f.nvars, &f.nclauses) != 4) {
                std::fclose(in);
                return false;
            }
            f.offsets.reserve((size_t)f.nclauses + 1);
            f.lits.reserve((size_t)f.nclauses * 3);
            f.offsets.push_back(0);
            continue;
        }
        char* p = buf;
        while (*p) {
            char* end = nullptr;
            long x = std::strtol(p, &end, 10);
            if (end == p) break;
            p = end;
            if (x == 0) {
                std::sort(cur.begin(), cur.end());
                for (int lit : cur) f.lits.push_back(lit_idx(lit));
                f.offsets.push_back((int)f.lits.size());
                cur.clear();
            } else {
                cur.push_back((int)x);
            }
        }
    }
    std::fclose(in);
    return f.nvars > 0 && (int)f.offsets.size() == f.nclauses + 1;
}

static std::vector<uint64_t> literal_colors(const Formula& f, int rounds) {
    std::vector<uint64_t> color(2 * f.nvars, 0), next(2 * f.nvars, 0);
    std::vector<uint32_t> deg(2 * f.nvars);
    std::vector<uint64_t> clause_color(f.nclauses);
    std::vector<uint64_t> tmp;
    tmp.reserve(1024);
    std::vector<std::pair<int, uint64_t>> occ;
    occ.reserve(f.lits.size());

    for (int ci = 0; ci < f.nclauses; ++ci) {
        int begin = f.offsets[ci], end = f.offsets[ci + 1];
        uint64_t s = splitmix64((uint64_t)(end - begin) * 17 + 3);
        for (int p = begin; p < end; ++p) {
            int li = f.lits[p];
            color[li] += s;
            deg[li]++;
        }
    }
    for (int i = 0; i < 2 * f.nvars; ++i) {
        color[i] = splitmix64(color[i] ^ ((uint64_t)deg[i] << 32));
    }

    for (int r = 0; r < rounds; ++r) {
        for (int ci = 0; ci < f.nclauses; ++ci) {
            int begin = f.offsets[ci], end = f.offsets[ci + 1];
            tmp.clear();
            for (int p = begin; p < end; ++p) tmp.push_back(color[f.lits[p]]);
            std::sort(tmp.begin(), tmp.end());
            uint64_t h = 1469598103934665603ULL;
            hash_combine(h, (uint64_t)tmp.size() + 0x123456789ULL);
            for (uint64_t x : tmp) {
                hash_combine(h, x);
            }
            clause_color[ci] = h;
        }

        occ.clear();
        for (int ci = 0; ci < f.nclauses; ++ci) {
            uint64_t c = clause_color[ci];
            for (int p = f.offsets[ci]; p < f.offsets[ci + 1]; ++p) {
                occ.push_back({f.lits[p], c});
            }
        }
        std::sort(occ.begin(), occ.end());
        for (int i = 0; i < 2 * f.nvars; ++i) {
            uint64_t h = 1469598103934665603ULL;
            hash_combine(h, color[i]);
            hash_combine(h, color[i ^ 1]);
            next[i] = h;
        }
        for (size_t pos = 0; pos < occ.size(); ) {
            int li = occ[pos].first;
            uint64_t h = next[li];
            size_t start = pos;
            while (pos < occ.size() && occ[pos].first == li) {
                hash_combine(h, occ[pos].second);
                ++pos;
            }
            hash_combine(h, pos - start);
            next[li] = h;
        }
        color.swap(next);
    }
    return color;
}

static bool load_model(const char* path, int nvars, std::vector<unsigned char>& values) {
    std::ifstream in(path, std::ios::binary);
    if (!in) return false;
    uint32_t file_n = 0;
    uint64_t file_hash = 0;
    in.read(reinterpret_cast<char*>(&file_n), sizeof(file_n));
    in.read(reinterpret_cast<char*>(&file_hash), sizeof(file_hash));
    if (!in || (int)file_n != nvars) return false;
    std::vector<unsigned char> bits((file_n + 7) / 8);
    in.read(reinterpret_cast<char*>(bits.data()), bits.size());
    if (!in) return false;
    values.assign(nvars + 1, 0);
    for (int v = 1; v <= nvars; ++v) {
        values[v] = ((bits[(v - 1) >> 3] >> ((v - 1) & 7)) & 1U) ? 1 : 2;
    }
    return true;
}

static bool check_model(const Formula& f, const std::vector<unsigned char>& values) {
    for (int ci = 0; ci < f.nclauses; ++ci) {
        bool ok = false;
        for (int p = f.offsets[ci]; p < f.offsets[ci + 1]; ++p) {
            int li = f.lits[p];
            int v = (li >> 1) + 1;
            bool neg = li & 1;
            bool val = values[v] == 1;
            if (val != neg) {
                ok = true;
                break;
            }
        }
        if (!ok) {
            std::fprintf(stderr, "bad clause %d\n", ci);
            return false;
        }
    }
    return true;
}

int main(int argc, char** argv) {
    if (argc != 5 && argc != 6) {
        std::fprintf(stderr, "usage: wl_transfer canonical.cnf target.cnf canonical_model.bin rounds [partial_units.cnf]\n");
        return 2;
    }
    Formula a, b;
    if (!parse_dimacs(argv[1], a) || !parse_dimacs(argv[2], b)) return 2;
    if (a.nvars != b.nvars || a.nclauses != b.nclauses) {
        std::fprintf(stderr, "dimension mismatch\n");
        return 2;
    }
    int rounds = std::atoi(argv[4]);
    auto ca = literal_colors(a, rounds);
    auto cb = literal_colors(b, rounds);

    std::unordered_map<uint64_t, int> target;
    std::unordered_map<uint64_t, int> target_count;
    target.reserve(cb.size() * 2);
    target_count.reserve(cb.size() * 2);
    for (int i = 0; i < (int)cb.size(); ++i) {
        target[cb[i]] = i;
        target_count[cb[i]]++;
    }
    std::unordered_map<uint64_t, int> source_count;
    source_count.reserve(ca.size() * 2);
    for (uint64_t c : ca) source_count[c]++;

    std::vector<unsigned char> aval;
    if (!load_model(argv[3], a.nvars, aval)) {
        std::fprintf(stderr, "could not load model\n");
        return 2;
    }
    std::vector<unsigned char> bval(b.nvars + 1, 0);
    int mapped_vars = 0, conflicts = 0, unique_lits = 0;
    for (int v = 1; v <= a.nvars; ++v) {
        int li = 2 * (v - 1);
        uint64_t c = ca[li];
        if (source_count[c] == 1 && target_count[c] == 1) {
            int tj = target[c];
            int tv = (tj >> 1) + 1;
            bool tneg = tj & 1;
            bool val = aval[v] == 1;
            bool target_val = tneg ? !val : val;
            unsigned char enc = target_val ? 1 : 2;
            if (bval[tv] != 0 && bval[tv] != enc) conflicts++;
            bval[tv] = enc;
            mapped_vars++;
            unique_lits++;
        }
    }
    int assigned = 0;
    for (int v = 1; v <= b.nvars; ++v) assigned += bval[v] != 0;
    std::printf("mapped_vars=%d assigned=%d conflicts=%d unique_lits=%d\n", mapped_vars, assigned, conflicts, unique_lits);
    if (assigned == b.nvars && conflicts == 0 && check_model(b, bval)) {
        std::printf("model-ok\n");
        return 0;
    }

    std::unordered_map<uint64_t, unsigned char> class_value;
    class_value.reserve(ca.size() * 2);
    for (int li = 0; li < (int)ca.size(); ++li) {
        int v = (li >> 1) + 1;
        bool neg = li & 1;
        bool var_true = aval[v] == 1;
        unsigned char lit_value = (var_true != neg) ? 1 : 2;
        auto it = class_value.find(ca[li]);
        if (it == class_value.end()) class_value.emplace(ca[li], lit_value);
        else if (it->second != lit_value) it->second = 3;
    }
    std::fill(bval.begin(), bval.end(), 0);
    conflicts = 0;
    int uniform_lits = 0;
    for (int li = 0; li < (int)cb.size(); ++li) {
        auto it = class_value.find(cb[li]);
        if (it == class_value.end() || it->second > 2) continue;
        ++uniform_lits;
        int v = (li >> 1) + 1;
        bool neg = li & 1;
        bool lit_true = it->second == 1;
        bool var_true = neg ? !lit_true : lit_true;
        unsigned char enc = var_true ? 1 : 2;
        if (bval[v] != 0 && bval[v] != enc) conflicts++;
        bval[v] = enc;
    }
    assigned = 0;
    for (int v = 1; v <= b.nvars; ++v) assigned += bval[v] != 0;
    std::printf("uniform_lits=%d assigned=%d conflicts=%d\n", uniform_lits, assigned, conflicts);
    if (argc == 6) {
        std::ofstream out(argv[5]);
        for (int v = 1; v <= b.nvars; ++v) {
            if (bval[v] == 1) out << v << " 0\n";
            else if (bval[v] == 2) out << -v << " 0\n";
        }
    }
    if (assigned == b.nvars && conflicts == 0 && check_model(b, bval)) {
        std::printf("model-ok\n");
        return 0;
    }
    return 1;
}
