export interface Locals {
    reserveAmount(id: number): void;
    allocSym(ident: string): void;
    symId(ident: string): number;

    currentLocalIdCounter(): number;
}

export class LocalsFnRoot implements Locals {
    private localsAmount = 0;
    private symLocalMap: { [key: string]: number } = {};
    private localIdCounter: number;

    constructor(private parent?: Locals) {
        this.localIdCounter = parent?.currentLocalIdCounter() ?? 0;
    }

    public reserveAmount(amount: number): void {
        this.localsAmount = Math.max(amount, this.localsAmount);
    }

    public allocSym(ident: string) {
        this.symLocalMap[ident] = this.localIdCounter;
        this.localIdCounter++;
        this.reserveAmount(this.localIdCounter);
    }

    public symId(ident: string): number {
        if (ident in this.symLocalMap) {
            return this.symLocalMap[ident];
        }
        if (this.parent) {
            return this.parent.symId(ident);
        }
        throw new Error(`undefined symbol '${ident}'`);
    }

    public stackReserved(): number {
        return this.localsAmount;
    }

    public currentLocalIdCounter(): number {
        return this.localIdCounter;
    }
}

export class LocalLeaf implements Locals {
    private symLocalMap: { [key: string]: number } = {};
    private localIdCounter: number;

    constructor(private parent: Locals) {
        this.localIdCounter = parent.currentLocalIdCounter();
    }

    public reserveAmount(amount: number): void {
        this.parent.reserveAmount(amount);
    }

    public allocSym(ident: string) {
        this.symLocalMap[ident] = this.localIdCounter;
        this.localIdCounter++;
        this.reserveAmount(this.localIdCounter);
    }

    public symId(ident: string): number {
        if (ident in this.symLocalMap) {
            return this.symLocalMap[ident];
        }
        return this.parent.symId(ident);
    }

    public currentLocalIdCounter(): number {
        return this.localIdCounter;
    }
}