/*
 * ECatalog is a database front-end, with two main features:
 * 1. Use of preferences
 *  A preference-based approach, where the user is allowed to define the importance of each criterion.
 *  Then the items are ranked accordingly to his criteria.
 * 2. Trade-off analysis
 *  A cooperative database approach, where the system "argues" with the user about his criteria.
 *  When there are no matching items, the system explains the minimal conflicting set and
 *  give some possible strong and weak relaxations about his criteria.
 * This package also containts the software and the set-up details used for our User Study,
 * comparing the use or not of the two previous features mentioned above.
 *
 * Copyright (C) 2006 David Portabella Clotet, Artificial Intelligence Laboratory, EPFL
 * 
 * This file is part of ecatalog-1.0.zip
 * 
 * ECatalog is free software and a free user study set-up;
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * ECatalog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ECatalog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * @version 1.0
 * @author David Portabella
 * To contact the author:
 * email: david@portabella.name and david.portabella@epfl.ch
 * 
 * More information about ECatalog:
 *  http://sourceforge.net/projects/ecatalog/
 *  http://icwww.epfl.ch/~portabel/ecatalogs/
 */

package utils;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

public class InputRecordAndPlaybackEngine {
    int KEY_PRESSED = 1, KEY_RELEASED = 2, MOUSE_MOVED = 3, MOUSE_PRESSED = 4, MOUSE_RELEASED = 5; // the java enum keyword did not allow me to set concrete numbers :?

    //enum EventCode {KEY_PRESSED, KEY_RELEASED, MOUSE_MOVED, MOUSE_PRESSED, MOUSE_RELEASED};
    DataOutputStream out;
    MyEventQueue myEventQueue;

    public void startRecording(String fileName, boolean useOwnEventQueue) throws IOException {
	//System.out.println("recording: start");
	if (useOwnEventQueue)
	    initEventQueue();
	FileOutputStream fos = new FileOutputStream(fileName);
	out = new DataOutputStream(fos);
    }

    public void stopRecording() {
	//System.out.println("recording: stop");
	DataOutputStream tmp = out;
	out = null;
	try { tmp.close(); } catch (Exception e) {}
    }

    public void startReplaying(String fileName) throws FileNotFoundException, AWTException {
	//System.out.println("reply");
	ReplayRunnable replay = new ReplayRunnable(fileName);
	new Thread(replay).start();
    }

    void initEventQueue() {
	if (myEventQueue != null)
	    return;
        myEventQueue = new MyEventQueue();
        Toolkit.getDefaultToolkit().getSystemEventQueue().push(myEventQueue);
    }


    class ReplayRunnable implements Runnable {
	DataInputStream in;
	Robot robot;
	int robotMouseButtonCode[] = {InputEvent.BUTTON1_MASK, InputEvent.BUTTON2_MASK, InputEvent.BUTTON3_MASK};

	ReplayRunnable(String fileName) throws FileNotFoundException, AWTException { 
	    FileInputStream fis = new FileInputStream(fileName);
	    in = new DataInputStream(fis);
	    robot = new Robot();
	}

	public void run() {
	    try {
		boolean finish = false;
		long lastWhen = 0; // just to avoid the compilation error: variable lastWhen might not have been initialized
		try { lastWhen = in.readLong(); } catch (EOFException e) { finish = true; }
  
		if (!finish) {
		    //REPLAY THE FIRST EVENT
		    replayEvent(lastWhen, in);
			
		    while (true) {
			long when;
			try { when = in.readLong(); } catch (EOFException e) { break; }

			//SLEEP
			long sleep = when - lastWhen;
			if (sleep < 0) sleep = 0;
			//System.out.println("waiting for " + sleep + " milliseconds");
			try { Thread.currentThread().sleep(sleep); } catch (InterruptedException e) {}
				
			//REPLAY EVENT
			replayEvent(when, in);
			lastWhen = when;
		    }
		}

	    }
	    catch (EOFException e) {
		System.out.println("Replay: EOFException");
	    }
	    catch (IOException e) {
		System.out.println("Replay: IOException");
	    }

	    //System.out.println("Replay: finished");

	    try { in.close(); } catch (IOException e) {}
	}

