#!/usr/bin/env python3
import lzma
import os
import random
import sys


def open_dimacs(path):
    return lzma.open(path, "rt", encoding="ascii", errors="ignore") if path.endswith(".xz") else open(path)


def order_var(n, i, j):
    if i > j:
        i, j = j, i
    return 2 + (i - 1) * n - ((i - 1) * i) // 2 + (j - i - 1)


def arc_var(n, i, j):
    order_end = 1 + n * (n - 1) // 2
    return order_end + 1 + (i - 1) * n + (j - 1)


def decode_arc(n, v):
    order_end = 1 + n * (n - 1) // 2
    off = v - (order_end + 1)
    return off // n + 1, off % n + 1


def decode_order(n, v):
    x = v - 2
    for i in range(1, n):
        row = n - i
        if x < row:
            return i, i + 1 + x
        x -= row
    raise ValueError(v)


def parse_non_sc(path):
    with open_dimacs(path) as fh:
        header = None
        first = None
        clause_no = 0
        edges = set()
        precede = set()
        in_edge_block = False
        for line in fh:
            if line.startswith("c"):
                continue
            if line.startswith("p"):
                ps = line.split()
                header = (int(ps[2]), int(ps[3]))
                continue
            lits = [int(x) for x in line.split() if x != "0"]
            if not lits:
                continue
            clause_no += 1
            if first is None:
                first = lits
                if len(first) != 3 or first[0] != -2 or first[2] != 3 or first[1] >= 0:
                    raise ValueError("not clean non-sc prefix")
                n = -first[1] - 1
                order_end = 1 + n * (n - 1) // 2
                arc_start = order_end + 1
                arc_end = order_end + n * n
                trans = n * (n - 1) * (n - 2)
            if clause_no <= trans:
                continue
            if len(lits) == 2 and lits[0] > 0 and lits[1] > 0 and arc_start <= lits[0] <= arc_end and arc_start <= lits[1] <= arc_end:
                in_edge_block = True
                a = decode_arc(n, lits[0])
                b = decode_arc(n, lits[1])
                if a[0] == b[1] and a[1] == b[0] and a[0] != a[1]:
                    edges.add(tuple(sorted(a)))
            elif in_edge_block:
                # Keep scanning after the edge block only for unit order constraints.
                pass
            if len(lits) == 1 and first is not None and 2 <= abs(lits[0]) <= order_end:
                i, j = decode_order(n, abs(lits[0]))
                if lits[0] > 0:
                    precede.add((i, j))
                else:
                    precede.add((j, i))
        return header, n, edges, precede


def greedy_order(n, edges, mode="fill", seed=0, noise=0, precede=()):
    rng = random.Random(seed)
    adj = [set() for _ in range(n)]
    for a, b in edges:
        a -= 1; b -= 1
        adj[a].add(b); adj[b].add(a)
    remaining = set(range(n))
    succ = [set() for _ in range(n)]
    pred = [set() for _ in range(n)]
    for a, b in precede:
        a -= 1; b -= 1
        succ[a].add(b); pred[b].add(a)
    order = []
    width = 0
    while remaining:
        best = None
        bestkey = None
        available = [v for v in remaining if not (pred[v] & remaining)]
        if not available:
            return 10**9, []
        for v in available:
            neigh = [u for u in adj[v] if u in remaining]
            deg = len(neigh)
            fill = 0
            for i in range(deg):
                ai = adj[neigh[i]]
                for j in range(i + 1, deg):
                    if neigh[j] not in ai:
                        fill += 1
            if mode == "degree":
                key = (deg, fill, rng.random() * noise)
            elif mode == "fill-degree":
                key = (fill, deg, rng.random() * noise)
            elif mode == "deficiency":
                key = (fill - deg, fill, deg, rng.random() * noise)
            elif mode == "weighted":
                key = (fill * 4 + deg + rng.random() * noise, fill, deg)
            elif mode == "weighted2":
                key = (fill * 2 + deg + rng.random() * noise, fill, deg)
            else:
                key = (fill, deg, rng.random() * noise)
            if bestkey is None or key < bestkey:
                bestkey = key
                best = v
                best_neigh = neigh
        width = max(width, len(best_neigh))
        for i in range(len(best_neigh)):
            u = best_neigh[i]
            for j in range(i + 1, len(best_neigh)):
                w = best_neigh[j]
                adj[u].add(w); adj[w].add(u)
        remaining.remove(best)
        order.append(best + 1)
    return width, order


def main():
    reps_env = int(os.environ.get("REPS", "8"))
    for path in sys.argv[1:]:
        try:
            header, n, edges, precede = parse_non_sc(path)
        except Exception as e:
            print(os.path.basename(path), "unsupported", e)
            continue
        best = (10**9, None, None)
        modes = ["fill", "degree", "fill-degree", "deficiency", "weighted", "weighted2"]
        for mode in modes:
            reps = 1 if mode == "degree" else reps_env
            for s in range(reps):
                w, order = greedy_order(n, edges, mode, s, 1 if s else 0, precede)
                if w < best[0]:
                    best = (w, mode, order)
        print(f"{os.path.basename(path)} n={n} m={len(edges)} prec={len(precede)} vars={header[0]} clauses={header[1]} greedy_width={best[0]} mode={best[1]}")


if __name__ == "__main__":
    main()
