// Copyright 2001 Finn Bock

package org.python.modules;

import java.lang.ref.*;
import java.util.*;
import org.python.core.*;


public class _weakref implements ClassDictInit {
    static ReferenceQueue referenceQueue = new ReferenceQueue();

    static RefReaperThread reaperThread;
    static Map objects = new HashMap();

    public static PyObject ReferenceError = null;

    static {
        reaperThread = new RefReaperThread();
        reaperThread.setDaemon(true);
        reaperThread.start();
    }

    /** <i>Internal use only. Do not call this method explicit.</i> */
    public static void classDictInit(PyObject dict)
        throws PyIgnoreMethodTag
    {
        ReferenceError = Py.makeClass("ReferenceError",
                            new PyObject[] { Py.RuntimeError },
                            Py.newJavaCode(_weakref.class, "empty__init__"),
                            Py.None);
        dict.__setitem__("ReferenceError", ReferenceError);
    }

    // An empty __init__ method
    public static PyObject empty__init__(PyObject[] arg, String[] kws) {
        PyObject dict = new PyStringMap();
        dict.__setitem__("__module__", new PyString("_weakref"));
        return dict;
    }

    public static ReferenceType ref(PyObject object)  {
        GlobalRef gref = mkGlobal(object);
        ReferenceType ret = (ReferenceType)gref.find(ReferenceType.class);
        if (ret != null) {
            return ret;
        }
        return new ReferenceType(mkGlobal(object), null);
    }

    public static ReferenceType ref(PyObject object, PyObject callback) {
        return new ReferenceType(mkGlobal(object), callback);
    }

    public static ProxyType proxy(PyObject object)  {
        GlobalRef gref = mkGlobal(object);
        ProxyType ret = (ProxyType)gref.find(ProxyType.class);
        if (ret != null) {
            return ret;
        }
        if (object.isCallable()) {
            return new CallableProxyType(mkGlobal(object), null);
        } else {
            return new ProxyType(mkGlobal(object), null);
        }
    }

    public static ProxyType proxy(PyObject object, PyObject callback) {
        if (object.isCallable()) {
            return new CallableProxyType(mkGlobal(object), callback);
        } else {
            return new ProxyType(mkGlobal(object), callback);
        }
    }

    public static int getweakrefcount(PyObject o) {
        GlobalRef ref = (GlobalRef) objects.get(new GlobalRef(o));
        if (ref == null)
            return 0;
        return ref.count();
    }

    public static PyList getweakrefs(PyObject o) {
        GlobalRef ref = (GlobalRef) objects.get(new GlobalRef(o));
        if (ref == null)
            return new PyList();
        return ref.refs();
    }

    private static GlobalRef mkGlobal(PyObject object) {
        GlobalRef ref = (GlobalRef) objects.get(new GlobalRef(object));
        if (ref == null) {
            ref = new GlobalRef(object, referenceQueue);
            objects.put(ref, ref);
        }
        return ref;
    }

    static class RefReaperThread extends Thread {
        RefReaperThread() {
            super("weakref reaper");
        }

        public void collect() throws InterruptedException {
            GlobalRef gr = (GlobalRef) referenceQueue.remove();
            gr.call();
            objects.remove(gr);
            gr = null;
        }

        public void run() {
            while (true) {
                try {
                    collect();
                } catch (InterruptedException exc) { }
             }
        }
    }


    public static class GlobalRef extends WeakReference {
        private Vector references = new Vector();
        private int hash;

        public GlobalRef(PyObject object) {
            super(object);
            hash = object.hashCode();
        }

        public GlobalRef(PyObject object, ReferenceQueue queue) {
            super(object, queue);
            hash = object.hashCode();
        }

        public synchronized void add(AbstractReference ref) {
            Reference r = new WeakReference(ref);
            references.addElement(r);
        }

