package me.topchetoeu.jscript.lib;

import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;

@Native("Generator") public class GeneratorLib {
    private boolean yielding = true;
    private boolean done = false;
    public CodeFrame frame;

    @Native("@@Symbol.typeName") public final String name = "Generator";

    private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
        if (done) {
            if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
            var res = new ObjectValue();
            res.defineProperty(ctx, "done", true);
            res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
            return res;
        }

        Object res = null;
        ctx.pushFrame(frame);
        yielding = false;

        while (!yielding) {
            try {
                res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
                inducedReturn = inducedError = Runners.NO_RETURN;
                if (res != Runners.NO_RETURN) {
                    done = true;
                    break;
                }
            }
            catch (EngineException e) {
                done = true;
                throw e;
            }
        }

        ctx.popFrame(frame);
        if (done) frame = null;
        else res = frame.pop();

        var obj = new ObjectValue();
        obj.defineProperty(ctx, "done", done);
        obj.defineProperty(ctx, "value", res);
        return obj;
    }

    @Native
    public ObjectValue next(Context ctx, Object ...args) {
        if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
        else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
    }
    @Native("throw")
    public ObjectValue _throw(Context ctx, Object error) {
        return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
    }
    @Native("return")
    public ObjectValue _return(Context ctx, Object value) {
        return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
    }

    @Override
    public String toString() {
        if (done) return "Generator [closed]";
        if (yielding) return "Generator [suspended]";
        return "Generator [running]";
    }

    public Object yield(Context ctx, Object thisArg, Object[] args) {
        this.yielding = true;
        return args.length > 0 ? args[0] : null;
    }
}