/*
 * @(#)TelnetScript.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */

package com.jscape.inet.telnet;

import java.io.Serializable;
import java.util.Vector;

/**
 * The TelnetScript class provides a simple way of running a set of batch scripted commands against a TelnetServer.  When
 * communicating with a Telnet server in interactive mode (non-batch) the user must wait until the completion of the previous
 * command before entering a new command.  In interactive mode the completion of the previous command is generally indicated
 * by the display of the shell prompt which allows the user to enter the next command.  The TelnetScript class is especially
 * useful in automating Telnet procedures such as user login and command execution.
 * <p/>
 * Example Usage:<P>
 * <pre>
 * // create new Telnet instance
 * Telnet telnet = new Telnet("10.0.0.1");
 *
 * // create new TelnetScript instance
 * TelnetScript script = new TelnetScript(telnet);
 *
 * // create task to automatically submit username
 * TelnetTask username = new TelnetTask("login:","jsmith","Password:");
 *
 * // create task to automatically submit password
 * TelnetTask password = new TelnetTask("Password:","secret","$");
 *
 * // create task to perform a directory listing
 *
 * TelnetTask dirListing = new TelnetTask("$","ls -al","$");
 *
 * // add tasks to script
 * script.addTask(username);
 * script.addTask(password);
 * script.addTask(dirListing);
 *
 * // connect to Telnet server and execute TelnetScript
 * telnet.connect();
 *
 * // disconnect from Telnet server
 * telnet.disconnect();
 * </pre>
 */

