package TestTools;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;

/**
 * SwingRobot is a front end to the java.awt.Robot class.
 * This program reads an ASCII script file (.rsf) that contains commands 
 * to the Swing robot. The commands are then translated into method calls
 * to the AWT Robot class.
 * @author David Cumberland.
 * Modifications by John Dalbey, Jason Scaroni
 * @version 2.0 4/15/06 
 * Modified to invoke the target application's main method, if it exists.
 * Read main method arguments from a AUT.propeties file (key is "commandline").
 * @version 2.1 6/10/2006 Added support for the colon character, used in web protocols.
 * @version 3.0 2/4/2007 Added semicolon character.
 *                       Added newClient script command.
 * @verssion 4.0 2014/1/18 Refactored to allow calling with a Scanner
 *
 * Script File Commands
 *  -- two dashes indicate a comment
 *  press_key keyname   --depress a key
 *  release_key keyname --release a key
 *  type_key keyname    --depress and release a key
 *  type_string string  --types a string of characters (not enclosed in quotes)
 *                      -- cannot contain control characters
 *  mouse_move x,y
 *  mouse_down
 *  mouse_up
 *  mouse_press x,y     --combines a mouse move with a mouse down and up
 *
 */
public class SwingRobot
{
    private static HashMap<String,Integer> mKeys = new HashMap<String,Integer>();
    private String appName;  // AUT name
    private Class targetClass = null; // This Class represents the user's application
    private int delayDuration;         // time to delay after each command
    private String scriptFilename; 
    private Scanner scriptInput;
    private boolean logging = false;  // is logging on or off

    static {         createHashtable();  }

    public SwingRobot(){}
    
    public SwingRobot(String aut, Scanner scriptScanner)
    {
        this.appName = aut;
        this.scriptInput = scriptScanner;
    }
    public void setDelayDuration(int durationInMillis)
    {
        if (durationInMillis > 0)
        {
            delayDuration = durationInMillis;
        }
    }
    public void setLogging(boolean logging)
    {
        this.logging = logging;
    }
    private boolean processParameters(String[] args)
    {
        boolean result = true;
        //process parameters
        if (args.length < 1)
        {
            System.err.println("Ver 3.0 Usage: provide the application name, a robot script file (.rsf)\n"
                               + "and an optional delay (in ms).");
            result = false;
        }
        appName = args[0];
        if (args.length < 2)
        {
            System.err.println("Missing command line parameter: robot script file (.rsf)");
            result = false;
        }
        scriptFilename = args[1];
        //Process the third command line argument, if it exists
        // which contains a delay duration
        if (args.length >= 3)
        {
            delayDuration = Integer.parseInt(args[2]);
        }
        else
        {
            //Note:  When I had this 500 the robot would issue duplicate key presses.  Mystery.
            delayDuration = 250;  // default = .25 seconds
        }     
        return result;
    }

