package org.jruby.ext.fiber;

import java.util.Map;
import java.util.WeakHashMap;

import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.RubyThread;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ExecutionContext;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyClass(name = "Fiber")
public abstract class Fiber extends RubyObject implements ExecutionContext {
    private final Map<Object, IRubyObject> contextVariables = new WeakHashMap<Object, IRubyObject>();
    protected volatile Block block;
    protected volatile RubyThread parent;
    protected boolean root;
    protected volatile Fiber transferredFrom;
    protected volatile Fiber transferredTo;

    @JRubyMethod(visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context, Block block) {
        final Ruby runtime = context.runtime;
        if (!root && (block == null || !block.isGiven())) {
            throw runtime.newArgumentError("tried to create Proc object without a block");
        }
        this.block = block;
        this.parent = context.getThread();
        
        initFiber(context);
        
        return this;
    }

    public Fiber(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }
    
    protected abstract void initFiber(ThreadContext context);

    protected abstract IRubyObject resumeOrTransfer(ThreadContext context, IRubyObject arg, boolean transfer);

    public abstract IRubyObject yield(ThreadContext context, IRubyObject res);
    
    public abstract boolean isAlive();

    public boolean isRoot() {
        return root;
    }

    public Fiber makeRootFiber() {
        root = true;
        return this;
    }

    @JRubyMethod()
    public IRubyObject resume(ThreadContext context) {
        return resumeOrTransfer(context, context.nil, false);
    }

    @JRubyMethod()
    public IRubyObject resume(ThreadContext context, IRubyObject arg) {
        return resumeOrTransfer(context, arg, false);
    }

    @JRubyMethod(rest = true)
    public IRubyObject resume(ThreadContext context, IRubyObject[] args) {
        return resumeOrTransfer(context, context.runtime.newArrayNoCopyLight(args), false);
    }

    // This should only be defined after require 'fiber'
    @JRubyMethod()
    public IRubyObject transfer(ThreadContext context) {
        return resumeOrTransfer(context, context.nil, true);
    }

    // This should only be defined after require 'fiber'
    @JRubyMethod()
    public IRubyObject transfer(ThreadContext context, IRubyObject arg) {
        return resumeOrTransfer(context, arg, true);
    }

    // This should only be defined after require 'fiber'
    @JRubyMethod(rest = true)
    public IRubyObject transfer(ThreadContext context, IRubyObject[] args) {
        return resumeOrTransfer(context, context.runtime.newArrayNoCopyLight(args), true);
    }

    public Map<Object, IRubyObject> getContextVariables() {
        return contextVariables;
    }

    /**
     * @return the transferredFrom
     */
    public Fiber getTransferredFrom() {
        return transferredFrom;
    }

    /**
     * @param transferredFrom the transferredFrom to set
     */
    public void setTransferredFrom(Fiber transferredFrom) {
        this.transferredFrom = transferredFrom;
    }

    /**
     * @return the transferredTo
     */
    public Fiber getTransferredTo() {
        return transferredTo;
    }

    /**
     * @param transferredTo the transferredTo to set
     */
    public void setTransferredTo(Fiber transferredTo) {
        this.transferredTo = transferredTo;
    }
    
}
