/*
 * Copyright (c) 1997 Bernhard Fischer, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes only and
 * without fee is hereby granted.
 */

// created 97/12/01 Bernhard Fischer : initial version


import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.util.Date;


public class Panorama extends Applet implements Runnable
{
    private int appWidth;
    private int appHeight;

    private Image drwImage = null;

    private String S_IMAGE          = "image";
    private String S_SCALE_NATIVE   = "scaleNative";

    public Panorama()
    {
    }


    public String getAppletInfo() {
        return
            "Panorama Applet - Version 1.1\n" +
            "Copyright © 1997 Bernhard Fischer\n" +
            "All Rights Reserved.";
    }


    private String parameterInfo[][] = {
        { S_IMAGE,          "image",        "URL of the image file"         },
        { S_SCALE_NATIVE,   "boolean",      "use library functionality"     },
    };

    public String[][] getParameterInfo() {
        return parameterInfo;
    }


    public void init()
    {
        appWidth  = size().width;
        appHeight = size().height;

        new Thread(this, "init").start();
    }


    void setCursor(int cursorType)
    {
        Component c = this;
        while (! (c instanceof Frame) && c != null)
            c = c.getParent();
        Frame f = (Frame) c;
        if (f != null)
            f.setCursor(cursorType);
    }


    public void run()
    {
        System.out.println("run()");

        String name = Thread.currentThread().getName();

        if (name.equals("init"))
            runInit();
        else
        if (name.equals("turn"))
            runTurn();

        System.out.println("run() finished");
    }


    private void runInit()
    {
        System.out.println("runInit()");

        runLoad();
        runScale();
        startTurn(50, 1);

        System.out.println("runInit() finished");
    }


    private void runLoad()
    {
        System.out.println("runLoad()");

        String image = getParameter(S_IMAGE);
        if (image == null) {
            setStatus("missing parameter");
            return;
        }

        setStatus("Loading image...");
        setCursor(Frame.WAIT_CURSOR);
        drwImage = getImage(getCodeBase(), image);
        MediaTracker tracker = new MediaTracker(this);
        tracker.addImage(drwImage, 0);
        try {
            tracker.waitForAll();
        }
        catch (InterruptedException ex) {
            System.out.println("internal error");
        }

        System.out.println("runLoad() finished");
    }


    private void runScale()
    {
        System.out.println("runScale()");

        boolean scaleNative = false;
        String scaleNativeStr = getParameter(S_SCALE_NATIVE);
        if (scaleNativeStr != null)
            scaleNative = Boolean.valueOf(scaleNativeStr).booleanValue();

        setStatus("Scaling image...");
        setCursor(Frame.WAIT_CURSOR);
        double factor = ((double) appHeight) / ((double) drwImage.getHeight(this));
        if (scaleNative)
            drwImage = scaleImage(drwImage, factor);
        else
            drwImage = scaleImageSliced(drwImage, factor);

        setStatus(null);
        setCursor(Frame.MOVE_CURSOR);
        repaint();

        System.out.println("runScale() finished");
    }


    private Image scaleImage(Image drwImage, double factor)
    {
        System.out.println("scaleImage()");
        int imgWidth  = drwImage.getWidth(this);
        int imgHeight = drwImage.getHeight(this);

        // create scaled image
        int scaWidth  = (int) Math.round(factor * (double) imgWidth );
        int scaHeight = (int) Math.round(factor * (double) imgHeight);
        Image scaImage = createImage(scaWidth, scaHeight);

         // draw the image
        Graphics scaGraphics = scaImage.getGraphics();
        scaGraphics.drawImage(drwImage, 0, 0, scaWidth, scaHeight, this);

         // wait for image drawing
        MediaTracker tracker = new MediaTracker(this);
        tracker.addImage(scaImage, 0, scaWidth, scaHeight);
        try {
            tracker.waitForAll();
        }
        catch (InterruptedException e) {
            System.out.println("InterruptedException");
        }

        System.out.println("scaleImage() finished");
        return scaImage;
    }


