/*
 * @(#)ScriptGenerator.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.uicapture.v1;

import java.awt.Rectangle;

import java.io.File;
import java.io.IOException;

import net.sourceforge.groboutils.uicapture.v1.event.ICaptureListener;
import net.sourceforge.groboutils.uicapture.v1.event.CaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseWheelCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseMovedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseButtonCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MousePressedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseReleasedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.KeyCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.KeyPressedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.KeyReleasedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.IAllowCapturePassThroughListener;


/**
 * Listens to CaptureEvents, and converts these into script commands to the
 * IScriptMaker.  It allows for easy subclassing through having a "Meta-Mode".
 * All events will be swallowed (not passed to the UI or IScriptMaker)
 * while in this meta-mode.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   Jan 7, 2002
 */
public class ScriptGenerator implements ICaptureListener,
        IAllowCapturePassThroughListener
{
    private IScriptMaker maker = null;
    private boolean metaMode = false;
    private long lastEventTime = -1;
    private VirtualWindow window = null;
    private IScreenScraper ss = null;
    private IFocusedWindowFinder fwf = null;
    private String baseImageName = null;
    private int filenameIndex = 0;
    private boolean isRunning = false;
    
    
    //-------------------------------------------------------------------------
    // Constructors
    
    /**
     * Uses the Default version for the IScreenScraper and IFocusedWindowFinder.
     */
    public ScriptGenerator( IScriptMaker maker, String baseImageName )
    {
        this( new DefaultScreenScraper(), new DefaultFocusedWindowFinder(),
            maker, baseImageName );
    }
    
    
    /**
     * Create using the given arguments to build the framework.
     */
    public ScriptGenerator( IScreenScraper ss,
            IFocusedWindowFinder fwf, IScriptMaker maker, String baseImageName )
    {
        this.ss = ss;
        this.fwf = fwf;
        this.maker = maker;
        this.baseImageName = baseImageName;
    }

    
    //-------------------------------------------------------------------------
    // Public Methods
    
    
    /*
     * Should this be here ????
    public VirtualWindow getVirtualWindow()
    {
        return this.window;
    }
     */

    /**
     * 
     * @exception java.awt.AWTException Robot isn't supported.
     */
    public void begin()
            throws java.awt.AWTException
    {
        this.window = new VirtualWindow( null, true );
        this.window.addCaptureListener( this );
        this.maker.start();
        this.isRunning = true;
    }
    
    
    public void end()
    {
        this.isRunning = false;
        fireStop();
    }
    
    
    public boolean isRunning()
    {
        return this.isRunning;
    }
     
     


    //-------------------------------------------------------------------------
    // Meta Methods
    
    /**
     * 
     */
    public boolean isInMetaMode()
    {
        return this.metaMode;
    }
    
    
    /**
     * 
     */
    public void setInMetaMode( boolean set )
    {
        this.metaMode = set;
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaMouseWheelMoved( MouseWheelCaptureEvent ce )
    {
        // do nothing
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaMousePressed( MousePressedCaptureEvent ce )
    {
        // do nothing
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaMouseReleased( MouseReleasedCaptureEvent ce )
    {
        // do nothing
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaKeyPressed( KeyPressedCaptureEvent ce )
    {
        // do nothing
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaKeyReleased( KeyReleasedCaptureEvent ce )
    {
        // do nothing
    }
    
    
    /**
     * Performs a meta-mode action when this event is caught and the
     * generator is in meta-mode.  Default action does nothing.
     */
    protected void metaMouseMoved( MouseMovedCaptureEvent ce )
    {
        // do nothing
    }

    
    //-------------------------------------------------------------------------
    // Fire ScriptMaker Event Methods
    
    
    /**
     * 
     */
    protected synchronized void fireStop()
    {
        if (this.window != null)
        {
            this.window.removeCaptureListener( this );
            
            if (isRunning())
            {
                this.maker.end();
            }
            
            this.window.dispose();
            this.window = null;
        }
    }
    
    
    /**
     * Saves the entire screen to a file.
     *
     * @param filename the file to save the image as.
     */
    protected synchronized void fireSaveScreen()
    {
        if (isRunning())
        {
            fireSaveScreenImage( new Rectangle(
                this.window.getWindow().getCoveredScreen() ) );
        }
    }
    
    
    /**
     * Saves a picture of the underlying UI's focused window to a file.
     *
     * @param filename the file to save the image as.
     */
    protected synchronized void fireSaveFocusedWindow()
    {
        if (isRunning())
        {
            Rectangle bounds = this.fwf.getFocusedWindowBounds();
            if (bounds == null)
            {
                // save the entire screen
                fireSaveScreen();
            }
            else
            {
                fireSaveScreenImage( bounds );
            }
        }
    }
    
    
    /**
     * Saves the selected screen part to a file.
     *
     * @param filename the file to save the image as.
     * @param x the x position of the part of the screen to grab.
     * @param y the y position of the part of the screen to grab.
     * @param w the width of the part of the screen to grab.
     * @param h the height of the part of the screen to grab.
     */
    protected synchronized void fireSaveScreenImage( int x, int y, int w,int h )
    {
        if (isRunning())
        {
            fireSaveScreenImage( new Rectangle( x, y, w, h ) );
        }
    }
    
    
    /**
     * Saves the selected screen part to a file.
     *
     * @param filename the file to save the image as.
     * @param bounds the part of the screen to grab.
     */
    protected synchronized void fireSaveScreenImage( Rectangle bounds )
    {
        if (isRunning())
        {
            File f = createFile();
            try
            {
                this.ss.writeImageToFile(
                    this.window.createScreenScrape(bounds ),
                    f );
                this.maker.generateScreenCapture( f,
                    bounds.x, bounds.y, bounds.width, bounds.height );
            }
            catch (IOException ioe)
            {
                encounteredError( ioe );
            }
        }
    }
    
    
    /**
     * 
     */
    protected synchronized void fireMouseWheelMoved( MouseWheelCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generateMoveMouseWheel( ce.getWheelRotation() );
        }
    }

    
    /**
     * 
     */
    protected synchronized void fireMouseMoved( MouseMovedCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generateMoveMouse( ce.getX(), ce.getY() );
        }
    }

    
    /**
     * 
     */
    protected synchronized void fireMousePressed( MouseButtonCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generatePressMouse( ce.getModifiers() );
        }
    }

    
    /**
     * 
     */
    protected synchronized void fireMouseReleased( MouseButtonCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generateReleaseMouse( ce.getModifiers() );
        }
    }

    
    /**
     * 
     */
    protected synchronized void fireKeyPressed( KeyCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generatePressKey( ce.getKeyCode() );
        }
    }

    
    /**
     * 
     */
    protected synchronized void fireKeyReleased( KeyCaptureEvent ce )
    {
        if (isRunning())
        {
            generateDelay( ce );
            this.maker.generateReleaseKey( ce.getKeyCode() );
        }
    }

    
    //-------------------------------------------------------------------------
    // Event Methods

    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void mouseWheelMoved( MouseWheelCaptureEvent ce )
    {
        if (allowMouseWheelMoved( ce ))
        {
            fireMouseWheelMoved( ce );
        }
        else
        {
            metaMouseWheelMoved( ce );
        }
    }
    
    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void mouseMoved( MouseMovedCaptureEvent ce )
    {
        if (allowMouseMoved( ce ))
        {
            fireMouseMoved( ce );
        }
        else
        {
            metaMouseMoved( ce );
        }
    }
    
    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void mousePressed( MousePressedCaptureEvent ce )
    {
        if (allowMousePressed( ce ))
        {
            fireMousePressed( ce );
        }
        else
        {
            metaMousePressed( ce );
        }
    }
    
    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void mouseReleased( MouseReleasedCaptureEvent ce )
    {
        if (allowMouseReleased( ce ))
        {
            fireMouseReleased( ce );
        }
        else
        {
            metaMouseReleased( ce );
        }
    }
    
    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void keyPressed( KeyPressedCaptureEvent ce )
    {
        if (allowKeyPressed( ce ))
        {
            fireKeyPressed( ce );
        }
        else
        {
            metaKeyPressed( ce );
        }
    }
    
    
    /**
     * If a subclass overrides this method, it <b>must</b> first check
     * if meta-mode is enabled, and invoke
     * the super's version of this method at the very end (right before
     * leaving the method).
     */
    public void keyReleased( KeyReleasedCaptureEvent ce )
    {
        if (allowKeyReleased( ce ))
        {
            fireKeyReleased( ce );
        }
        else
        {
            metaKeyReleased( ce );
        }
    }
    
    
    //-------------
    
    
    /**
     * @see java.awt.event.MouseWheelListener
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowMouseWheelMoved( MouseWheelCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    /**
     * @see java.awt.event.MouseListener
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowMousePressed( MousePressedCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    /**
     * @see java.awt.event.MouseListener
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowMouseReleased( MouseReleasedCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    /**
     * @see java.awt.event.KeyListener
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowKeyPressed( KeyPressedCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    /**
     * @see java.awt.event.KeyListener
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowKeyReleased( KeyReleasedCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    /**
     * Not part of the allow interface.
     *
     * @return <tt>true</tt> to allow the event to passed to the underlying
     *      UI, or <tt>false</tt> to 'swallow' the event.
     */
    public final boolean allowMouseMoved( MouseMovedCaptureEvent ce )
    {
        return !isInMetaMode();
    }
    
    
    
    //-------------------------------------------------------------------------
    // Misc Protected Methods

    
    /**
     * Create a delay event in the script maker.
     */
    protected void generateDelay( CaptureEvent ce )
    {
        long delay = getDelayTime( ce );
        if (delay > 0)
        {
            this.maker.generateDelay( delay );
        }
    }
    
    
    /**
     * Calculate the amount of time between the last event and the given
     * event.
     *
     * @return the delay time in milliseconds.
     */
    protected synchronized long getDelayTime( CaptureEvent ce )
    {
        long waitTime = ce.getTimeOfEvent();
        if (this.lastEventTime < 0)
        {
            this.lastEventTime = waitTime;
            return 0;
        }
        if (waitTime < 0 || waitTime < this.lastEventTime)
        {
            return 0;
        }
        long diff = waitTime - this.lastEventTime;
        this.lastEventTime = waitTime;
        return diff;
    }
    
    
    /**
     * 
     */
    protected synchronized File createFile()
    {
        File f = new File( this.baseImageName + this.filenameIndex + '.' +
            this.ss.getFileExtention() );
        ++this.filenameIndex;
        
        return f;
    }
    
    
    /**
     * 
     */
    protected void encounteredError( Throwable t )
    {
        t.printStackTrace();
    }
}

