/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.apps.slideshow;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JWindow;

import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.bridge.ViewBox;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.renderer.StaticRenderer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGDocument;

public class Main extends JComponent {

    StaticRenderer renderer;
    UserAgent      userAgent;
    DocumentLoader loader;
    BridgeContext  ctx;

    BufferedImage image;
    BufferedImage display;
    File [] files;

    static int duration = 3000;
    static int frameDelay = duration+7000;

    boolean done = false;

    public Main(File []files, Dimension size) { 
        setBackground(Color.black);
        this.files = files;
        renderer = new StaticRenderer();
        userAgent = new UserAgentAdapter();
        loader    = new DocumentLoader(userAgent);
        ctx       = new BridgeContext(userAgent, loader);

        boolean frameless = false;
        if (size == null) {
            size = Toolkit.getDefaultToolkit().getScreenSize();
            frameless = true;
        }

        setPreferredSize(size);
        setDoubleBuffered(false);
        addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent me) {
                    if (done)
                        System.exit(0);
                    else
                        togglePause();
                }
            });

        size.width += 2;
        size.height += 2;
        display = new BufferedImage(size.width, size.height, 
                                    BufferedImage.TYPE_INT_BGR);

        Thread t = new RenderThread();
        t.start();

        JWindow w = new JWindow();
        w.setBackground(Color.black);
        w.getContentPane().setBackground(Color.black);
        w.getContentPane().add(this);
        w.pack();
        w.setLocation(new Point(-1, -1));
        w.setVisible(true);
    }

    class RenderThread extends Thread {
        public void run() {
            renderer.setDoubleBuffered(true);
            for (int i=0; i<files.length; i++) {
                GraphicsNode   gvtRoot = null;
                GVTBuilder builder = new GVTBuilder();

                try {
                    System.out.println("Reading: " + files[i]);
                    Document svgDoc = loader.loadDocument
                        (files[i].toURL().toString());
                    System.out.println("Building: " + files[i]);
                    gvtRoot = builder.build(ctx, svgDoc);
                    System.out.println("Rendering: " + files[i]);
                    renderer.setTree(gvtRoot);

                    Element elt = ((SVGDocument)svgDoc).getRootElement();
                    renderer.setTransform
                        (ViewBox.getViewTransform
                         (null, elt, display.getWidth(), display.getHeight()));

                    renderer.updateOffScreen(display.getWidth(), 
                                             display.getHeight());

                    Rectangle r = new Rectangle(0, 0,
                                                display.getWidth(), 
                                                display.getHeight());
                    renderer.repaint(r);
                    System.out.println("Painting: " + files[i]);
                    image = renderer.getOffScreen();
                    setTransition(image);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            
            }
            if (transitionThread != null) {
                try {
                    transitionThread.join();
                } catch (InterruptedException ie) { }
                done = true;
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
            }
        }
    }

    Thread transitionThread = null;

    public void setTransition(BufferedImage newImg) {
        synchronized (this) {
            while (transitionThread != null) {
                try {
                    wait();
                } catch (InterruptedException ie) { }
            }
            transitionThread = new TransitionThread(newImg);
            transitionThread.start();
        }
    }


    long   startLastTransition=0;

    boolean paused = false;

    public void togglePause() {
        synchronized(this) {
            paused = !paused;
            Cursor c;
            if (paused) {
                c = new Cursor(Cursor.WAIT_CURSOR);
            } else {
                c = new Cursor(Cursor.DEFAULT_CURSOR);
                if (transitionThread != null) {
                    synchronized (transitionThread) {
                        transitionThread.notifyAll();
                    }
                }
            }
            setCursor(c);
        }
    }

    class TransitionThread extends Thread {
        BufferedImage src;
        int blockw = 75;
        int blockh = 75;

        public TransitionThread(BufferedImage bi) {
            src = bi;
        }

        public void run() {
            int xblocks = (display.getWidth()+blockw-1)/blockw;
            int yblocks = (display.getHeight()+blockh-1)/blockh;
            int nblocks = xblocks*yblocks;

            int tblock = duration/nblocks;
            
            Point [] rects = new Point[nblocks];
            for (int y=0; y<yblocks; y++)
                for (int x=0; x<xblocks; x++)
                    rects[y*xblocks+x] = new Point(x, y);

            Graphics2D g2d = display.createGraphics();
            g2d.setColor(new Color(0,0,0));

            long currTrans = System.currentTimeMillis();
            while ((currTrans-startLastTransition) < frameDelay) {
                try {
                    long stime = frameDelay-(currTrans-startLastTransition);
                    if (stime > 500) {
                        System.gc();
                        currTrans = System.currentTimeMillis();
                        stime = frameDelay-(currTrans-startLastTransition);
                    }
                    if (stime > 0) sleep(stime);
                } catch (InterruptedException ie) { }
                currTrans = System.currentTimeMillis();
            }

            synchronized(this) {
                while (paused) {
                    try {
                        wait();
                    } catch (InterruptedException ie) { }
                }
            }

            long last = startLastTransition = System.currentTimeMillis();
            
            for (int i=0; i<rects.length; i++) {
                int idx = (int)(Math.random()*(rects.length-i));
                Point pt = rects[idx];
                for (int j=idx+1; j<rects.length-i;j++)
                    rects[j-1] = rects[j];
                int x=pt.x*blockw, y=pt.y*blockh;
                int w=blockw, h = blockh;
                if (x+w > src.getWidth())  w = src.getWidth()-x;
                if (y+h > src.getHeight()) h = src.getHeight()-y;

                synchronized (display) {
                    g2d.fillRect(x, y, w, h);
                    BufferedImage sub;

                    sub = src.getSubimage(x, y, w, h);
                    g2d.drawImage(sub, null, x, y);
                }

                repaint(x, y, w, h);
                long current = System.currentTimeMillis();
                try {
                    long dt = current-last;
                    if (dt < tblock)
                        sleep(tblock-dt);
                } catch (InterruptedException ie) { }
                last = current;
            }

            synchronized (Main.this) {
                transitionThread = null;
                Main.this.notifyAll();
            }
        }
    }

    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        if (display == null) return;
        // System.out.println("Drawing Image: " + display);
        g2d.drawImage(display, null, 0, 0);
    }

    public static void readFileList(String file, Vector fileVec) {
        BufferedReader br;
        try {
            br = new BufferedReader(new FileReader(file));
        } catch(FileNotFoundException fnfe) {
            System.err.println("Unable to open file-list: " + file);
            return;
        }
        try {
            URL flURL = new File(file).toURL();
            String line;
            while ((line = br.readLine()) != null) {
                String str = line;
                int idx = str.indexOf('#');
                if (idx != -1)
                    str = str.substring(0, idx);
                str.trim();
                if (str.length() == 0)
                    continue;
                try {
                    URL imgURL = new URL(flURL, str);
                    fileVec.add(imgURL.getFile());
                } catch (MalformedURLException mue) {
                    System.err.println("Can't make sense of line:\n  " + line);
                }
            }
        } catch (IOException ioe) {
            System.err.println("Error while reading file-list: " + file);
        }
    }

    public static void main(String []args) {

        Vector fileVec = new Vector();

        Dimension d = null;
        
        if (args.length == 0) {
            showUsage();
            return;
        }

        for (int i=0; i<args.length; i++) {
            if ((args[i].equals("-h")) ||
                (args[i].equals("-help")) ||
                (args[i].equals("--help"))){
                showUsage();
                return;
            } else if (args[i].equals("--")) {
                i++;
                while(i < args.length) {
                    fileVec.add(args[i++]);
                }
                break;
            } else if ((args[i].equals("-fl"))||
                     (args[i].equals("--file-list"))) {
                if (i+1 == args.length) {
                    System.err.println
                        ("Must provide name of file list file after " + 
                         args[i]);
                    break;
                }
                readFileList(args[i+1], fileVec);
                i++;
            } else if ((args[i].equals("-ft"))||
                       (args[i].equals("--frame-time"))) {
                if (i+1 == args.length) {
                    System.err.println
                        ("Must provide time in millis after " + args[i]);
                    break;
                }
                try {
                    frameDelay = Integer.decode(args[i+1]).intValue();
                    i++;
                } catch (NumberFormatException nfe) {
                    System.err.println
                        ("Can't parse frame time: " + args[i+1]);
                }
            } else if ((args[i].equals("-tt"))||
                       (args[i].equals("--transition-time"))) {
                if (i+1 == args.length) {
                    System.err.println
                        ("Must provide time in millis after " + args[i]);
                    break;
                }
                try {
                    duration = Integer.decode(args[i+1]).intValue();
                    i++;
                } catch (NumberFormatException nfe) {
                    System.err.println
                        ("Can't parse transition time: " + args[i+1]);
                }                
            } else if ((args[i].equals("-ws"))||
                       (args[i].equals("--window-size"))) {
                
                if (i+1 == args.length) {
                    System.err.println
                        ("Must provide window size [w,h] after " + args[i]);
                    break;
                }
                try {
                    int idx = args[i+1].indexOf(',');
                    int w, h;
                    if (idx == -1)
                        w = h = Integer.decode(args[i+1]).intValue();
                    else {
                        String wStr = args[i+1].substring(0,idx);
                        String hStr = args[i+1].substring(idx+1);
                        w = Integer.decode(wStr).intValue();
                        h = Integer.decode(hStr).intValue();
                    }
                    d = new Dimension(w, h);
                    i++;
                } catch (NumberFormatException nfe) {
                    System.err.println
                        ("Can't parse window size: " + args[i+1]);
                }
            } else
                fileVec.add(args[i]);
        }
            
        File [] files = new File[fileVec.size()];


        for (int i=0; i<fileVec.size(); i++) {
            try {
                files[i] = new File((String)fileVec.get(i));
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

        new Main(files, d);
    }

    public static void showUsage() {
        System.out.println
("Options:\n" +
 "                                 -- : Remaining args are file names\n" +
 "                         -fl <file>\n" +
 "                 --file-list <file> : file contains list of images to\n" +
 "                                      show one per line\n" +
 "             -ws <width>[,<height>]\n" +
 "    -window-size <width>[,<height>] : Set the size of slideshow window\n" +
 "                                      defaults to full screen\n" +
 "                          -ft <int>\n" +
 "                 --frame-time <int> : Amount of time in millisecs to\n" +
 "                                      show each frame.\n" +
 "                                      Includes transition time.\n" +
 "                          -tt <int>\n" +
 "            --transition-time <int> : Amount of time in millisecs to\n" +
 "                                      transition between frames.\n" +
 "                             <file> : SVG file to display");
    }

}
