import { AstCreator, Mod, Stmt } from "./ast.ts";
import { Checker } from "./checker.ts";
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Monomorphizer } from "./mono.ts";
import { FnNamesMap, Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
import { Pos } from "./token.ts";
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
import { mirOpCount, printMir } from "./middle/mir.ts";

import * as path from "jsr:@std/path";
import { lowerAst } from "./middle/lower_ast.ts";
import { eliminateUnusedLocals } from "./middle/elim_unused_local.ts";
import {
    eliminateOnlyChildsBlocks,
    eliminateUnreachableBlocks,
} from "./middle/elim_blocks.ts";
import { checkBorrows } from "./middle/borrow_checker.ts";
import { makeMoveCopyExplicit } from "./middle/explicit_move_copy.ts";

export type CompileResult = {
    program: number[];
    fnNames: FnNamesMap;
};

export class Compiler {
    private astCreator = new AstCreator();
    private reporter;

    public constructor(private startFilePath: string) {
        this.reporter = new Reporter(this.startFilePath);
    }

    public async compile(): Promise<CompileResult> {
        const { ast } = new ModTree(
            this.startFilePath,
            this.astCreator,
            this.reporter,
        ).resolve();

        new SpecialLoopDesugarer(this.astCreator).desugar(ast);
        new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
        new StructLiteralDesugarer(this.astCreator).desugar(ast);

        new Resolver(this.reporter).resolve(ast);

        new CompoundAssignDesugarer(this.astCreator).desugar(ast);

        new Checker(this.reporter).check(ast);

        //const mir = lowerAst(ast);
        //
        //console.log("Before optimizations:");
        //printMir(mir);

        //const mirHistory = [mirOpCount(mir)];
        //for (let i = 0; i < 1; ++i) {
        //    eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1);
        //    eliminateOnlyChildsBlocks(mir);
        //    eliminateUnreachableBlocks(mir);
        //    eliminateTransientVals(mir);
        //
        //    const opCount = mirOpCount(mir);
        //    const histOccurence = mirHistory
        //        .filter((v) => v === opCount).length;
        //    if (histOccurence >= 2) {
        //        break;
        //    }
        //    mirHistory.push(opCount);
        //}
        //
        //console.log("After optimizations:");
        //printMir(mir);

        if (this.reporter.errorOccured()) {
            console.error("Errors occurred, stopping compilation.");
            Deno.exit(1);
        }

        const mir = lowerAst(ast);

        makeMoveCopyExplicit(mir);
        checkBorrows(mir, this.reporter);

        printMir(mir);

        //const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
        //
        //const lastPos = await lastPosInTextFile(this.startFilePath);
        //
        //const lowerer = new Lowerer(monoFns, callMap, lastPos);
        //const { program, fnNames } = lowerer.lower();
        ////lowerer.printProgram();
        //
        //return { program, fnNames };
        return { program: [], fnNames: {} };
    }
}

export class ModTree implements AstVisitor<[string]> {
    constructor(
        private entryFilePath: string,
        private astCreator: AstCreator,
        private reporter: Reporter,
    ) {}

    public resolve(): Mod {
        const entryAst = this.parseFile(this.entryFilePath);

        visitStmts(entryAst, this, this.entryFilePath);

        return { filePath: this.entryFilePath, ast: entryAst };
    }

    private parseFile(filePath: string): Stmt[] {
        const text = Deno.readTextFileSync(filePath);

        const lexer = new Lexer(text, this.reporter);

        const parser = new Parser(lexer, this.astCreator, this.reporter);
        const ast = parser.parse();

        return ast;
    }

    visitModBlockStmt(stmt: Stmt, filePath: string): VisitRes {
        if (stmt.kind.type !== "mod_block") {
            throw new Error();
        }
        const { ident, stmts: ast } = stmt.kind;
        stmt.kind = {
            type: "mod",
            ident,
            mod: { filePath, ast },
        };
        visitStmts(ast, this, filePath);
        return "stop";
    }

    visitModFileStmt(stmt: Stmt, filePath: string): VisitRes {
        if (stmt.kind.type !== "mod_file") {
            throw new Error();
        }
        const { ident, filePath: modFilePath } = stmt.kind;
        const ast = this.parseFile(
            ident === "std"
                ? path.join(
                    path.dirname(path.fromFileUrl(Deno.mainModule)),
                    "../std/lib.slg",
                )
                : path.join(path.dirname(filePath), modFilePath),
        );
        stmt.kind = { type: "mod", ident, mod: { filePath, ast } };
        visitStmts(ast, this, filePath);
        return "stop";
    }
}

async function lastPosInTextFile(filePath: string): Promise<Pos> {
    const text = await Deno.readTextFile(filePath);

    let index = 0;
    let line = 1;
    let col = 1;

    while (index < text.length) {
        if (text[index] == "\n") {
            line += 1;
            col = 1;
        } else {
            col += 1;
        }
        index += 1;
    }

    return { index, line, col };
}
