/* Copyright (c) Jython Developers */
package org.python.core;

import org.python.expose.ExposedGet;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedType;

@ExposedType(name = "generator", base = PyObject.class, isBaseType = false)
public class PyGenerator extends PyIterator {

    public static final PyType TYPE = PyType.fromClass(PyGenerator.class);

    @ExposedGet
    protected PyFrame gi_frame;

    @ExposedGet
    protected boolean gi_running;

    private PyObject closure;

    public PyGenerator(PyFrame frame, PyObject closure) {
        super(TYPE);
        gi_frame = frame;
        this.closure = closure;
    }

    public PyObject send(PyObject value) {
        return generator_send(value);
    }

    @ExposedMethod
    final PyObject generator_send(PyObject value) {
        if (gi_frame == null) {
            throw Py.StopIteration("");
        }
        if (gi_frame.f_lasti == 0 && value != Py.None) {
            throw Py.TypeError("can't send non-None value to a just-started generator");
        }
        gi_frame.setGeneratorInput(value);
        return next();
    }

    public PyObject throw$(PyObject type, PyObject value, PyObject tb) {
        return generator_throw$(type, value, tb);
    }

    @ExposedMethod(names="throw", defaults={"null", "null"})
    final PyObject generator_throw$(PyObject type, PyObject value, PyObject tb) {
        if (tb == Py.None) {
            tb = null;
        } else if (tb != null && !(tb instanceof PyTraceback)) {
            throw Py.TypeError("throw() third argument must be a traceback object");
        }
        return raiseException(Py.makeException(type, value, tb));
    }

    public PyObject close() {
        return generator_close();
    }

    @ExposedMethod
    final PyObject generator_close() {
        try {
            raiseException(Py.makeException(Py.GeneratorExit));
            throw Py.RuntimeError("generator ignored GeneratorExit");
        } catch (PyException e) {
            if (!(e.type == Py.StopIteration || e.type == Py.GeneratorExit)) {
                throw e;
            }
        }
        return Py.None;
    }

    @Override
    public PyObject next() {
        return generator_next();
    }

    @ExposedMethod(doc="x.next() -> the next value, or raise StopIteration")
    final PyObject generator_next() {
        return super.next();
    }

    @Override
    public PyObject __iter__() {
        return generator___iter__();
    }

    @ExposedMethod
    final PyObject generator___iter__() {
        return this;
    }

    private PyObject raiseException(PyException ex) {
        if (gi_frame == null || gi_frame.f_lasti == 0) {
            gi_frame = null;
            throw ex;
        }
        gi_frame.setGeneratorInput(ex);
        return next();
    }

    @Override
    protected void finalize() throws Throwable {
        if (gi_frame == null || gi_frame.f_lasti == -1) {
            return;
        }
        try {
            close();
        } catch (PyException pye) {
            // PEP 342 specifies that if an exception is raised by close,
            // we output to stderr and then forget about it;
            String className =  PyException.exceptionClassName(pye.type);
            int lastDot = className.lastIndexOf('.');
            if (lastDot != -1) {
                className = className.substring(lastDot + 1);
            }
            String msg = String.format("Exception %s: %s in %s", className, pye.value.__repr__(),
                                       __repr__());
            Py.println(Py.getSystemState().stderr, Py.newString(msg));
        } catch (Throwable t) {
            // but we currently ignore any Java exception completely. perhaps we
            // can also output something meaningful too?
        } finally {
            super.finalize();
        }
    }

    @Override
    public PyObject __iternext__() {
        return __iternext__(Py.getThreadState());
    }

    public PyObject __iternext__(ThreadState state) {
        if (gi_running) {
            throw Py.ValueError("generator already executing");
        }
        if (gi_frame == null) {
            return null;
        }

        if (gi_frame.f_lasti == -1) {
            gi_frame = null;
            return null;
        }
        gi_running = true;
        PyObject result = null;
        try {
            result = gi_frame.f_code.call(state, gi_frame, closure);
        } catch (PyException pye) {
            if (!(pye.type == Py.StopIteration || pye.type == Py.GeneratorExit)) {
                gi_frame = null;
                throw pye;
            } else {
                stopException = pye;
                gi_frame = null;
                return null;
            }
        } finally {
            gi_running = false;
        }
        if (result == Py.None && gi_frame.f_lasti == -1) {
            return null;
        }
        return result;
    }
}
