/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 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 org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import com.oroinc.net.telnet.*;
import org.apache.tools.ant.BuildException;
import java.io.*;
import java.util.*;

import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;


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

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 <read> 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 <write> 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 boolean taskVerbose= false;
		protected int taskFlags=RE.MATCH_MULTILINE;
        protected String taskString= "";
		protected String taskRegex= "";
        public void execute(AntTelnetClient telnet) 
                throws BuildException
        {
            throw new BuildException("Shouldn't be able instantiate a SubTask directly");
        }
        public void addText(String s) { setString(s);}
        public void setString(String s)
        {
           taskString += s; 
        }

        public void setFlags(int i)
        {
           taskFlags = i; 
        }
        public void setRegex(String s)
        {
           taskRegex = s; 
        }
        public void setVerbose(boolean b)
        {
           taskVerbose = b; 
		   /*
		   if (taskVerbose)
			log(taskString+":"+taskRegex+":"+taskVerbose, Project.MSG_INFO);
		   */
        }
		
	}
    /**
     *  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
        {
			String	s;
			if (taskRegex.length()>0)
			{
				try{
					RE	r	=	new RE(taskRegex, taskFlags);
					s	=	telnet.waitForRegex(r, timeout);
					if (taskVerbose==true)
					{
						log("Matched:'"+s+"'", Project.MSG_INFO);
					}
				}
				catch(RESyntaxException re)
				{
					throw new BuildException(re.toString(), getLocation());
				}
			} else {
				s	=	telnet.waitForString(taskString, timeout);
				if (taskVerbose==true)
				{
					log("Matched:'"+s+"'", Project.MSG_INFO);
				}
			}
        }
        /**
         *  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 
       * @parm s The string to wait on 
       */
      public String waitForString(String s)
      {
           return waitForString(s, null);
      }

      /**
       * Read from the telnet session until the Regex we are 
       * waiting for is found 
       * @parm s The string to wait on 
       */
      public String waitForRegex(RE r)
      {
           return waitForRegex(r, null);
      }
      /**
       * Read from the telnet session until the string we are 
       * waiting for is found or the timeout has been reached
       * @parm s The string to wait on 
       * @parm timeout The maximum number of seconds to wait
       */
      public String waitForRegex(RE r, Integer timeout)
      {
        InputStream is =this.getInputStream();
        StringBuffer sb = new StringBuffer(1024);
        try {
          if (timeout == null || timeout.intValue() == 0)
          {
			  // This is for an infinite wait until all results has stopped arriving for 250ms
			  while (r.match(sb.toString()) == false)
                  {
					while (is.available() >0)
					{
                      sb.append((char) is.read());
					}
					Thread.sleep(250);
					
					if (is.available() == 0)
						break;
                  }
          }
          else
          {
			  // This loop times out regardless of data available
              Calendar endTime = Calendar.getInstance(); 
              endTime.add(Calendar.SECOND,timeout.intValue());
              while (r.match(sb.toString()) == false)
              {
					while (is.available() >0)
					  sb.append((char) is.read());
                  if (Calendar.getInstance().before(endTime)) {
                      Thread.sleep(250);
				  } else {
					  throw new BuildException("Response Timed-Out:'"+sb.toString()+"'", getLocation());
				  }
              }
          }
          log(sb.toString(), Project.MSG_INFO);
        } catch (BuildException be)
        { 
            throw be;
        } catch (Exception e)
        { 
            throw new BuildException(e, getLocation());
        }
		return sb.toString();
      }

    
      /**
       * Read from the telnet session until the string we are 
       * waiting for is found or the timeout has been reached
       * @parm s The string to wait on 
       * @parm timeout The maximum number of seconds to wait
       */
      public String waitForString(String s, Integer timeout)
      {
        InputStream is =this.getInputStream();
        StringBuffer sb = new StringBuffer(1024);
        try {
          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:'"+sb.toString()+"'", 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());
        }
		return sb.toString();
      }

    
      /**
       * Write this string to the telnet session.
       * @parm 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());
        }
      }
    }
}