	boolean replayEvent(long when, DataInputStream in) throws IOException, EOFException {
	    int code;
	    try { 
		code = in.readByte();
	    } catch (EOFException e) {
		return false;
	    }

	    if (code == KEY_PRESSED) {
		int keyCode = in.readInt();
		//System.out.println("When = " + when + ", KEY_PRESSED: KeyCode = " + keyCode);
		robot.keyPress(keyCode);

	    } else if (code == KEY_RELEASED) {
		int keyCode = in.readInt();
		//System.out.println("When = " + when + ", KEY_RELEASED: KeyCode = " + keyCode);
		robot.keyRelease(keyCode);

	    } else if (code == MOUSE_MOVED) {
		int x = in.readInt();
		int y = in.readInt();
		//System.out.println("When = " + when + ", MOUSE_MOVED: x = " + x + ", y = " + y);
		robot.mouseMove(x, y);

	    } else if (code == MOUSE_PRESSED) {
		int button = in.readByte();
		int buttonCode = robotMouseButtonCode[button]; //InputEvent.BUTTON1_MASK

		//System.out.println("When = " + when + ", MOUSE_PRESSED: buttonCode = " + button);
		robot.mousePress(buttonCode);

	    } else if (code == MOUSE_RELEASED) {
		int button = in.readByte();
		int buttonCode = robotMouseButtonCode[button]; //InputEvent.BUTTON1_MASK
		//System.out.println("When = " + when + ", MOUSE_RELEASED: buttonCode = " + button);
		robot.mouseRelease(buttonCode);

	    } else {
		throw new Error("Replay: code not expected: " + code);
	    }
	    return true;
	}
    }

    public void recordInputEvent(InputEvent event) {
	if (out == null)
	    return;

	try {
	    //System.out.println("when: " + ((InputEvent)event).getWhen() + ", type=" + event.toString());
	    long when = event.getWhen();

	    if (event instanceof KeyEvent) {
		KeyEvent e = (KeyEvent) event;
		if (e.getID() == KeyEvent.KEY_PRESSED) {
		    out.writeLong(when);
		    int keyCode = e.getKeyCode();
		    out.writeByte(KEY_PRESSED);
		    out.writeInt(keyCode);
		    //System.out.println("When = " + when + ", KEY_PRESSED: KeyCode = " + keyCode);
		} else if (e.getID() == KeyEvent.KEY_RELEASED) {
		    out.writeLong(when);
		    int keyCode = e.getKeyCode();
		    out.writeByte(KEY_RELEASED);
		    out.writeInt(keyCode);
		    //System.out.println("When = " + when + ", KEY_RELEASED: KeyCode = " + keyCode);
		} else {
		    //System.out.println("note logged: when: " + ((InputEvent)event).getWhen() + ", type=" + event.toString());
		}
	    } else if (event instanceof MouseEvent) {
		MouseEvent e = (MouseEvent) event;
		if (e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED) {
		    out.writeLong(when);
		    int x = e.getX();
		    int y = e.getY();
		    out.writeByte(MOUSE_MOVED);
		    out.writeInt(x);
		    out.writeInt(y);
		    //System.out.println("When = " + when + ", MOUSE_MOVED: x = " + x + ", y = " + y);

		} else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
		    int button = e.getButton();
		    int buttonCode = -1;
		    if (button == MouseEvent.BUTTON1) buttonCode = 0;
		    else if (button == MouseEvent.BUTTON2) buttonCode = 1;
		    else if (button == MouseEvent.BUTTON3) buttonCode = 2;
		    
		    if (buttonCode != -1) {
			out.writeLong(when);
			out.writeByte(MOUSE_PRESSED);
			out.writeByte(buttonCode);
			//System.out.println("When = " + when + ", MOUSE_PRESSED: buttonCode = " + buttonCode);
		    }
		} else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
		    int button = e.getButton();
		    int buttonCode = -1;
		    if (button == MouseEvent.BUTTON1) buttonCode = 0;
		    else if (button == MouseEvent.BUTTON2) buttonCode = 1;
		    else if (button == MouseEvent.BUTTON3) buttonCode = 2;
		    
		    if (buttonCode != -1) {
			out.writeLong(when);
			out.writeByte(MOUSE_RELEASED);
			out.writeByte(buttonCode);
			//System.out.println("When = " + when + ", MOUSE_RELEASED: buttonCode = " + buttonCode);
		    }
		} else {
		    //System.out.println("note logged: when: " + ((InputEvent)event).getWhen() + ", type=" + event.toString());
		}
	    } else {
		//System.out.println("note logged: when: " + ((InputEvent)event).getWhen() + ", type=" + event.toString());
	    }

	} catch (IOException e) {
	    throw new Error(e);
	}
    }

    class MyEventQueue extends EventQueue {
	protected void dispatchEvent(AWTEvent event) {
	    //System.out.println("InputRecordAndPlaybackEngine.dispatchEvent");
	    if (out != null && event instanceof InputEvent)
		recordInputEvent((InputEvent) event);
	    super.dispatchEvent(event);
	}
	/*
	void myDispatchEvent(AWTEvent event) {
	    super.dispatchEvent(event);
	}
	*/
    };
}