        private final AbstractReference getReferenceAt(int idx) {
            WeakReference wref = (WeakReference) references.elementAt(idx);
            return (AbstractReference) wref.get();
        }

        /**
         * Search for a reusable refrence. To be reused, it must be of the
         * same class and it must not have a callback.
         */
        synchronized AbstractReference find(Class cls) {
            for (int i = references.size() - 1; i >= 0; i--) {
                AbstractReference r = getReferenceAt(i);
                if (r == null)
                    references.removeElementAt(i);
                else if (r.callback == null && r.getClass() == cls) {
                    return r;
                }
            }
            return null;
        }

        /**
         * Call each of the registered references.
         */
        synchronized void call() {
            for (int i = references.size() - 1; i >= 0; i--) {
                AbstractReference r = getReferenceAt(i);
                if (r == null)
                    references.removeElementAt(i);
                else
                    r.call();
            }
        }

        synchronized public int count() {
            for (int i = references.size() - 1; i >= 0; i--) {
                AbstractReference r = getReferenceAt(i);
                if (r == null) {
                    references.removeElementAt(i);
                }
            }
            return references.size();
        }

        synchronized public PyList refs() {
            Vector list = new Vector();
            for (int i = references.size() - 1; i >= 0; i--) {
                AbstractReference r = getReferenceAt(i);
                if (r == null)
                    references.removeElementAt(i);
                else
                    list.addElement(r);
            }
            return new PyList(list);
        }

