/*****************************************************************************
 * 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.swing;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;

import org.apache.batik.test.DefaultTestReport;
import org.apache.batik.test.Test;
import org.apache.batik.test.TestReport;


import org.apache.batik.bridge.ScriptingEnvironment;
import org.apache.batik.bridge.UpdateManager;
import org.apache.batik.bridge.UpdateManagerEvent;
import org.apache.batik.bridge.UpdateManagerListener;
import org.apache.batik.script.Interpreter;
import org.apache.batik.script.InterpreterException;
import org.apache.batik.util.RunnableQueue;

import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;

/**
 * One line Class Desc
 *
 * Complete Class Desc
 *
 * @author <a href="mailto:deweese@apache.org>l449433</a>
 * @version $Id: JSVGCanvasHandler.java,v 1.5 2003/07/10 02:01:27 deweese Exp $
 */
public class JSVGCanvasHandler {

    public interface Delegate {
        public String getName();
        // Returns true if a load event was triggered.  In this case
        // the handler will wait for the load event to complete/fail.
        public boolean canvasInit(JSVGCanvas canvas);
        public void canvasLoaded(JSVGCanvas canvas);
        public void canvasRendered(JSVGCanvas canvas);
        public boolean canvasUpdated(JSVGCanvas canvas);
        public void canvasDone(JSVGCanvas canvas);
        public void failure(TestReport report);
    }
    
    public static final String REGARD_TEST_INSTANCE = "regardTestInstance";
    public static final String REGARD_START_SCRIPT = 
        "try { regardStart(); } catch (er) {}";

    /**
     * Error when canvas can't load SVG file.
     * {0} The file/url that could not be loaded.
     */
    public static final String ERROR_CANNOT_LOAD_SVG = 
        "JSVGCanvasHandler.message.error.could.not.load.svg";

    /**
     * Error when canvas can't render SVG file.
     * {0} The file/url that could not be rendered.
     */
    public static final String ERROR_SVG_RENDER_FAILED = 
        "JSVGCanvasHandler.message.error.svg.render.failed";

    /**
     * Error when canvas can't peform render update SVG file.
     * {0} The file/url that could not be updated..
     */
    public static final String ERROR_SVG_UPDATE_FAILED = 
        "JSVGCanvasHandler.message.error.svg.update.failed";

    /**
     * Entry describing the error
     */
    public static final String ENTRY_KEY_ERROR_DESCRIPTION 
        = "JSVGCanvasHandler.entry.key.error.description";

    public static String fmt(String key, Object []args) {
        return TestMessages.formatMessage(key, args);
    }

    JFrame     frame = null;
    JSVGCanvas canvas = null;
    UpdateManager updateManager = null;
    WindowListener wl = null;
    InitialRenderListener irl = null;
    LoadListener ll = null;
    UpdateRenderListener url = null;
    
    boolean failed;
    boolean abort = false;
    boolean done  = false;
    Object loadMonitor = new Object();
    Object renderMonitor = new Object();
    
    Delegate delegate;
    Test host;
    String desc;

    public JSVGCanvasHandler(Test host, Delegate delegate) {
        this.host     = host;
        this.delegate = delegate;
    }

    public JFrame getFrame()     { return frame; }
    public JSVGCanvas getCanvas() { return canvas; }

    public JSVGCanvas createCanvas() { return new JSVGCanvas(); }

