/*
 * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.image;

import java.util.Vector;
import sun.awt.AppContext;

/**
  * An ImageFetcher is a thread used to fetch ImageFetchable objects.
  * Once an ImageFetchable object has been fetched, the ImageFetcher
  * thread may also be used to animate it if necessary, via the
  * startingAnimation() / stoppingAnimation() methods.
  *
  * There can be up to FetcherInfo.MAX_NUM_FETCHERS_PER_APPCONTEXT
  * ImageFetcher threads for each AppContext.  A per-AppContext queue
  * of ImageFetchables is used to track objects to fetch.
  *
  * @author Jim Graham
  * @author Fred Ecks
  */
class ImageFetcher extends Thread {
    static final int HIGH_PRIORITY = 8;
    static final int LOW_PRIORITY = 3;
    static final int ANIM_PRIORITY = 2;

    static final int TIMEOUT = 5000; // Time in milliseconds to wait for an
                                     // ImageFetchable to be added to the
                                     // queue before an ImageFetcher dies

    /**
      * Constructor for ImageFetcher -- only called by add() below.
      */
    private ImageFetcher(ThreadGroup threadGroup, int index) {
        super(threadGroup, "Image Fetcher " + index);
        setDaemon(true);
    }

    /**
      * Adds an ImageFetchable to the queue of items to fetch.  Instantiates
      * a new ImageFetcher if it's reasonable to do so.
      * If there is no available fetcher to process an ImageFetchable, then
      * reports failure to caller.
      */
    public static boolean add(ImageFetchable src) {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        synchronized(info.waitList) {
            if (!info.waitList.contains(src)) {
                info.waitList.addElement(src);
                if (info.numWaiting == 0 &&
                            info.numFetchers < info.fetchers.length) {
                    createFetchers(info);
                }
                /* Creation of new fetcher may fail due to high vm load
                 * or some other reason.
                 * If there is already exist, but busy, fetcher, we leave
                 * the src in queue (it will be handled by existing
                 * fetcher later).
                 * Otherwise, we report failure: there is no fetcher
                 * to handle the src.
                 */
                if (info.numFetchers > 0) {
                    info.waitList.notify();
                } else {
                    info.waitList.removeElement(src);
                    return false;
                }
            }
        }
        return true;
    }

    /**
      * Removes an ImageFetchable from the queue of items to fetch.
      */
    public static void remove(ImageFetchable src) {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        synchronized(info.waitList) {
            if (info.waitList.contains(src)) {
                info.waitList.removeElement(src);
            }
        }
    }