        /**
         * Allow GlobalRef's to be used as hashtable keys.
         */
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof GlobalRef)) return false;
            Object t = this.get();
            Object u = ((GlobalRef)o).get();
            if ((t == null) || (u == null)) return false;
            if (t == u) return true;
            return t.equals(u);
        }

        /**
         * Allow GlobalRef's to be used as hashtable keys.
         */
        public int hashCode() {
            return hash;
        }
    }


    public static abstract class AbstractReference extends PyObject {
        PyObject callback;
        protected GlobalRef gref;

        public AbstractReference(GlobalRef gref, PyObject callback) {
            this.gref = gref;
            this.callback = callback;
            gref.add(this);
        }

        void call() {
            if (callback == null)
                return;
            try {
                callback.__call__(this);
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        }

        protected PyObject py() {
            PyObject o = (PyObject) gref.get();
            if (o == null) {
                throw new PyException(ReferenceError,
                              "weakly-referenced object no longer exists");
            }
            return o;
        }

        public int hashCode() {
            return gref.hash;
        }

        public PyObject __eq__(PyObject other) {
            if (other.getClass() != getClass())
                return null;
            PyObject pythis = (PyObject) gref.get();
            PyObject pyother = (PyObject) ((AbstractReference) other).
                                                            gref.get();
            if (pythis == null || pyother == null)
                return this == other ? Py.One : Py.Zero;
            return  pythis._eq(pyother);
        }

    }


    public static class ReferenceType extends AbstractReference {
        ReferenceType(GlobalRef gref, PyObject callback) {
            super(gref, callback);
        }

        public PyObject __call__() {
            return Py.java2py(gref.get());
        }

        public String toString() {
            String ret = "<weakref at " +
                    Integer.toString(System.identityHashCode(this), 16) +";";
            PyObject obj = (PyObject) gref.get();
            if (obj != null)
                ret += " to " + obj.safeRepr() + ">";
            else
                ret += " dead>";
            return ret;
        }
    }

    public static class ProxyType extends AbstractReference {
        ProxyType(GlobalRef ref, PyObject callback) {
            super(ref, callback);
        }

        public PyObject __findattr__(String name) {
            return py().__findattr__(name);
        }

        public void __setattr__(String name, PyObject value) {
            py().__setattr__(name, value);
        }

        public void __delattr__(String name) {
            py().__delattr__(name);
        }

        public PyString __str__() { return py().__str__(); }
        public PyString __hex__() { return py().__hex__(); }
        public PyString __oct__() { return py().__oct__(); }
        public PyInteger __int__() { return py().__int__(); }
        public PyFloat __float__() { return py().__float__(); }
        public PyLong __long__() { return py().__long__(); }
        public PyComplex __complex__() { return py().__complex__(); }
        public PyObject __pos__() { return py().__pos__(); }
        public PyObject __neg__() { return py().__neg__(); }
        public PyObject __abs__() { return py().__abs__(); }
        public PyObject __invert__() { return py().__invert__(); }

        public PyObject __add__(PyObject o) { return py().__add__(o); }
        public PyObject __radd__(PyObject o) { return py().__radd__(o); }
        public PyObject __iadd__(PyObject o) { return py().__iadd__(o); }
        public PyObject __sub__(PyObject o) { return py().__sub__(o); }
        public PyObject __rsub__(PyObject o) { return py().__rsub__(o); }
        public PyObject __isub__(PyObject o) { return py().__isub__(o); }
        public PyObject __mul__(PyObject o) { return py().__mul__(o); }
        public PyObject __rmul__(PyObject o) { return py().__rmul__(o); }
        public PyObject __imul__(PyObject o) { return py().__imul__(o); }
        public PyObject __div__(PyObject o) { return py().__div__(o); }
        public PyObject __rdiv__(PyObject o) { return py().__rdiv__(o); }
        public PyObject __idiv__(PyObject o) { return py().__idiv__(o); }
        public PyObject __mod__(PyObject o) { return py().__mod__(o); }
        public PyObject __rmod__(PyObject o) { return py().__rmod__(o); }
        public PyObject __imod__(PyObject o) { return py().__imod__(o); }
        public PyObject __divmod__(PyObject o) { return py().__divmod__(o); }
        public PyObject __rdivmod__(PyObject o) { return py().__rdivmod__(o);}
        public PyObject __pow__(PyObject o) { return py().__pow__(o); }
        public PyObject __rpow__(PyObject o) { return py().__rpow__(o); }
        public PyObject __ipow__(PyObject o) { return py().__ipow__(o); }
        public PyObject __lshift__(PyObject o) { return py().__lshift__(o); }
        public PyObject __rlshift__(PyObject o) { return py().__rlshift__(o);}
        public PyObject __ilshift__(PyObject o) { return py().__ilshift__(o);}

        public PyObject __rshift__(PyObject o) { return py().__rshift__(o); }
        public PyObject __rrshift__(PyObject o) { return py().__rrshift__(o);}
        public PyObject __irshift__(PyObject o) { return py().__irshift__(o);}
        public PyObject __and__(PyObject o) { return py().__and__(o); }
        public PyObject __rand__(PyObject o) { return py().__rand__(o); }
        public PyObject __iand__(PyObject o) { return py().__iand__(o); }
        public PyObject __or__(PyObject o) { return py().__or__(o); }
        public PyObject __ror__(PyObject o) { return py().__ror__(o); }
        public PyObject __ior__(PyObject o) { return py().__ior__(o); }
        public PyObject __xor__(PyObject o) { return py().__xor__(o); }
        public PyObject __rxor__(PyObject o) { return py().__rxor__(o); }
        public PyObject __ixor__(PyObject o) { return py().__ixor__(o); }

        public String toString() {
            String ret = "<weakref at " +
                    Integer.toString(System.identityHashCode(this), 16);
            PyObject obj = (PyObject) gref.get();
            if (obj == null)
                obj = Py.None;
            ret += " to " + obj.safeRepr() + " at "+
                    Integer.toString(System.identityHashCode(obj), 16) + ">";
            return ret;
        }
    }

    public static class CallableProxyType extends ProxyType {
        CallableProxyType(GlobalRef ref, PyObject callback) {
            super(ref, callback);
        }
        public PyObject __call__(PyObject[] args, String[] kws) {
            return py().__call__(args, kws);
        }
    }


}


