import { FnStmtKind } from "../ast.ts";
import {
    Block,
    Fn,
    Local,
    LocalId,
    Mir,
    replaceBlockSrcs,
    RValue,
} from "./mir.ts";

export function eliminateTransientLocals(mir: Mir) {
    for (const fn of mir.fns) {
        const otherLocals = fn.locals
            .slice(1 + (fn.stmt.kind as FnStmtKind).params.length)
            .map((local) => local.id);

        for (const block of fn.blocks) {
            new EliminateTransientLocalsBlockPass(block, otherLocals)
                .pass();
        }
    }
}

type Candidate = {
    dst: LocalId;
    src: LocalId;
};

class EliminateTransientLocalsBlockPass {
    private candidates: Candidate[] = [];

    public constructor(
        private block: Block,
        private readonly locals: LocalId[],
    ) {
    }

    public pass() {
        this.findCandidatesInBlock(this.block);

        this.candidates = this.candidates
            .filter((cand) => this.locals.includes(cand.dst));

        this.eliminateCandidatesInBlock(this.block);
    }

    private eliminateCandidatesInBlock(block: Block) {
        replaceBlockSrcs(block, (src) => this.replaceMaybe(src));
    }

    private replaceMaybe(src: RValue): RValue {
        if (src.type !== "local") {
            return src;
        }
        const candidate = this.candidates
            .find((cand) => cand.dst === src.id)?.src;
        return candidate !== undefined ? { type: "local", id: candidate } : src;
    }

    private findCandidatesInBlock(block: Block) {
        for (const op of block.ops) {
            const ok = op.kind;
            switch (ok.type) {
                case "error":
                    break;
                case "assign":
                    this.markDst(ok.dst, ok.src);
                    break;
                case "assign_error":
                case "assign_null":
                case "assign_bool":
                case "assign_int":
                case "assign_string":
                case "assign_fn":
                case "field":
                case "assign_field":
                case "index":
                case "assign_index":
                case "call_val":
                case "binary":
                    break;
                default:
                    throw new Error();
            }
        }
        const tk = block.ter.kind;
        switch (tk.type) {
            case "error":
                break;
            case "return":
                break;
            case "jump":
                break;
            case "if":
                break;
        }
    }

    private markDst(dst: LocalId, src: RValue) {
        if (src.type !== "local") {
            return;
        }

        this.candidates = this.candidates
            .filter((cand) => cand.dst !== dst);
        this.candidates = this.candidates
            .filter((cand) => cand.src !== dst);
        this.candidates.push({ dst, src: src.id });
    }
}
