import { AstCreator, Expr, ExprKind, Stmt, StmtKind } from "../ast.ts";
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "../ast_visitor.ts";
import { Pos } from "../token.ts";

export class SpecialLoopDesugarer implements AstVisitor {
    public constructor(
        private astCreator: AstCreator,
    ) {}

    public desugar(stmts: Stmt[]) {
        visitStmts(stmts, this);
    }

    visitWhileExpr(expr: Expr): VisitRes {
        if (expr.kind.type !== "while") {
            throw new Error();
        }
        const npos: Pos = { index: 0, line: 1, col: 1 };
        const Expr = (kind: ExprKind, pos = npos) =>
            this.astCreator.expr(kind, pos);
        const Stmt = (kind: StmtKind, pos = npos) =>
            this.astCreator.stmt(kind, pos);

        expr.kind = {
            type: "loop",
            body: Expr({
                type: "block",
                stmts: [
                    Stmt({
                        type: "expr",
                        expr: Expr({
                            type: "if",
                            cond: Expr({
                                type: "unary",
                                unaryType: "not",
                                subject: expr.kind.cond,
                            }),
                            truthy: Expr({
                                type: "block",
                                stmts: [
                                    Stmt({ type: "break" }),
                                ],
                            }),
                        }),
                    }),
                    Stmt({
                        type: "expr",
                        expr: expr.kind.body,
                    }),
                ],
            }),
        };

        visitExpr(expr, this);
        return "stop";
    }

    visitForInExpr(expr: Expr): VisitRes {
        if (expr.kind.type !== "for_in") {
            throw new Error();
        }
        const npos: Pos = { index: 0, line: 1, col: 1 };
        const Expr = (kind: ExprKind, pos = npos) =>
            this.astCreator.expr(kind, pos);
        const Stmt = (kind: StmtKind, pos = npos) =>
            this.astCreator.stmt(kind, pos);

        expr.kind = {
            type: "block",
            stmts: [
                Stmt({
                    type: "let",
                    param: this.astCreator.param({
                        ident: "::values",
                        mut: true,
                        pos: npos,
                    }),
                    value: expr.kind.value,
                }),
                Stmt({
                    type: "let",
                    param: this.astCreator.param({
                        ident: "::length",
                        mut: false,
                        pos: npos,
                    }),
                    value: Expr({
                        type: "call",
                        subject: Expr({
                            type: "path",
                            subject: Expr({
                                type: "ident",
                                ident: "std",
                            }),
                            ident: "array_length",
                        }),
                        args: [
                            Expr({
                                type: "ident",
                                ident: "::values",
                            }),
                        ],
                    }),
                }),
                Stmt({
                    type: "let",
                    param: this.astCreator.param({
                        ident: "::index",
                        mut: true,
                        pos: npos,
                    }),
                    value: Expr({ type: "int", value: 0 }),
                }, expr.pos),
                Stmt({
                    type: "expr",
                    expr: Expr({
                        type: "loop",
                        body: Expr({
                            type: "block",
                            stmts: [
                                Stmt({
                                    type: "expr",
                                    expr: Expr({
                                        type: "if",
                                        cond: Expr({
                                            type: "unary",
                                            unaryType: "not",
                                            subject: Expr({
                                                type: "binary",
                                                binaryType: "<",
                                                left: Expr({
                                                    type: "ident",
                                                    ident: "::index",
                                                }),
                                                right: Expr({
                                                    type: "ident",
                                                    ident: "::length",
                                                }),
                                            }),
                                        }),
                                        truthy: Expr({
                                            type: "block",
                                            stmts: [
                                                Stmt({
                                                    type: "break",
                                                }),
                                            ],
                                        }),
                                    }),
                                }),
                                Stmt({
                                    type: "let",
                                    param: expr.kind.param,
                                    value: Expr({
                                        type: "index",
                                        subject: Expr({
                                            type: "ident",
                                            ident: "::values",
                                        }),
                                        value: Expr({
                                            type: "ident",
                                            ident: "::index",
                                        }),
                                    }),
                                }, expr.pos),
                                Stmt({
                                    type: "expr",
                                    expr: expr.kind.body,
                                }),
                                Stmt({
                                    type: "assign",
                                    assignType: "+=",
                                    subject: Expr({
                                        type: "ident",
                                        ident: "::index",
                                    }),
                                    value: Expr({
                                        type: "int",
                                        value: 1,
                                    }),
                                }),
                            ],
                        }),
                    }),
                }),
            ],
        };

        visitExpr(expr, this);
        return "stop";
    }

    visitForExpr(expr: Expr): VisitRes {
        if (expr.kind.type !== "for") {
            throw new Error();
        }
        const Expr = (
            kind: ExprKind,
            pos: Pos = { index: 0, line: 1, col: 1 },
        ) => this.astCreator.expr(kind, pos);
        const Stmt = (
            kind: StmtKind,
            pos: Pos = { index: 0, line: 1, col: 1 },
        ) => this.astCreator.stmt(kind, pos);

        expr.kind = {
            type: "block",
            stmts: [
                ...[expr.kind.decl]
                    .filter((v) => v !== undefined),
                Stmt({
                    type: "expr",
                    expr: Expr({
                        type: "loop",
                        body: Expr({
                            type: "block",
                            stmts: [
                                ...[expr.kind.cond]
                                    .filter((v) => v !== undefined)
                                    .map(
                                        (cond) =>
                                            Stmt({
                                                type: "expr",
                                                expr: Expr({
                                                    type: "if",
                                                    cond: Expr({
                                                        type: "unary",
                                                        unaryType: "not",
                                                        subject: cond,
                                                    }),
                                                    truthy: Expr({
                                                        type: "block",
                                                        stmts: [
                                                            Stmt({
                                                                type: "break",
                                                            }),
                                                        ],
                                                    }),
                                                }),
                                            }, cond.pos),
                                    ),
                                Stmt({
                                    type: "expr",
                                    expr: expr.kind.body,
                                }),
                                ...[expr.kind.incr]
                                    .filter((v) => v !== undefined),
                            ],
                        }),
                    }),
                }),
            ],
        };

        visitExpr(expr, this);
        return "stop";
    }
}
