package org.jruby.ext;

import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyKernel;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;

/**
 * This library adds reference queue support to JRuby's weakrefs by adding a
 * RefQueue class that wraps a Java ReferenceQueue and replacing the built-in
 * WeakRef impl with a new one that's aware of RefQueue.
 * 
 * @author headius
 */
public class RefQueueLibrary implements Library {
    public void load(Ruby runtime, boolean wrap) throws IOException {
        // only used for RefError
        RubyKernel.require(runtime.getKernel(), runtime.newString("weakref"), Block.NULL_BLOCK);

        RubyModule weaklingModule = runtime.getOrCreateModule("Weakling");
        RubyClass weakrefClass = runtime.defineClassUnder("WeakRef", runtime.getObject(), WEAKREF_ALLOCATOR, weaklingModule);
        weakrefClass.setAllocator(WEAKREF_ALLOCATOR);
        weakrefClass.defineAnnotatedMethods(WeakRef.class);

        RubyClass refQueueClass = runtime.defineClassUnder("RefQueue", runtime.getObject(), REFQUEUE_ALLOCATOR, weaklingModule);
        refQueueClass.defineAnnotatedMethods(RefQueue.class);
    }
    
    private static final ObjectAllocator WEAKREF_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
            return new WeakRef(runtime, klazz);
        }
    };

    private static final ObjectAllocator REFQUEUE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
            return new RefQueue(runtime, klazz);
        }
    };

    @JRubyClass(name="RefQueue", parent="Object")
    public static class RefQueue extends RubyObject {
        private final ReferenceQueue queue;

        public RefQueue(Ruby runtime, RubyClass klass) {
            super(runtime, klass);
            queue = new ReferenceQueue();
        }

        public ReferenceQueue getQueue() {
            return queue;
        }

        @JRubyMethod
        public IRubyObject poll() {
            return returnable(queue.poll());
        }

        @JRubyMethod
        public IRubyObject remove() {
            try {
                return returnable(queue.remove());
            } catch (InterruptedException ie) {
                // ignore
                return getRuntime().getNil();
            }
        }

        @JRubyMethod
        public IRubyObject remove(IRubyObject timeout) {
            try {
                return returnable(queue.remove(timeout.convertToInteger().getLongValue()));
            } catch (InterruptedException ie) {
                // ignore
                return getRuntime().getNil();
            }
        }

        private IRubyObject returnable(Object result) {
            RubyWeakReference ref = (RubyWeakReference)result;
            if (ref == null) return getRuntime().getNil();
            return ref.getWeakRef();
        }
    }

    public static class RubyWeakReference extends WeakReference<IRubyObject> {
        private final WeakRef ref;
        public RubyWeakReference(IRubyObject obj, WeakRef ref) {
            super(obj);
            this.ref = ref;
        }
        public RubyWeakReference(IRubyObject obj, WeakRef ref, ReferenceQueue queue) {
            super(obj, queue);
            this.ref = ref;
        }
        public WeakRef getWeakRef() {
            return ref;
        }
    }

    public static class WeakRef extends RubyObject {
        private RubyWeakReference ref;

        public WeakRef(Ruby runtime, RubyClass klazz) {
            super(runtime, klazz);
        }

        @JRubyMethod(name = "get")
        public IRubyObject get() {
            IRubyObject obj = ref.get();

            if (obj == null) {
                // FIXME weakref.rb also does caller(2) here for the backtrace
                throw newRefError("Illegal Reference - probably recycled");
            }

            return obj;
        }

        @JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
        public IRubyObject initialize(ThreadContext context, IRubyObject obj) {
            ref = new RubyWeakReference(obj, this);

            return context.getRuntime().getNil();
        }

        @JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
        public IRubyObject initialize(ThreadContext context, IRubyObject obj, IRubyObject queue) {
            if (!(queue instanceof RefQueue)) {
                throw getRuntime().newTypeError("WeakRef can only queue into a RefQueue");
            }
            ref = new RubyWeakReference(obj, this, ((RefQueue)queue).getQueue());

            return context.getRuntime().getNil();
        }

        @JRubyMethod(name = "weakref_alive?")
        public IRubyObject weakref_alive_p() {
            return ref.get() != null ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        private RaiseException newRefError(String message) {
            RubyException exception =
                    (RubyException)getRuntime().getClass("RefError").newInstance(getRuntime().getCurrentContext(),
                    new IRubyObject[] {getRuntime().newString(message)}, Block.NULL_BLOCK);

            return new RaiseException(exception);
        }
    }
}

@@ -405,58 +405,8 @@
 on;%0A
-import org.jruby.javasupport.util.RuntimeHelpers;%0A
 impo

Remove unused import.