/*
 * 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.awt.image.*;
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.util.Hashtable;

public abstract class InputStreamImageSource implements ImageProducer,
                                                        ImageFetchable
{
    ImageConsumerQueue consumers;

    ImageDecoder decoder;
    ImageDecoder decoders;

    boolean awaitingFetch = false;

    abstract boolean checkSecurity(Object context, boolean quiet);

    int countConsumers(ImageConsumerQueue cq) {
        int i = 0;
        while (cq != null) {
            i++;
            cq = cq.next;
        }
        return i;
    }

    synchronized int countConsumers() {
        ImageDecoder id = decoders;
        int i = countConsumers(consumers);
        while (id != null) {
            i += countConsumers(id.queue);
            id = id.next;
        }
        return i;
    }

    public void addConsumer(ImageConsumer ic) {
        addConsumer(ic, false);
    }

    synchronized void printQueue(ImageConsumerQueue cq, String prefix) {
        while (cq != null) {
            System.out.println(prefix+cq);
            cq = cq.next;
        }
    }

    synchronized void printQueues(String title) {
        System.out.println(title+"[ -----------");
        printQueue(consumers, "  ");
        for (ImageDecoder id = decoders; id != null; id = id.next) {
            System.out.println("    "+id);
            printQueue(id.queue, "      ");
        }
        System.out.println("----------- ]"+title);
    }

    synchronized void addConsumer(ImageConsumer ic, boolean produce) {
        checkSecurity(null, false);
        for (ImageDecoder id = decoders; id != null; id = id.next) {
            if (id.isConsumer(ic)) {
                // This consumer is already being fed.
                return;
            }
        }
        ImageConsumerQueue cq = consumers;
        while (cq != null && cq.consumer != ic) {
            cq = cq.next;
        }
        if (cq == null) {
            cq = new ImageConsumerQueue(this, ic);
            cq.next = consumers;
            consumers = cq;
        } else {
            if (!cq.secure) {
                Object context = null;
                SecurityManager security = System.getSecurityManager();
                if (security != null) {
                    context = security.getSecurityContext();
                }
                if (cq.securityContext == null) {
                    cq.securityContext = context;
                } else if (!cq.securityContext.equals(context)) {
                    // If there are two different security contexts that both
                    // have a handle on the same ImageConsumer, then there has
                    // been a security breach and whether or not they trade
                    // image data is small fish compared to what they could be
                    // trading.  Throw a Security exception anyway...
                    errorConsumer(cq, false);
                    throw new SecurityException("Applets are trading image data!");
                }
            }
            cq.interested = true;
        }
        if (produce && decoder == null) {
            startProduction();
        }
    }

    public synchronized boolean isConsumer(ImageConsumer ic) {
        for (ImageDecoder id = decoders; id != null; id = id.next) {
            if (id.isConsumer(ic)) {
                return true;
            }
        }
        return ImageConsumerQueue.isConsumer(consumers, ic);
    }

    private void errorAllConsumers(ImageConsumerQueue cq, boolean needReload) {
        while (cq != null) {
            if (cq.interested) {
                errorConsumer(cq, needReload);
            }
            cq = cq.next;
        }
    }

    private void errorConsumer(ImageConsumerQueue cq, boolean needReload) {
        cq.consumer.imageComplete(ImageConsumer.IMAGEERROR);
        if ( needReload && cq.consumer instanceof ImageRepresentation) {
            ((ImageRepresentation)cq.consumer).image.flush();
        }
        removeConsumer(cq.consumer);
    }

    public synchronized void removeConsumer(ImageConsumer ic) {
        for (ImageDecoder id = decoders; id != null; id = id.next) {
            id.removeConsumer(ic);
        }
        consumers = ImageConsumerQueue.removeConsumer(consumers, ic, false);
    }

    public void startProduction(ImageConsumer ic) {
        addConsumer(ic, true);
    }

    private synchronized void startProduction() {
        if (!awaitingFetch) {
            if (ImageFetcher.add(this)) {
                awaitingFetch = true;
            } else {
                ImageConsumerQueue cq = consumers;
                consumers = null;
                errorAllConsumers(cq, false);
            }
        }
    }

    private synchronized void stopProduction() {
        if (awaitingFetch) {
            ImageFetcher.remove(this);
            awaitingFetch = false;
        }
    }

    public void requestTopDownLeftRightResend(ImageConsumer ic) {
    }

    protected abstract ImageDecoder getDecoder();

    protected ImageDecoder decoderForType(InputStream is,
                                          String content_type) {
        // Don't believe the content type - file extensions can
        // lie.
        /*
        if (content_type.equals("image/gif")) {
            return new GifImageDecoder(this, is);
        } else if (content_type.equals("image/jpeg")) {
            return new JPEGImageDecoder(this, is);
        } else if (content_type.equals("image/x-xbitmap")) {
            return new XbmImageDecoder(this, is);
        }
        else if (content_type == URL.content_jpeg) {
            return new JpegImageDecoder(this, is);
        } else if (content_type == URL.content_xbitmap) {
            return new XbmImageDecoder(this, is);
        } else if (content_type == URL.content_xpixmap) {
            return new Xpm2ImageDecoder(this, is);
        }
        */

        return null;
    }

    protected ImageDecoder getDecoder(InputStream is) {
        if (!is.markSupported())
            is = new BufferedInputStream(is);
        try {
          /* changed to support png
             is.mark(6);
             */
          is.mark(8);
            int c1 = is.read();
            int c2 = is.read();
            int c3 = is.read();
            int c4 = is.read();
            int c5 = is.read();
            int c6 = is.read();
            // added to support png
            int c7 = is.read();
            int c8 = is.read();
            // end of adding
            is.reset();
            is.mark(-1);

            if (c1 == 'G' && c2 == 'I' && c3 == 'F' && c4 == '8') {
                return new GifImageDecoder(this, is);
            } else if (c1 == '\377' && c2 == '\330' && c3 == '\377') {
                return new JPEGImageDecoder(this, is);
            } else if (c1 == '#' && c2 == 'd' && c3 == 'e' && c4 == 'f') {
                return new XbmImageDecoder(this, is);
//          } else if (c1 == '!' && c2 == ' ' && c3 == 'X' && c4 == 'P' &&
//                     c5 == 'M' && c6 == '2') {
//              return new Xpm2ImageDecoder(this, is);
                // added to support png
            } else if (c1 == 137 && c2 == 80 && c3 == 78 &&
                c4 == 71 && c5 == 13 && c6 == 10 &&
                c7 == 26 && c8 == 10) {
                return new PNGImageDecoder(this, is);
            }
            // end of adding
        } catch (IOException e) {
        }

        return null;
    }

    public void doFetch() {
        synchronized (this) {
            if (consumers == null) {
                awaitingFetch = false;
                return;
            }
        }
        ImageDecoder imgd = getDecoder();
        if (imgd == null) {
            badDecoder();
        } else {
            setDecoder(imgd);
            try {
                imgd.produceImage();
            } catch (IOException e) {
                e.printStackTrace();
                // the finally clause will send an error.
            } catch (ImageFormatException e) {
                e.printStackTrace();
                // the finally clause will send an error.
            } finally {
                removeDecoder(imgd);
                if ( Thread.currentThread().isInterrupted() || !Thread.currentThread().isAlive()) {
                    errorAllConsumers(imgd.queue, true);
                } else {
                    errorAllConsumers(imgd.queue, false);
            }
        }
    }
    }

    private void badDecoder() {
        ImageConsumerQueue cq;
        synchronized (this) {
            cq = consumers;
            consumers = null;
            awaitingFetch = false;
        }
        errorAllConsumers(cq, false);
    }

    private void setDecoder(ImageDecoder mydecoder) {
        ImageConsumerQueue cq;
        synchronized (this) {
            mydecoder.next = decoders;
            decoders = mydecoder;
            decoder = mydecoder;
            cq = consumers;
            mydecoder.queue = cq;
            consumers = null;
            awaitingFetch = false;
        }
        while (cq != null) {
            if (cq.interested) {
                // Now that there is a decoder, security may have changed
                // so reverify it here, just in case.
                if (!checkSecurity(cq.securityContext, true)) {
                    errorConsumer(cq, false);
                }
            }
            cq = cq.next;
        }
    }

    private synchronized void removeDecoder(ImageDecoder mydecoder) {
        doneDecoding(mydecoder);
        ImageDecoder idprev = null;
        for (ImageDecoder id = decoders; id != null; id = id.next) {
            if (id == mydecoder) {
                if (idprev == null) {
                    decoders = id.next;
                } else {
                    idprev.next = id.next;
                }
                break;
            }
            idprev = id;
        }
    }

    synchronized void doneDecoding(ImageDecoder mydecoder) {
        if (decoder == mydecoder) {
            decoder = null;
            if (consumers != null) {
                startProduction();
            }
        }
    }

    void latchConsumers(ImageDecoder id) {
        doneDecoding(id);
    }

    synchronized void flush() {
        decoder = null;
    }
}