    private Image scaleImageSliced(Image drwImage, double factor) {
        System.out.println("scaleImage()");
        if (factor == 1.0)
            return drwImage;
        int imgWidth  = drwImage.getWidth(this);
        int imgHeight = drwImage.getHeight(this);
        int scaHeight = (int) Math.round((double) imgHeight * factor);
        int scaWidth  = (int) Math.round((double) imgWidth  * factor);
        Image scaImage = createImage(scaWidth, scaHeight);
        Graphics scaGraphics = scaImage.getGraphics();
        int widthBlockSize = 64; // 64;
        int scaPixWidth = (int) Math.round((double) widthBlockSize * factor);
        for (int toXX = 0; toXX < scaWidth; toXX += scaPixWidth) {
            double progress = (double) toXX / (double) scaWidth * (double) 100;
            setStatus("Scaling image..." + " (" + Math.round(progress) + "%)");
            if (toXX + scaPixWidth >= scaWidth)
                scaPixWidth = scaWidth - toXX;
            int fromXX = (int) Math.round((double) toXX / factor);
            int fromWidth = (int) Math.round((double) scaPixWidth / factor);
            int drwPix[] = new int[fromWidth * imgHeight];
            PixelGrabber pixelGrabber =
                new PixelGrabber(drwImage.getSource(),
                    fromXX, 0, fromWidth, imgHeight,
                    drwPix, 0, fromWidth);
            try {
                pixelGrabber.grabPixels();
            }
            catch (InterruptedException e) {
                System.out.println("InterruptedException");
            }
            int scaPix[] = new int[scaPixWidth * scaHeight];
            for (int toX = 0; toX < scaPixWidth; toX++) {
                int fromX = (int) Math.round((double) toX / factor);
                for (int toY = 0; toY < scaHeight; toY++) {
                    int fromY = (int) Math.round((double) toY / factor);
                    scaPix[toY * scaPixWidth + toX] = drwPix[fromY * fromWidth + fromX];
                }
            }
            Image pixImage = createImage(
                new MemoryImageSource(scaPixWidth, scaHeight,
                                      scaPix, 0, scaPixWidth));
            scaGraphics.drawImage(pixImage, toXX, 0, null);
            MediaTracker tracker = new MediaTracker(this);
            tracker.addImage(pixImage, 0, scaPixWidth, scaHeight);
            try {
                tracker.waitForAll();
            }
            catch (InterruptedException e) {
                System.out.println("InterruptedException");
            }
        }
        System.out.println("scaleImage() finished");
        return scaImage;
    }


    public void update(Graphics g)
    {
        paint(g);
    }


    private int v;

    private String msg = null;
    private final int BORDER_WIDTH = 8;

    public void paint(Graphics g)
    {
        int v;
        synchronized(this) {
            v = this.v;
        }

        if (drwImage != null && drwImage.getWidth(this) > 0) {
            g.clipRect(0, 0, appWidth, appHeight);
            int drwImageWidth = drwImage.getWidth(this);
            for (int offset = 0; offset - v < appWidth; offset += drwImageWidth) {
                g.drawImage(drwImage, offset - v, 0, this);
            }
        }

        if (msg != null) {
            Dimension d = this.size();
            FontMetrics fm = getFontMetrics(this.getFont());
            int width  = fm.stringWidth(msg);
            int height = fm.getHeight();
            int x = (d.width  - width ) / 2;
            int y = (d.height + height) / 2;
            int bx = x - BORDER_WIDTH;
            int by = y - height - BORDER_WIDTH;
            int bw = width  + 2 * BORDER_WIDTH;
            int bh = height + 2 * BORDER_WIDTH;
            g.clearRect(bx, by, bw, bh);
            g.drawRect(bx, by, bw, bh);
            g.drawString(msg, x, y);
        }
    }


    private void setStatus(String msg)
    {
        this.msg = msg;
        repaint();
    }


    private int x0;
    private int y0;
    private int v0;
    private int d0 = 0;

    public boolean mouseDown(Event event, int i, int j)
    {
        System.out.println("mouseDown");
        d0 = delay;
        startTurn(0, 0);
        v0 = v;
        x0 = i;
        y0 = j;
        return true;
    }


    public boolean mouseDrag(Event event, int i, int j)
    {
        System.out.println("mouseDrag");
        if (drwImage != null) {
            int drwImageWidth = drwImage.getWidth(this);
            if (drwImageWidth != 0) {
                synchronized(this) {
                    v = (v0 - (i - x0) % drwImageWidth + drwImageWidth) % drwImageWidth;
                }
                repaint();
            }
        }
        return true;
    }


    private final int MIN_DELAY = 5;

    public boolean mouseUp(Event event, int i, int j)
    {
        System.out.println("mouseUp");
        if (Math.abs(x0 - i) < 10 && d0 == 0) {
            double speed = - (double) (appWidth - 2 * i) / (double) appWidth;
            int offset = (speed >= 0) ? 1 : -1;
            int delay = Math.abs((Math.abs(speed) > (double) 0.01) ? (int) Math.round((double) MIN_DELAY / speed) : 0);
            startTurn(delay, offset);
        }
        repaint();
        return true;
    }


    private Thread turnThread = null;
    private int delay  = 0;
    private int offset = 0;

    private void startTurn(int delay, int offset)
    {
        System.out.println("startTurn()");

        this.delay  = delay;
        this.offset = offset;

        if (delay == 0) {
            if (turnThread != null)
                turnThread.stop();
            turnThread = null;
        }
        else
        if (turnThread == null) {
            System.out.println("starting turn thread...");
            turnThread = new Thread(this, "turn");
            turnThread.start();
        }

        System.out.println("startTurn() finished");
    }


    private void runTurn()
    {
        System.out.println("runTurn()");

        int drwImageWidth = drwImage.getWidth(this);
        while (true) {
            try {
                turnThread.sleep(delay);
                if (drwImageWidth != 0) {
                    v = (v + offset + drwImageWidth) % drwImageWidth;
                    repaint();
                }
            }
            catch (InterruptedException e) {
                System.out.println("InterruptedException");
            }
        }

        // System.out.println("runTurn() finished");
    }
}
