/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.prism.impl;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.PhantomReference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Hashtable;
import java.util.LinkedList;

/**
 * This class is used for registering and disposing the native
 * data associated with Java objects.
 *
 * The object can register itself by calling one of the addRecord
 * methods and providing either the pointer to the native disposal
 * method or a descendant of the Disposer.Record interface with overridden
 * dispose() method.
 *
 * When the object becomes unreachable, the dispose() method
 * of the associated Disposer.Record object will be called.
 */
public class Disposer {

    private static Disposer disposerInstance;
    private static final int WEAK = 0;
    private static final int PHANTOM = 1;
    private static final int SOFT = 2;
    private static int refType = PHANTOM;

    static {
        String type = PrismSettings.refType;
        if (type != null) {
            if (type.equals("weak")) {
                refType = WEAK;
                if (PrismSettings.verbose) System.err.println("Using WEAK refs");
            } else if (type.equals("soft")) {
                refType = SOFT;
                if (PrismSettings.verbose) System.err.println("Using SOFT refs");
            } else {
                refType = PHANTOM;
                if (PrismSettings.verbose) System.err.println("Using PHANTOM refs");
            }
        }
        disposerInstance = new Disposer();
    }

    private final ReferenceQueue queue = new ReferenceQueue();
    private final Hashtable records = new Hashtable();
    private final LinkedList<Record> disposalQueue = new LinkedList<Record>();

    /**
     * Private constructor to prevent outside instantiation.
     */
    private Disposer() {
    }

//    /**
//     * Registers the object and the native data for later disposal.
//     * @param target Object to be registered
//     * @param disposeMethod pointer to the native disposal method
//     * @param pData pointer to the data to be passed to the
//     *              native disposal method
//     */
//    public static void addRecord(Object target,
//                                 long disposeMethod, long pData)
//    {
//        disposerInstance.add(target,
//                             new DefaultDisposerRecord(disposeMethod, pData));
//    }

    /**
     * Registers the target object and the native data for later disposal when
     * the target is unreachable.
     * .
     * @param target Object to be registered
     * @param rec the associated DisposerRecord object
     * @see DisposerRecord
     */
    public static void addRecord(Object target, Disposer.Record rec) {
        disposerInstance.add(target, rec);
    }

    /**
     * Add the object to the disposal queue. The object will be disposed
     * the next time cleanup is called.
     *
     * @param rec the DisposerRecord object to be disposed
     */
    public static void disposeRecord(Disposer.Record rec) {
        disposerInstance.addToDisposalQueue(rec);
    }

    /**
     * Disposes all unreachable objects and all objects in the disposal queue.
     * It first polls the reference queue, calling the dispose method of each
     * unreachable object. It then iterates the list of objects in the
     * disposal queue, calling the dispose method of each object.
     *
     * NOTE: This method must only be called from the Render Thread (the
     * thread on which  the resources were created).
     */
    public static void cleanUp() {
        disposerInstance.disposeUnreachables();
        disposerInstance.processDisposalQueue();
    }

    /**
     * Performs the actual registration of the target object to be disposed.
     * @param target Object to be registered, or if target is an instance
     *               of DisposerTarget, its associated disposer referent
     *               will be the Object that is registered
     * @param rec the associated DisposerRecord object
     * @see DisposerRecord
     */
    synchronized private void add(Object target, Disposer.Record rec) {
        if (target instanceof Disposer.Target) {
            target = ((Disposer.Target)target).getDisposerReferent();
        }
        Reference ref;
        if (refType == PHANTOM) {
            ref = new PhantomReference(target, queue);
        } else if (refType == SOFT) {
            ref = new SoftReference(target, queue);
        } else {
            ref = new WeakReference(target, queue);
        }
        records.put(ref, rec);
    }

    synchronized private void addToDisposalQueue(Disposer.Record rec) {
        disposalQueue.add(rec);
    }

    /**
     * Polls the reference queue to see if there are any unreachable objects
     * to be disposed.  If there is work to be done, this method disposes all
     * unreachable objects in the queue, otherwise it returns immediately.
     */
    synchronized private void disposeUnreachables() {
        Object obj;
        while ((obj = queue.poll()) != null) {
            try {
                ((Reference)obj).clear();
                Disposer.Record rec = (Disposer.Record)records.remove(obj);
                rec.dispose();
                obj = null;
                rec = null;
            } catch (Exception e) {
                System.out.println("Exception while removing reference: " + e);
                e.printStackTrace();
            }
        }
    }

    synchronized private void processDisposalQueue() {
        // disposalQueue is always empty in the case of Windows using the d3d pipe.
        while (!disposalQueue.isEmpty()) {
            disposalQueue.remove().dispose();
        }
    }

    /**
     * This interface is used to hold the resource to be disposed.
     */
    public static interface Record {
        public void dispose();
    }

    /**
     * This is an interface which should be implemented by
     * the classes which use Disposer.
     */
    public static interface Target {
        /**
         * Returns an object which will be
         * used as the referent in the ReferenceQueue.
         */
        public Object getDisposerReferent();
    }
}
