/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000,2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.tools.ant.taskdefs.optional.net;

import com.oroinc.net.telnet.TelnetClient;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Vector;

/**
 * Class to provide automated telnet protocol support for the Ant build tool
 *
 * @author <a href="mailto:ScottCarlson@email.com">ScottCarlson@email.com</a>
 * @version $Revision$
 */

public class TelnetTask extends Task {
    /**
     *  The userid to login with, if automated login is used
     */
    private String userid  = null;

    /**
     *  The password to login with, if automated login is used
     */
    private String password= null;

    /**
     *  The server to connect to. 
     */
    private String server  = null;

    /**
     *  The tcp port to connect to. 
     */
    private int port = 23;

    /**
     *  The Object which handles the telnet session.
     */
    private AntTelnetClient telnet = null;

    /**
     *  The list of read/write commands for this session
     */
    private Vector telnetTasks = new Vector();

    /** 
     *  If true, adds a CR to beginning of login script
     */
    private boolean addCarriageReturn = false;

    /**
     *  Default time allowed for waiting for a valid response
     *  for all child reads.  A value of 0 means no limit.
     */
    private Integer defaultTimeout = null;

    /** 
     *  Verify that all parameters are included. 
     *  Connect and possibly login
     *  Iterate through the list of Reads and writes 
     */
    public void execute() throws BuildException 
    {
       /**  A server name is required to continue */
       if (server== null) {
           throw new BuildException("No Server Specified");
       }
       /**  A userid and password must appear together 
        *   if they appear.  They are not required.
        */
       if (userid == null && password != null) {
           throw new BuildException("No Userid Specified");
       }
       if (password == null && userid != null) {
           throw new BuildException("No Password Specified");
       }

       /**  Create the telnet client object */
       telnet = new AntTelnetClient();
       try {
           telnet.connect(server, port);
       } catch(IOException e) {
           throw new BuildException("Can't connect to "+server);
       }
       /**  Login if userid and password were specified */
       if (userid != null && password != null) {
          login();
       }
       /**  Process each sub command */
       Enumeration tasksToRun = telnetTasks.elements();
       while (tasksToRun!=null && tasksToRun.hasMoreElements())
       {
           TelnetSubTask task = (TelnetSubTask) tasksToRun.nextElement();
           if (task instanceof TelnetRead && defaultTimeout != null) {
               ((TelnetRead)task).setDefaultTimeout(defaultTimeout);
           }
           task.execute(telnet);
       }
    }

    /**  
     *  Process a 'typical' login.  If it differs, use the read 
     *  and write tasks explicitely
     */
    private void login()
    {
       if (addCarriageReturn) {
          telnet.sendString("\n", true);
       }
       telnet.waitForString("ogin:");
       telnet.sendString(userid, true);
       telnet.waitForString("assword:");
       telnet.sendString(password, false);
    }

    /**
     *  Set the userid attribute 
     */
    public void setUserid(String u) { this.userid = u; }

    /**
     *  Set the password attribute 
     */
    public void setPassword(String p) { this.password = p; }

    /**
     *  Set the server address attribute 
     */
    public void setServer(String m) { this.server = m; }

    /**
     *  Set the tcp port to connect to attribute 
     */
    public void setPort(int p) { this.port = p; }

    /**
     *  Set the tcp port to connect to attribute 
     */
    public void setInitialCR(boolean b)
    {
       this.addCarriageReturn = b;
    }

    /**
     *  Change the default timeout to wait for 
     *  valid responses
     */
    public void setTimeout(Integer i)
    {
       this.defaultTimeout = i;
    }

    /**
     *  A subTask &lt;read&gt; tag was found.  Create the object, 
     *  Save it in our list, and return it.
     */
   
    public TelnetSubTask createRead()
    {
        TelnetSubTask task = (TelnetSubTask)new TelnetRead();
        telnetTasks.addElement(task);
        return task;
    }