    private boolean loadScriptFile()
    {
        boolean result = true;
        //open the script file for processing
        try
        {
            scriptInput = new Scanner(new File(scriptFilename));
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            System.err.println("Unable to open script file: " + scriptFilename);
            result = false;
        }
        return result;
    }

    
   public void runScript()
   {
        Robot rob = null;          // the instance of AWT Robot
        String line;                // input line from script file
        StringTokenizer strTok;     // to parse input line
        Integer keyEvent;           // user keystroke

        launchClient(appName);

        // create a robot to feed in GUI events
        try
        {
            rob = new Robot();
        }
        catch (AWTException ex)
        {
            System.err.println("Can't start Robot: " + ex);
            System.exit(0);
        }
        rob.delay(1000); //delay to let the application load

        rob.setAutoDelay(delayDuration);    // set delay that happens after every command

        try
        {
        //process all the lines in file
            while (scriptInput.hasNextLine())
            {
                line = scriptInput.nextLine();
                line = line.trim();    
                if (logging)
                {
                    System.out.println("script command: " + line);
                }

                //if not a comment and not a short lines (from Unix -> Windows transfer)
                if (line.length() > 0 && 
                 !(line.startsWith("//") || line.startsWith("--")))
                {
                    //process the line, interpreting commands
                    // and calling the Robot methods
                    if (line.startsWith("newClient"))
                    {
                        // Create an instance of the application to be controlled
                        launchClient(appName);
                    }
                    else if (line.startsWith("mouse_move"))
                    {
                        line = line.replace(',', ' ');
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 3)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        rob.mouseMove(Integer.parseInt(strTok.nextToken()),
                         Integer.parseInt(strTok.nextToken()));
                    }

                    else if (line.startsWith("mouse_press"))
                    {
                        line = line.replace(',', ' ');
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 3)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        rob.mouseMove(Integer.parseInt(strTok.nextToken()),
                         Integer.parseInt(strTok.nextToken()));
                        rob.mousePress(InputEvent.BUTTON1_MASK);
                        rob.mouseRelease(InputEvent.BUTTON1_MASK);
                    }

                    else if (line.startsWith("mouse_down"))
                    {
                        rob.mousePress(InputEvent.BUTTON1_MASK);
                    }

                    else if (line.startsWith("mouse_up"))
                    {
                        rob.mouseRelease(InputEvent.BUTTON1_MASK);
                    }

                    else if (line.startsWith("press_key"))
                    {
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 2)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        keyEvent = (Integer) mKeys.get(strTok.nextToken().toLowerCase());
                        if (keyEvent == null)
                        {
                            throw new Exception("***** unrecognized key name: "
                             + line);
                        }

                        rob.keyPress(keyEvent.intValue());
                    }

                    else if (line.startsWith("release_key"))
                    {
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 2)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        keyEvent = (Integer) mKeys.get(strTok.nextToken().toLowerCase());
                        if (keyEvent == null)
                        {
                            throw new Exception("***** unrecognized key name: "
                             + line);
                        }
                        rob.keyRelease(keyEvent.intValue());
                    }