public class TelnetScript
        implements Serializable {

    /**
     * The first TelnetTask to be executed by this TelnetScript
     */
    TelnetTask head = null;

    /**
     * The last TelnetTask to be executed by this TelnetScript
     */
    TelnetTask tail = null;

    /**
     * The Telnet instance this TelnetScript is bound to
     */
    Telnet telnet = null;

    /**
     * Event listeners
     */
    private transient Vector listeners = new Vector();


    /**
     * Applies this terminator to all TelnetTask if not null
     */
    private String terminator;


    /**
     * Constructs a new TelnetScript instance.
     *
     * @param telnet Telnet instance this TelnetScript is bound to.
     *
     * @see Telnet
     */
    public TelnetScript(Telnet telnet) {
        this.telnet = telnet;
    }

    /**
     * Constructs a new TelnetScript instance.
     * When adding a new TelnetTask using addTask method the TelnetTask#setTerminator method is invoked with
     * the terminator value.  This is a convenient method of setting the terminator globally for all TelnetTask
     * added to a TelnetScript instead of setting it for each TelnetTask.
     *
     * @param telnet     Telnet instance this TelnetScript is bound to.
     * @param terminator the line terminator to use when executing command for a TelnetTask
     *
     * @see Telnet
     * @see TelnetTask
     * @see #addTask
     */
    public TelnetScript(Telnet telnet, String terminator) {
        this(telnet);
        this.terminator = terminator;
    }

    /**
     * Checks if there are any TelnetTask bound to this TelnetScript
     *
     * @return true if there are TelnetTask, false otherwise
     */
    public boolean isEmpty() {
        return head == null;
    }

    /**
     * Clears all tasks from the script.
     */
    public void clear() {
        this.head = null;
        this.tail = null;
    }

    /**
     * Adds a TelnetTask to this TelnetScript.
     *
     * @param task the TelnetTask to add
     *
     * @see TelnetTask
     */
    public void addTask(TelnetTask task) {
        synchronized (this) {
            task.setScript(this);

            // set line terminator for task
            if (terminator != null) {
                task.setTerminator(terminator);
            }

            // if no tasks added to script
            if (!isEmpty()) {
                task.prevTask = tail;
                task.nextTask = null;
                tail = task;
                tail.prevTask.nextTask = tail;

                // if previous task is complete then activate task
                if (task.prevTask.isComplete()) {
                    tail.setStatus(TelnetTask.ACTIVE);
                    telnet.addTelnetListener(tail);

                    // if start prompt is null then execute immediately
                    if (tail.getStartPrompt() == null) {
                        tail.execute();
                    }
                }
            } else {
                head = task;
                tail = head;

                // activate task
                head.setStatus(TelnetTask.ACTIVE);

                // if start prompt is null then execute immediately
                if (head.getStartPrompt() == null) {
                    head.execute();
                }

                telnet.addTelnetListener(head);
            } // end if
        }
    }

    /**
     * Gets the named TelnetTask based on the name attribute of a TelnetTask.
     *
     * @param name the name of the TelnetTask to retrieve.
     *
     * @return the named TelnetTask if found, null otherwise
     *
     * @see TelnetTask
     */
    public TelnetTask getTask(String name) {
        synchronized (this) {
            TelnetTask task = null;
            for (TelnetTask tmp = head; tmp != null; tmp = tmp.nextTask) {
                String taskName = tmp.getName();
                if (taskName != null && taskName.equals(name)) {
                    task = tmp;
                    break;
                }
            }
            return task;
        }
    }

    /**
     * Adds a <code>TelnetScriptListener</code> to this <code>TelnetScript</code>.
     *
     * @param listener the <code>TelnetScriptListener</code> instance to add
     *
     * @see TelnetScriptListener
     */
    public synchronized void addTelnetScriptListener(TelnetScriptListener listener) {
        listeners.addElement(listener);
    }

    /**
     * Removes a <code>TelnetScriptListener</code> from this TelnetScript
     *
     * @param listener the <code>TelnetScriptListener</code> instance to remove
     *
     * @see TelnetScriptListener
     */
    public synchronized void removeTelnetScriptListener(TelnetScriptListener listener) {
        if (listeners.contains(listener)) {
            listeners.removeElement(listener);
        }
    }


    /**
     * Fires <code>TelnetTaskStartEvent</code>.
     * This event is fired upon executing the command contained within a <code>TelnetTask</code>.
     *
     * @param task the <code>TelnetTask</code> that has started
     *
     * @see TelnetTaskStartEvent
     * @see TelnetTask
     */
    protected void fireTaskStart(TelnetTask task) {
        Vector thisList = new Vector();
        TelnetTaskStartEvent thisEvent = new TelnetTaskStartEvent(this, task);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        } // Send a TelnetTaskStartEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((TelnetScriptListener) thisList.elementAt(elem)).taskStart(thisEvent);
        }
    }

    /**
     * Fires <code>TelnetTaskTimeoutEvent</code>.
     * This event is fired when a TelnetTask exceeds it's defined timeout waiting for it's defined end prompt.
     *
     * @param task the <code>TelnetTask</code> that has timed out
     *
     * @see TelnetTaskTimeoutEvent
     * @see TelnetTask
     */
    protected void fireTaskTimeout(TelnetTask task) {
        Vector thisList = new Vector();
        TelnetTaskTimeoutEvent thisEvent = new TelnetTaskTimeoutEvent(this, task);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        } // Send a TelnetTaskTimeoutEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((TelnetScriptListener) thisList.elementAt(elem)).taskTimeout(thisEvent);
        }
    }


    /**
     * Fires <code>TelnetTaskEndEvent</code>.
     * This event is fired upon completion of executing command contained within a <code>TelnetTask</code> and receipt of end prompt from Telnet server.
     *
     * @param task the <code>TelnetTask</code> that has completed
     *
     * @see TelnetTaskEndEvent
     * @see TelnetTask
     */
    protected void fireTaskEnd(TelnetTask task) {
        this.telnet.debug("Firing TelnetScript.fireTaskEnd");
        Vector thisList = new Vector();
        TelnetTaskEndEvent thisEvent = new TelnetTaskEndEvent(this, task);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        } // Send a TelnetTaskEndEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((TelnetScriptListener) thisList.elementAt(elem)).taskEnd(thisEvent);
        }
        this.telnet.debug("Firing TelnetScript.fireTaskEnd complete.");
    }

    /**
     * Fires <code>TelnetTaskFailedEvent</code>.
     * This event is fired in the event of an I/O error while trying to execute the command in a <code>TelnetTask</code>.
     *
     * @param task      the <code>TelnetTask</code> that failed
     * @param exception the <code>Exception</code> that caused the <code>TelnetTask</code> to fail
     *
     * @see TelnetTaskFailedEvent
     * @see TelnetTask
     */
    protected void fireTaskFailed(TelnetTask task, Exception exception) {
        Vector thisList = new Vector();
        TelnetTaskFailedEvent thisEvent = new TelnetTaskFailedEvent(this, task, exception);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        } // Send a TelnetTaskFailedEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((TelnetScriptListener) thisList.elementAt(elem)).taskFailed(thisEvent);
        }
    }


    /**
     * Checks whether this TelnetScript has completed execution of all TelnetTask.
     *
     * @return true if all tasks executed, false otherwise
     */
    public boolean isComplete() {
        synchronized (this) {
            if (tail != null) {
                return tail.isComplete();
            } else {
                return true;
            }
        }
    }

}