    public void runCanvas(String desc) {
        this.desc = desc;

        setupCanvas();

        if ( abort) return;

        try {
            synchronized (renderMonitor) {
                synchronized (loadMonitor) {
                    if (delegate.canvasInit(canvas)) {
                        checkLoad();
                    }
                }

                if ( abort) return;

                checkRender();
                if ( abort) return;

                try {
                    EventQueue.invokeAndWait(new Runnable() {
                            public void run() {
                                updateManager = canvas.getUpdateManager();
                                if (updateManager == null)
                                    return;
                                url = new UpdateRenderListener();
                                updateManager.addUpdateManagerListener(url);
                            }});
                } catch (Throwable t) { t.printStackTrace(); }
                if ( abort) return;

                if (updateManager == null)
                    return;

                bindHost();

                if ( abort) return;

                while (!done) {
                    checkUpdate();
                    if ( abort) return;
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            delegate.canvasDone(canvas);
            dispose();
        }
    }

    public void setupCanvas() { 
        try {
            EventQueue.invokeAndWait(new Runnable() {
                    public void run() {
                        frame = new JFrame(delegate.getName());
                        canvas = createCanvas();
                        canvas.setPreferredSize(new Dimension(450, 500));
                        frame.getContentPane().add(canvas);
                        frame.pack();
                        wl = new WindowAdapter() {
                                public void windowClosing(WindowEvent e) {
                                    synchronized (loadMonitor) {
                                        abort = true;
                                        loadMonitor.notifyAll();
                                    }
                                    synchronized (renderMonitor) {
                                        abort = true;
                                        renderMonitor.notifyAll();
                                    }
                                }
                            };
                        frame.addWindowListener(wl);
                        frame.setVisible(true);

                        irl = new InitialRenderListener();
                        canvas.addGVTTreeRendererListener(irl);
                        ll = new LoadListener();
                        canvas.addSVGDocumentLoaderListener(ll);
                    }});
        } catch (Throwable t) { 
            t.printStackTrace();
        }
    }


    public void scriptDone() {
        Runnable r = new Runnable() {
                public void run() {
                    synchronized(renderMonitor) {
                        done = true;
                        failed = false;
                        renderMonitor.notifyAll();
                    }
                }
            };
        if (updateManager == null) {
            // Don't run it in this thread or we deadlock the event queue.
            Thread t = new Thread(r);
            t.start();
        } else {
            updateManager.getUpdateRunnableQueue().invokeLater(r);
        }
    }
    
    public void dispose() {
        if (frame != null) {
            frame.removeWindowListener(wl);
            frame.setVisible(false);
        }
        wl = null;
        if (canvas != null) {
            canvas.removeGVTTreeRendererListener(irl);  irl=null;
            canvas.removeSVGDocumentLoaderListener(ll); ll=null;
            canvas.removeUpdateManagerListener(url);    url=null;
        }
        updateManager = null;
        canvas = null;
        frame = null;
    }

    public void checkSomething(Object monitor, String errorCode) {
        synchronized (monitor) {
            failed = true;
            try { monitor.wait(); }
            catch(InterruptedException ie) { /* nothing */ }
            if (abort || failed) {
                DefaultTestReport report = new DefaultTestReport(host);
                report.setErrorCode(errorCode);
                report.setDescription(new TestReport.Entry[] { 
                    new TestReport.Entry
                    (fmt(ENTRY_KEY_ERROR_DESCRIPTION, null),
                     fmt(errorCode, new Object[]{desc}))
                });
                report.setPassed(false);
                delegate.failure(report);
                done = true;
                return;
            }
        }
    }

    public void checkLoad() {
        checkSomething(loadMonitor, ERROR_CANNOT_LOAD_SVG);
        delegate.canvasLoaded(canvas);
    }

    public void checkRender() {
        checkSomething(renderMonitor, ERROR_SVG_RENDER_FAILED);
        delegate.canvasRendered(canvas);
    }

    public void checkUpdate() {
        checkSomething(renderMonitor, ERROR_SVG_UPDATE_FAILED);
        if (!done)
            done = delegate.canvasUpdated(canvas);
    }

    public void bindHost() {
        RunnableQueue rq;
        rq = updateManager.getUpdateRunnableQueue();
        rq.invokeLater(new Runnable() {
                UpdateManager um = updateManager;
                public void run() {
                    ScriptingEnvironment scriptEnv;
                    scriptEnv = um.getScriptingEnvironment();
                    Interpreter interp;
                    interp    = scriptEnv.getInterpreter();
                    interp.bindObject(REGARD_TEST_INSTANCE, 
                                      host);
                    try {
                        interp.evaluate(REGARD_START_SCRIPT);
                    } catch (InterpreterException ie) {
                        // Could not wait if no start script.
                    }
                }
            });
    }
    class UpdateRenderListener implements UpdateManagerListener {
        public void updateCompleted(UpdateManagerEvent e) {
            synchronized(renderMonitor){
                failed = false;
                renderMonitor.notifyAll();
            }
        }
        public void updateFailed(UpdateManagerEvent e) {
            synchronized(renderMonitor){
                renderMonitor.notifyAll();
            }
        }
        public void managerStarted(UpdateManagerEvent e) { }
        public void managerSuspended(UpdateManagerEvent e) { }
        public void managerResumed(UpdateManagerEvent e) { }
        public void managerStopped(UpdateManagerEvent e) { }
        public void updateStarted(UpdateManagerEvent e) { }
    }

    class InitialRenderListener extends GVTTreeRendererAdapter {
        public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
            synchronized(renderMonitor){
                failed = false;
                renderMonitor.notifyAll();
            }
        }


        public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
            synchronized(renderMonitor){
                renderMonitor.notifyAll();
            }
        }

        public void gvtRenderingFailed(GVTTreeRendererEvent e) {
            synchronized(renderMonitor){
                renderMonitor.notifyAll();
            }
        }
    }

    class LoadListener extends SVGDocumentLoaderAdapter {
        public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
            synchronized(loadMonitor){
                failed = false;
                loadMonitor.notifyAll();
            }
        }

        public void documentLoadingFailed(SVGDocumentLoaderEvent e) {
            synchronized(loadMonitor){
                loadMonitor.notifyAll();
            }
        }

        public void documentLoadingCancelled(SVGDocumentLoaderEvent e) {
            synchronized(loadMonitor){
                loadMonitor.notifyAll();
            }
        }
    }
}