                    else if (line.startsWith("type_key"))
                    {
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 2)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        String toke = strTok.nextToken(); //Skip the first token
                        keyEvent = (Integer) mKeys.get(toke.toLowerCase());
                        //System.out.println("typing key: " + toke);
                        if (keyEvent == null)
                        {
                            throw new Exception("***** unrecognized key name: "
                             + line);
                        }
                        rob.keyPress(keyEvent.intValue());
                        rob.keyRelease(keyEvent.intValue());
                        //System.out.println("done typing key: " + toke);
                    }

                    else if (line.startsWith("type_string"))
                    {
                        line = line.substring(12).trim();
                        rob.setAutoDelay(1);    // turn off delay for entering string

                        for (int index = 0; index < line.length(); index++)
                        {
                            char letter = line.charAt(index);
                            if (letter >= 'A' && letter <= 'Z')
                            {
                                rob.keyPress(KeyEvent.VK_SHIFT);
                            }
                            keyEvent = (Integer) mKeys.get(String.valueOf(letter));
                            rob.keyPress(keyEvent.intValue());
                            rob.keyRelease(keyEvent.intValue());
                            rob.keyRelease(KeyEvent.VK_SHIFT);
                        }

                        rob.setAutoDelay(delayDuration);    // restore delay
                    }

                    else if (line.startsWith("wait"))
                    {
                        strTok = new StringTokenizer(line, " ");
                        if (strTok.countTokens() < 2)
                        {
                            throw new Exception("***** invalid script command: "
                             + line);
                        }

                        strTok.nextToken(); //Skip the first token
                        rob.delay(Integer.parseInt(strTok.nextToken()));
                    }

                    else
                    {
                        throw new Exception("***** invalid script command: " + line);
                    }

                    //rob.delay(500);
                }
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }

        //close the file
        try
        {
            scriptInput.close();
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }

        //make sure all special mKeys have been turned off
        rob.keyRelease(KeyEvent.VK_CONTROL);
        rob.keyRelease(KeyEvent.VK_ALT);
    }

    private String[] getProperties()
        {
        String[] result = {""};
        try
        {
            FileInputStream propertiesFile = new FileInputStream("AUT.properties");
            // if file was found
            if (propertiesFile != null)
            {
                // Load the properties
                Properties defaultProperties  = new Properties();
                defaultProperties.load(propertiesFile);

                // Get the command line parameters for the target main
                String cmdString =(String)defaultProperties.get("commandline");
                if (cmdString != null)
                {
                    // put each token into a string array
                    result = cmdString.split(" ");                                       
                }
            }
        }
        catch (FileNotFoundException ex)
        {
            //System.out.println("No properties file found in current directory.");
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        return result;
        }

   // Create an instance of the application to be controlled
   private void launchClient(String className)
   {
        // Create an instance of the application to be controlled
        try
        {
            targetClass = Class.forName(className);
            // Check for main, and if not found, call default constructor
            if (! callMain(targetClass) )
            {                
                System.out.println("Invoking default constructor of " + targetClass);
                Object o = targetClass.newInstance();
            }
        }
        catch (Exception err)
        {
                        System.out.println("Error: " + err);
                        err.printStackTrace();
                        System.exit(1);
        }
     }
     
     public void killClient()
     {
         targetClass = null;
     }
     @SuppressWarnings("unchecked") 
     private boolean callMain(Class appClass)
     {
      String[] cmdLine = getProperties(); 
      //System.out.println("prepared command line: " + cmdLine[0]);

      /* Use Reflection to invoke the main method in the target application */
      // define the String array type - use getClass on OUR string array.
      Class[] parameterTypes = new Class[] {cmdLine.getClass()};
      Method mainMethod;
      Object[] arguments =  {cmdLine};
      try {
        mainMethod = appClass.getMethod("main", parameterTypes);
        System.out.println("Invoking main method of " + appClass);
        mainMethod.invoke(null,arguments);
      } catch (NoSuchMethodException e) {
          return false; 
      } catch (IllegalAccessException e) {
          System.out.println(e);
      } catch (InvocationTargetException e) {
          System.out.println(e);
      }
      return true;
    } // end call Main

    private static void createHashtable()
    {
        //create hashtable of input characters
        mKeys.put("0", new Integer(KeyEvent.VK_0));
        mKeys.put("1", new Integer(KeyEvent.VK_1));
        mKeys.put("2", new Integer(KeyEvent.VK_2));
        mKeys.put("3", new Integer(KeyEvent.VK_3));
        mKeys.put("4", new Integer(KeyEvent.VK_4));
        mKeys.put("5", new Integer(KeyEvent.VK_5));
        mKeys.put("6", new Integer(KeyEvent.VK_6));
        mKeys.put("7", new Integer(KeyEvent.VK_7));
        mKeys.put("8", new Integer(KeyEvent.VK_8));
        mKeys.put("9", new Integer(KeyEvent.VK_9));
        mKeys.put("a", new Integer(KeyEvent.VK_A));
        mKeys.put("b", new Integer(KeyEvent.VK_B));
        mKeys.put("c", new Integer(KeyEvent.VK_C));
        mKeys.put("d", new Integer(KeyEvent.VK_D));
        mKeys.put("e", new Integer(KeyEvent.VK_E));
        mKeys.put("f", new Integer(KeyEvent.VK_F));
        mKeys.put("g", new Integer(KeyEvent.VK_G));
        mKeys.put("h", new Integer(KeyEvent.VK_H));
        mKeys.put("i", new Integer(KeyEvent.VK_I));
        mKeys.put("j", new Integer(KeyEvent.VK_J));
        mKeys.put("k", new Integer(KeyEvent.VK_K));
        mKeys.put("l", new Integer(KeyEvent.VK_L));
        mKeys.put("m", new Integer(KeyEvent.VK_M));
        mKeys.put("n", new Integer(KeyEvent.VK_N));
        mKeys.put("o", new Integer(KeyEvent.VK_O));
        mKeys.put("p", new Integer(KeyEvent.VK_P));
        mKeys.put("q", new Integer(KeyEvent.VK_Q));
        mKeys.put("r", new Integer(KeyEvent.VK_R));
        mKeys.put("s", new Integer(KeyEvent.VK_S));
        mKeys.put("t", new Integer(KeyEvent.VK_T));
        mKeys.put("u", new Integer(KeyEvent.VK_U));
        mKeys.put("v", new Integer(KeyEvent.VK_V));
        mKeys.put("w", new Integer(KeyEvent.VK_W));
        mKeys.put("x", new Integer(KeyEvent.VK_X));
        mKeys.put("y", new Integer(KeyEvent.VK_Y));
        mKeys.put("z", new Integer(KeyEvent.VK_Z));
        mKeys.put("A", new Integer(KeyEvent.VK_A));
        mKeys.put("B", new Integer(KeyEvent.VK_B));
        mKeys.put("C", new Integer(KeyEvent.VK_C));
        mKeys.put("D", new Integer(KeyEvent.VK_D));
        mKeys.put("E", new Integer(KeyEvent.VK_E));
        mKeys.put("F", new Integer(KeyEvent.VK_F));
        mKeys.put("G", new Integer(KeyEvent.VK_G));
        mKeys.put("H", new Integer(KeyEvent.VK_H));
        mKeys.put("I", new Integer(KeyEvent.VK_I));
        mKeys.put("J", new Integer(KeyEvent.VK_J));
        mKeys.put("K", new Integer(KeyEvent.VK_K));
        mKeys.put("L", new Integer(KeyEvent.VK_L));
        mKeys.put("M", new Integer(KeyEvent.VK_M));
        mKeys.put("N", new Integer(KeyEvent.VK_N));
        mKeys.put("O", new Integer(KeyEvent.VK_O));
        mKeys.put("P", new Integer(KeyEvent.VK_P));
        mKeys.put("Q", new Integer(KeyEvent.VK_Q));
        mKeys.put("R", new Integer(KeyEvent.VK_R));
        mKeys.put("S", new Integer(KeyEvent.VK_S));
        mKeys.put("T", new Integer(KeyEvent.VK_T));
        mKeys.put("U", new Integer(KeyEvent.VK_U));
        mKeys.put("V", new Integer(KeyEvent.VK_V));
        mKeys.put("W", new Integer(KeyEvent.VK_W));
        mKeys.put("X", new Integer(KeyEvent.VK_X));
        mKeys.put("Y", new Integer(KeyEvent.VK_Y));
        mKeys.put("Z", new Integer(KeyEvent.VK_Z));

        //extra characters
        mKeys.put(" ", new Integer(KeyEvent.VK_SPACE));
        mKeys.put("space", new Integer(KeyEvent.VK_SPACE));
        mKeys.put("enter", new Integer(KeyEvent.VK_ENTER));
        mKeys.put("shift", new Integer(KeyEvent.VK_SHIFT));
        mKeys.put("esc", new Integer(KeyEvent.VK_ESCAPE));
        mKeys.put("del", new Integer(KeyEvent.VK_DELETE));
        mKeys.put("backspace", new Integer(KeyEvent.VK_BACK_SPACE));
        mKeys.put("capslock", new Integer(KeyEvent.VK_CAPS_LOCK));
        mKeys.put("ctrl", new Integer(KeyEvent.VK_CONTROL));
        mKeys.put("alt", new Integer(KeyEvent.VK_ALT));
        mKeys.put("tab", new Integer(KeyEvent.VK_TAB));
        mKeys.put(".", new Integer(KeyEvent.VK_PERIOD));
        mKeys.put("down", new Integer(KeyEvent.VK_DOWN));
        mKeys.put("up", new Integer(KeyEvent.VK_UP));
        mKeys.put("left", new Integer(KeyEvent.VK_LEFT));
        mKeys.put("right", new Integer(KeyEvent.VK_RIGHT));
        mKeys.put("-", new Integer(KeyEvent.VK_MINUS));
        mKeys.put("f4", new Integer(KeyEvent.VK_F4));
        mKeys.put("/", new Integer(KeyEvent.VK_SLASH));
        mKeys.put(";", new Integer(KeyEvent.VK_SEMICOLON));   
        /* Colon character requires special processing. 
           Technically there is no colon on the US keyboard, only a shifted semicolon,
           so a special check is added in type_string handler to press shift.
         */
        mKeys.put(":", new Integer(KeyEvent.VK_SEMICOLON));   
    }

    public static void main(String[] args)
    {
//         try
//         {
        SwingRobot robot = new SwingRobot();
        if (!robot.processParameters(args))
        {
           System.out.println("SwingRobot failed processing command line arguments");
        }
        if (!robot.loadScriptFile())
        {
           System.out.println("SwingRobot failed loading script file.");
        }
        robot.runScript();
//         }
//         catch (AWTException ex)
//         {
//             System.out.println("Exception running awt.Robot");
//             ex.printStackTrace();
//         }
    }
}