/*
 * @(#)VirtualWindowController.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.Robot;
import java.awt.Window;
import java.awt.Dimension;
import java.awt.Rectangle;

import net.sourceforge.groboutils.uicapture.v1.event.MouseMovedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MousePressedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseReleasedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.MouseWheelCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.KeyPressedCaptureEvent;
import net.sourceforge.groboutils.uicapture.v1.event.KeyReleasedCaptureEvent;

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


/**
 * A window which covers the whole screen, and does not paint in the background.
 * It captures keyboard and mouse events, and sends them to both all registered
 * listeners, and to the underlying GUI as well.
 *
 * WARNING: if the screen size is to resize, then this will not work correctly.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   Jan 4, 2002
 */
public class VirtualWindowController
{
    //-------------------------------------------------------------------------
    // Private fields
    
    private VirtualWindow window = null;
    private IScreenScraper ss = null;
    private IFocusedWindowFinder fwf = null;
    
    private static final long MAX_ROBOT_DELAY = 60000L;
    
    
    
    //-------------------------------------------------------------------------
    // Constructors

    /**
     * Uses the Default version for the IScreenScraper and IFocusedWindowFinder.
     */
    public VirtualWindowController()
    {
        this( new DefaultScreenScraper(), new DefaultFocusedWindowFinder() );
    }
    
    
    /**
     * Create using the given arguments to build the framework.
     */
    public VirtualWindowController( IScreenScraper ss,
            IFocusedWindowFinder fwf )
    {
        this.ss = ss;
        this.fwf = fwf;
    }
    


    //-------------------------------------------------------------------------
    // Public methods
    
    
    /**
     * Begin the reinactment process.
     *
     * @exception java.awt.AWTException thrown if a Robot is not supported
     *      in the current JDK implementation.
     */
    public void begin()
            throws java.awt.AWTException
    {
        assertInactive();
        this.window = new VirtualWindow( null, false );
    }
    
    
    /**
     * Complete the reinactment process.
     */
    public void end()
    {
        assertActive();
        this.window.dispose();
        this.window = null;
    }
    
    
    /**
     * Pause for a period of milliseconds.  Since Robot can only delay for
     * 60 seconds at a time, this will first sleep using the Robot, then
     * will use Thread for the remainder of the time.
     *
     * @param milliseconds Time in milliseconds to sleep.
     */
    public void sleep( long milliseconds )
    {
        assertActive();
        
        int ms = (milliseconds > MAX_ROBOT_DELAY ? (int)MAX_ROBOT_DELAY :
            (int)milliseconds);
        long remainder = milliseconds - ms;
        
        this.window.delay( ms );
        if (remainder > 0)
        {
            try
            {
                Thread.sleep( remainder );
            }
            catch (InterruptedException ie)
            {
                // ignore
            }
        }
    }
    
    
    /**
     * Waits until all events currently on the event queue have been processed.
     */
    public void waitForIdle()
    {
        assertActive();
        this.window.waitForIdle();
    }
    
    
    /**
     * Retrieves the size and position of the capture area of the screen.
     *
     * @return the screen size.
     */
    public Rectangle getBounds()
    {
        assertActive();
        return this.window.getWindow().getCoveredScreen();
    }
    
    
    /**
     * Compares the two files for identity.
     *
     * @return <tt>true</tt> if the files are identical, else <tt>false</tt>.
     */
    public boolean compareFiles( File one, File two )
            throws IOException
    {
        throw new IllegalStateException( "Not implemented yet." );
    }
    
    
    public String getImageExtension()
    {
        return this.ss.getFileExtention();
    }
    
    
    /**
     * Saves the entire screen to a file.
     *
     * @param filename the file to save the image as.
     * @exception IOException thrown if there was a problem writing the file.
     */
    public void saveScreen( String filename )
            throws IOException
    {
        assertActive();
        saveScreenImage( filename, new Rectangle( getBounds() ) );
    }
    
    
    /**
     * Saves a picture of the underlying UI's focused window to a file.
     *
     * @param filename the file to save the image as.
     * @exception IOException thrown if there was a problem writing the file.
     */
    public void saveFocusedWindow( String filename )
            throws IOException
    {
        assertActive();
        Rectangle bounds = this.fwf.getFocusedWindowBounds();
        if (bounds == null)
        {
            // save the entire screen
            saveScreen( filename );
        }
        else
        {
            saveScreenImage( filename, 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.
     * @exception IOException thrown if there was a problem writing the file.
     */
    public void saveScreenImage( String filename, int x, int y, int w, int h )
            throws IOException
    {
        saveScreenImage( filename, 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.
     * @exception IOException thrown if there was a problem writing the file.
     */
    public void saveScreenImage( String filename, Rectangle bounds )
            throws IOException
    {
        assertActive();
        this.ss.writeImageToFile( this.window.createScreenScrape( bounds ),
            new File( filename ) );
    }
    
    
    
    
    //-------------------------------------------------------------------------
    // Event methods
    
    
    /**
     * Simulate a mouse movement.
     *
     * @param x the x position (aboslute) to move the mouse to.
     * @param y the y position (aboslute) to move the mouse to.
     */
    public void moveMouse( int x, int y )
    {
        assertActive();
        MouseMovedCaptureEvent ce = new MouseMovedCaptureEvent( x, y );
        this.window.simulateEvent( ce );
    }
    
    
    /**
     * Simulate a mouse button press.
     *
     * @param modifiers the mouse button modifiers.
     */
    public void pressMouse( int modifiers )
    {
        assertActive();
        MousePressedCaptureEvent ce = new MousePressedCaptureEvent( modifiers );
        this.window.simulateEvent( ce );
    }
    
    
    /**
     * Simulate a mouse button release.
     *
     * @param modifiers the mouse button modifiers.
     */
    public void releaseMouse( int modifiers )
    {
        assertActive();
        MouseReleasedCaptureEvent ce =
            new MouseReleasedCaptureEvent( modifiers );
        this.window.simulateEvent( ce );
    }
    
    
    /**
     * Simulate a mouse wheel rotation.
     *
     * @param rotation the number of 'clicks' to rotate the mouse wheel.
     */
    public void rotateMouseWheel( int rotation )
    {
        assertActive();
        MouseWheelCaptureEvent ce = new MouseWheelCaptureEvent( rotation );
        this.window.simulateEvent( ce );
    }
    
    
    /**
     * Simulate a key press.
     *
     * @param keyCode the key code of the key.
     */
    public void pressKey( int keyCode )
    {
        assertActive();
        KeyPressedCaptureEvent ce = new KeyPressedCaptureEvent( keyCode );
        this.window.simulateEvent( ce );
    }
    
    
    /**
     * Simulate a key release.
     *
     * @param keyCode the key code of the key.
     */
    public void releaseKey( int keyCode )
    {
        assertActive();
        KeyReleasedCaptureEvent ce = new KeyReleasedCaptureEvent( keyCode );
        this.window.simulateEvent( ce );
    }
    
    
    
    //-------------------------------------------------------------------------
    // Protected methods
    
    protected void assertActive()
    {
        if (this.window == null)
        {
            throw new IllegalStateException("Window is not active.");
        }
    }


    protected void assertInactive()
    {
        if (this.window != null)
        {
            throw new IllegalStateException("Window is active.");
        }
    }
}