    /**
     *  A subTask &lt;write&gt; tag was found.  Create the object, 
     *  Save it in our list, and return it.
     */
    public TelnetSubTask createWrite()
    {
        TelnetSubTask task = (TelnetSubTask)new TelnetWrite();
        telnetTasks.addElement(task);
        return task;
    }

    /**  
     *  This class is the parent of the Read and Write tasks.
     *  It handles the common attributes for both.
     */
    public class TelnetSubTask
    {
        protected String taskString= "";
        public void execute(AntTelnetClient telnet) 
                throws BuildException
        {
            throw new BuildException("Shouldn't be able instantiate a SubTask directly");
        }
        /**
         *  nested text elements need their properties explicitly expanded
         */
        public void addText(String s) {
            setString(project.replaceProperties(s));
        }
        /**
         * attribute assignment of properties
         */
        public void setString(String s)
        {
           taskString += s; 
        }
    }
    /**
     *  This class sends text to the connected server 
     */
    public class TelnetWrite extends TelnetSubTask
    {
        private boolean echoString = true;
        public void execute(AntTelnetClient telnet) 
               throws BuildException
        {
           telnet.sendString(taskString, echoString);
        }
        
        public void setEcho(boolean b)
        {
           echoString = b;
        }
    }
    /**
     *  This class reads the output from the connected server
     *  until the required string is found. 
     */
    public class TelnetRead extends TelnetSubTask
    {
        private Integer timeout = null;
        public void execute(AntTelnetClient telnet) 
               throws BuildException
        {
            telnet.waitForString(taskString, timeout);
        }
        /**
         *  Override any default timeouts
         */
        public void setTimeout(Integer i)
        {
           this.timeout = i;
        }
        /**
         *  Sets the default timeout if none has been set already
         */
        public void setDefaultTimeout(Integer defaultTimeout)
        {
           if (timeout == null) {
              timeout = defaultTimeout;
           }
    }
    }
    /**
     *  This class handles the abstraction of the telnet protocol.
     *  Currently it is a wrapper around <a href="www.oroinc.com">ORO</a>'s 
     *  NetComponents
     */
    public class AntTelnetClient extends TelnetClient
    {
      /**
       * Read from the telnet session until the string we are 
       * waiting for is found 
       * @param s The string to wait on 
       */
      public void waitForString(String s)
      {
           waitForString(s, null);
      }

      /**
       * Read from the telnet session until the string we are 
       * waiting for is found or the timeout has been reached
       * @param s The string to wait on 
       * @param timeout The maximum number of seconds to wait
       */
      public void waitForString(String s, Integer timeout)
      {
        InputStream is =this.getInputStream();
        try {
          StringBuffer sb = new StringBuffer();
          if (timeout == null || timeout.intValue() == 0)
          {
              while (sb.toString().indexOf(s) == -1)
                  {
                      sb.append((char) is.read());
                  }
          }
          else
          {
              Calendar endTime = Calendar.getInstance(); 
              endTime.add(Calendar.SECOND,timeout.intValue());
              while ( sb.toString().indexOf(s) == -1)
              {
                  while (Calendar.getInstance().before(endTime) &&
                         is.available() == 0) {
                      Thread.sleep(250);
                  }
                  if (is.available() == 0) {
                      throw new BuildException("Response Timed-Out", getLocation());
                  }
                  sb.append((char) is.read());
              }
          }
          log(sb.toString(), Project.MSG_INFO);
        } catch (BuildException be)
        { 
            throw be;
        } catch (Exception e)
        { 
            throw new BuildException(e, getLocation());
        }
      }

    
      /**
       * Write this string to the telnet session.
       * @param echoString  Logs string sent
       */
      public void sendString(String s, boolean echoString)
      {
        OutputStream os =this.getOutputStream();
        try {
          os.write((s + "\n").getBytes());
          if (echoString) {
              log(s, Project.MSG_INFO);
          }
          os.flush();
        } catch (Exception e)
        { 
          throw new BuildException(e, getLocation());
        }
      }
    }
}