    /**
      * Checks to see if the given thread is one of the ImageFetchers.
      */
    public static boolean isFetcher(Thread t) {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        synchronized(info.waitList) {
            for (int i = 0; i < info.fetchers.length; i++) {
                if (info.fetchers[i] == t) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
      * Checks to see if the current thread is one of the ImageFetchers.
      */
    public static boolean amFetcher() {
        return isFetcher(Thread.currentThread());
    }

    /**
      * Returns the next ImageFetchable to be processed.  If TIMEOUT
      * elapses in the mean time, or if the ImageFetcher is interrupted,
      * null is returned.
      */
    private static ImageFetchable nextImage() {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        synchronized(info.waitList) {
            ImageFetchable src = null;
            long end = System.currentTimeMillis() + TIMEOUT;
            while (src == null) {
                while (info.waitList.size() == 0) {
                    long now = System.currentTimeMillis();
                    if (now >= end) {
                        return null;
                    }
                    try {
                        info.numWaiting++;
                        info.waitList.wait(end - now);
                    } catch (InterruptedException e) {
                        // A normal occurrence as an AppContext is disposed
                        return null;
                    } finally {
                        info.numWaiting--;
                    }
                }
                src = (ImageFetchable) info.waitList.elementAt(0);
                info.waitList.removeElement(src);
            }
            return src;
        }
    }

    /**
      * The main run() method of an ImageFetcher Thread.  Calls fetchloop()
      * to do the work, then removes itself from the array of ImageFetchers.
      */
    public void run() {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        try {
            fetchloop();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            synchronized(info.waitList) {
                Thread me = Thread.currentThread();
                for (int i = 0; i < info.fetchers.length; i++) {
                    if (info.fetchers[i] == me) {
                        info.fetchers[i] = null;
                        info.numFetchers--;
                    }
                }
            }
        }
    }

    /**
      * The main ImageFetcher loop.  Repeatedly calls nextImage(), and
      * fetches the returned ImageFetchable objects until nextImage()
      * returns null.
      */
    private void fetchloop() {
        Thread me = Thread.currentThread();
        while (isFetcher(me)) {
            // we're ignoring the return value and just clearing
            // the interrupted flag, instead of bailing out if
            // the fetcher was interrupted, as we used to,
            // because there may be other images waiting
            // to be fetched (see 4789067)
            me.interrupted();
            me.setPriority(HIGH_PRIORITY);
            ImageFetchable src = nextImage();
            if (src == null) {
                return;
            }
            try {
                src.doFetch();
            } catch (Exception e) {
                System.err.println("Uncaught error fetching image:");
                e.printStackTrace();
            }
            stoppingAnimation(me);
        }
    }


    /**
      * Recycles this ImageFetcher thread as an image animator thread.
      * Removes this ImageFetcher from the array of ImageFetchers, and
      * resets the thread name to "ImageAnimator".
      */
    static void startingAnimation() {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        Thread me = Thread.currentThread();
        synchronized(info.waitList) {
            for (int i = 0; i < info.fetchers.length; i++) {
                if (info.fetchers[i] == me) {
                    info.fetchers[i] = null;
                    info.numFetchers--;
                    me.setName("Image Animator " + i);
                    if(info.waitList.size() > info.numWaiting) {
                       createFetchers(info);
                    }
                    return;
                }
            }
        }
        me.setPriority(ANIM_PRIORITY);
        me.setName("Image Animator");
    }

    /**
      * Returns this image animator thread back to service as an ImageFetcher
      * if possible.  Puts it back into the array of ImageFetchers and sets
      * the thread name back to "Image Fetcher".  If there are already the
      * maximum number of ImageFetchers, this method simply returns, and
      * fetchloop() will drop out when it sees that this thread isn't one of
      * the ImageFetchers, and this thread will die.
      */
    private static void stoppingAnimation(Thread me) {
        final FetcherInfo info = FetcherInfo.getFetcherInfo();
        synchronized(info.waitList) {
            int index = -1;
            for (int i = 0; i < info.fetchers.length; i++) {
                if (info.fetchers[i] == me) {
                    return;
                }
                if (info.fetchers[i] == null) {
                    index = i;
                }
            }
            if (index >= 0) {
                info.fetchers[index] = me;
                info.numFetchers++;
                me.setName("Image Fetcher " + index);
                return;
            }
        }
    }

    /**
      * Create and start ImageFetcher threads in the appropriate ThreadGroup.
      */
    private static void createFetchers(final FetcherInfo info) {
       // We need to instantiate a new ImageFetcher thread.
       // First, figure out which ThreadGroup we'll put the
       // new ImageFetcher into
       final AppContext appContext = AppContext.getAppContext();
       ThreadGroup threadGroup = appContext.getThreadGroup();
       ThreadGroup fetcherThreadGroup;
       try {
          if (threadGroup.getParent() != null) {
             // threadGroup is not the root, so we proceed
             fetcherThreadGroup = threadGroup;
          } else {
             // threadGroup is the root ("system") ThreadGroup.
             // We instead want to use its child: the "main"
             // ThreadGroup.  Thus, we start with the current
             // ThreadGroup, and go up the tree until
             // threadGroup.getParent().getParent() == null.
             threadGroup = Thread.currentThread().getThreadGroup();
             ThreadGroup parent = threadGroup.getParent();
             while ((parent != null)
                  && (parent.getParent() != null)) {
                  threadGroup = parent;
                  parent = threadGroup.getParent();
             }
             fetcherThreadGroup = threadGroup;
         }
       } catch (SecurityException e) {
         // Not allowed access to parent ThreadGroup -- just use
         // the AppContext's ThreadGroup
         fetcherThreadGroup = appContext.getThreadGroup();
       }
       final ThreadGroup fetcherGroup = fetcherThreadGroup;

       java.security.AccessController.doPrivileged(
         new java.security.PrivilegedAction() {
         public Object run() {
             for (int i = 0; i < info.fetchers.length; i++) {
               if (info.fetchers[i] == null) {
                   ImageFetcher f = new ImageFetcher(
                           fetcherGroup, i);
                   try {
                       f.start();
                       info.fetchers[i] = f;
                       info.numFetchers++;
                       break;
                   } catch (Error e) {
                   }
               }
             }
          return null;
        }
       });
      return;
    }

}

/**
  * The FetcherInfo class encapsulates the per-AppContext ImageFetcher
  * information.  This includes the array of ImageFetchers, as well as
  * the queue of ImageFetchable objects.
  */
class FetcherInfo {
    static final int MAX_NUM_FETCHERS_PER_APPCONTEXT = 4;

    Thread[] fetchers;
    int numFetchers;
    int numWaiting;
    Vector waitList;

    private FetcherInfo() {
        fetchers = new Thread[MAX_NUM_FETCHERS_PER_APPCONTEXT];
        numFetchers = 0;
        numWaiting = 0;
        waitList = new Vector();
    }

    /* The key to put()/get() the FetcherInfo into/from the AppContext. */
    private static final Object FETCHER_INFO_KEY =
                                        new StringBuffer("FetcherInfo");

    static FetcherInfo getFetcherInfo() {
        AppContext appContext = AppContext.getAppContext();
        synchronized(appContext) {
            FetcherInfo info = (FetcherInfo)appContext.get(FETCHER_INFO_KEY);
            if (info == null) {
                info = new FetcherInfo();
                appContext.put(FETCHER_INFO_KEY, info);
            }
            return info;
        }
    }
}
