//______________________________________________________________________________
//   |       |
//   | knipp |           Knipp Medien und Kommunikation GmbH
//    -------
//
//
//     Knipp RRI Toolkit
//
//
//     Copyright (C) 2005 by Knipp Medien und Kommunikation GmbH
//
//     This library is free software; you can redistribute it and/or
//     modify it under the terms of the GNU Lesser General Public License 
//     as published by the Free Software Foundation; either version 2.1 
//     of the License, or any later version.
// 
//     This library 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 Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General
//     Public License along with this library; if not, write to the 
//     Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
//     Boston, MA 02111-1307 USA 
//
//
//     initial author:    Klaus Malorny
//
//     version:           1.3 (15112), 2011/05/27 16:00:14
//                        $Revision: 15112 $ 
//                        $Date: 2011-05-27 15:48:22 +0200 (Fri, 27 May 2011) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit.test;

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;

import java.util.Date;
import java.util.TimeZone;
import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.Queue;

import java.util.concurrent.ConcurrentLinkedQueue;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import de.knipp.rritoolkit.ChannelFactory;
import de.knipp.rritoolkit.Codec;
import de.knipp.rritoolkit.ContactData;
import de.knipp.rritoolkit.DefaultResponseFactory;
import de.knipp.rritoolkit.DomainData;
import de.knipp.rritoolkit.DomainData.Role;
import de.knipp.rritoolkit.PooledPort;
import de.knipp.rritoolkit.Port;
import de.knipp.rritoolkit.dns.DnsEntry;

import de.knipp.rritoolkit.dns.DnsEntryA;
import de.knipp.rritoolkit.dns.DnsEntryAAAA;
import de.knipp.rritoolkit.dns.DnsEntryDNSKEY;
import de.knipp.rritoolkit.dns.DnsEntryMX;
import de.knipp.rritoolkit.dns.DnsEntryNS;

import de.knipp.rritoolkit.message.Message;

import de.knipp.rritoolkit.request.AuthInfo1CreateRequest;
import de.knipp.rritoolkit.request.AuthInfo1DeleteRequest;
import de.knipp.rritoolkit.request.AuthInfo2CreateRequest;
import de.knipp.rritoolkit.request.ContactCheckRequest;
import de.knipp.rritoolkit.request.ContactCreateRequest;
import de.knipp.rritoolkit.request.ContactInfoRequest;
import de.knipp.rritoolkit.request.ContactUpdateRequest;
import de.knipp.rritoolkit.request.DomainChangeProvRequest;
import de.knipp.rritoolkit.request.DomainCheckRequest;
import de.knipp.rritoolkit.request.DomainCreateRequest;
import de.knipp.rritoolkit.request.DomainDeleteRequest;
import de.knipp.rritoolkit.request.DomainInfoRequest;
import de.knipp.rritoolkit.request.DomainMigrateDescrRequest;
import de.knipp.rritoolkit.request.DomainUpdateRequest;
import de.knipp.rritoolkit.request.QueueDeleteRequest;
import de.knipp.rritoolkit.request.QueueReadRequest;
import de.knipp.rritoolkit.request.Request;

import de.knipp.rritoolkit.response.DomainInfoResponse;
import de.knipp.rritoolkit.response.DomainMigrateDescrResponse;
import de.knipp.rritoolkit.response.DomainUpdateResponse;
import de.knipp.rritoolkit.response.QueueReadResponse;
import de.knipp.rritoolkit.response.Response;

import de.knipp.rritoolkit.tcp.TcpChannelFactory;
import de.knipp.rritoolkit.tcp.TcpParams;



/**
 *  small test program
 *
 *  @version  $Revision: 15112 $
 *  @author   Klaus Malorny
 */

public class Test
{
  /** the buffered reader while parsing */
  
  private BufferedReader batchReader;
  
  
  /** the last parsed arguments during parsing */
  
  private String[] parsedArgs;
  
  
  
  
  /** pattern to split lines */

  private static Pattern argPattern = Pattern.compile (
    "\\s*(?:\"([^\"]*)\"|(\\S+))\\s*");
  
  /**
   *  create a contact data instance from the given arguments
   *
   *  @param args              the arguments
   *  @return                  the instance or {@code null} if the creation
   *                           failed
   */
  
  private static ContactData createContactData (String... args)
  {
    ContactData contact = new ContactData ();
    int count = args.length;
    int i = 0;
    
    if (i >= count)
    {
      System.out.println ("missing handle");
      return null;
    }
    
    contact.setHandle (args[i++]);
    
    if (i >= count)
    {
      System.out.println ("missing type");
      return null;
    }
    
    contact.setType (ContactData.Type.fromToken (args[i++]));
    
    if (i >= count)
    {
      System.out.println ("missing name");
      return null;
    }
    
    contact.setName (args[i++]);
    
    if (i >= count)
    {
      System.out.println ("missing organization");
      return null;
    }
    
    List<String> list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setOrganisation (list);
    
    if (i >= count)
    {
      System.out.println ("missing address");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setAddress (list);
    
    if (i >= count)
    {
      System.out.println ("missing postal code");
      return null;
    }
    
    contact.setPostalCode (args[i++]);
    
    if (i >= count)
    {
      System.out.println ("missing city");
      return null;
    }
    
    contact.setCity (args[i++]);
    
    if (i >= count)
    {
      System.out.println ("missing country code");
      return null;
    }
    
    contact.setCountryCode (args[i++]);
  
    if (i >= count)
    {
      System.out.println ("missing phone numbers");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setPhoneNumbers (list);

    if (i >= count)
    {
      System.out.println ("missing fax numbers");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setFaxNumbers (list);
    
    if (i >= count)
    {
      System.out.println ("missing e-mail addresses");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setEMailAddresses (list);
    
    if (i >= count)
    {
      System.out.println ("missing sip addresses");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setSipAddresses (list);
    
    if (i >= count)
    {
      System.out.println ("missing remarks");
      return null;
    }
    
    list = new ArrayList<String> ();
    while (i < count && !args[i].equals (".")) list.add (args[i++]);
    i++;
    contact.setRemarks (list);
 
    // tbi: disclose
    
    return contact;
  }
  
  
  
  
  /**
   *  test method for the contact create request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */

  private static void testContactCreate (Port port, String[] args)
    throws Exception
  {
    ContactData data = createContactData (args);
    if (data == null) return;
    
    ContactCreateRequest req = new ContactCreateRequest (data);
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  

  
  
  
  /**
   *  test method for the contact create request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */

  private static void testContactUpdate (Port port, String[] args)
    throws Exception
  {
    ContactData data = createContactData (args);
    if (data == null) return;
    
    ContactUpdateRequest req = new ContactUpdateRequest (data);
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  
  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testContactCheck (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("Handle required");
      return;
    }
    
    ContactCheckRequest req = new ContactCheckRequest (args[0]);
    
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  

  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testContactInfo (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("Handle required");
      return;
    }
    
    ContactInfoRequest req = new ContactInfoRequest (args[0]);
    
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  
  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainCheck (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("domain name required");
      return;
    }
    
    DomainCheckRequest req = new DomainCheckRequest ();
    
    if (args.length == 1)
      req.setDomainName (args[0]);
     else
      req.setDomainName (args[0], args[1]);
    
    Response resp = req.execute (port);
    System.out.println (resp);
  }


  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainInfo (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("domain name required");
      return;
    }
    
    DomainInfoRequest req = new DomainInfoRequest ();
    
    if (args.length == 1)
      req.setDomainName (args[0]);
     else
      req.setDomainName (args[0], args[1]);
    
    int pos = 2;
    
    while (args.length > pos)
    {
      if (args[pos].equals ("rec")) 
      {
        req.setWithRecursion (true);
      
      } else if (args[pos].equals ("prov"))
      {
        req.setWithProvider (true);
      
      } else
      {
        System.err.println ("unknown option \"" + args[pos] + "\"");
      }
      
      pos++;
    }
    
    Response resp = req.execute (port);
    System.out.println (resp);
  }


  
  
  
  /**
   *  create a domain data instance from the given arguments
   *
   *  @param withAuthInfo      whether AuthInfo may be provided
   *  @param args              the arguments
   *  @return                  the instance or {@code null} if failed
   *                           for some reason
   */
  
  private static DomainData createDomainData (boolean withAuthInfo,
    String... args)
  {
    if (withAuthInfo)
    {
      if (args.length != 7 && args.length != 9)
      {
        System.err.println ("<dn> <idn> <holder> <admin> " +
          "<tech> <zone> ( <authinfo> | - ) [ <ns1> <ns2> ]");

        return null;
      }
    } else
    {
      if (args.length != 6 && args.length != 8)
      {
        System.err.println ("<dn> <idn> <holder> <admin> " +
          "<tech> <zone> [ <ns1> <ns2> ]");

        return null;
      }
    }

    int i = 0;
    DomainData ddata = new DomainData ();
    ddata.setDomainName (args[i], args[i + 1]);
    i += 2;

    ContactData cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Holder, cdata);
      
    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Admin, cdata);

    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Tech, cdata);

    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Zone, cdata);

    if (withAuthInfo)
    {
      String temp = args[i++];
      if (!temp.equals ("-")) ddata.setChangeProvAuthInfo (temp);
    }

    if (args.length > i)
    {
      ddata.addDnsEntry (new DnsEntryNS (args[i++]));
      ddata.addDnsEntry (new DnsEntryNS (args[i++]));
    
    } else
    {
      ddata.addDnsEntry (
        new DnsEntryA ("www." + args[1], "195.253.6.159"));

      ddata.addDnsEntry (
        new DnsEntryAAAA ("www." + args[1], "2a01:5b0:0:29::9f"));
      
      ddata.addDnsEntry (
        new DnsEntryMX (args[1], 10, "mail.knipp.de"));
    }
    
    return ddata;
  }
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainCreate (Port port, String[] args)
    throws Exception
  {
    DomainData ddata = createDomainData (false, args);
    if (ddata == null) return;
    
    DomainCreateRequest req = new DomainCreateRequest (ddata);
    Response resp = req.execute (port);
    System.out.println (resp);
  }    

  /**
   *  create a domain data instance from the given arguments
   *
   *  @param withAuthInfo      whether AuthInfo may be provided
   *  @param args              the arguments
   *  @return                  the instance or {@code null} if failed
   *                           for some reason
   */

  private static DomainData createDomainDataExt (boolean withAuthInfo,
    String... args)
  {
    if (withAuthInfo)
    {
      if (args.length < 9)
      {
        System.err.println ("<dn> <idn> <holder> <admin> " +
          "<tech> <zone> ( <authinfo> | - ) <ns> [<ip> ... '.'] ...");

        return null;
      }
    } else
    {
      if (args.length < 8)
      {
        System.err.println ("<dn> <idn> <holder> <admin> " +
          "<tech> <zone> <ns> [<ip> ... '.'] ...");

        return null;
      }
    }

    int i = 0;
    DomainData ddata = new DomainData ();
    ddata.setDomainName (args[i], args[i + 1]);
    i += 2;

    ContactData cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Holder, cdata);

    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Admin, cdata);

    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Tech, cdata);

    cdata = new ContactData ();
    cdata.setHandle (args[i++]);
    ddata.addContact (DomainData.Role.Zone, cdata);

    if (withAuthInfo)
    {
      String temp = args[i++];
      if (!temp.equals ("-")) ddata.setChangeProvAuthInfo (temp);
    }

    while (i < args.length)
    {
      String name = args[i++];
      List<String> addrs = new ArrayList<String> ();

      while (i < args.length && !args[i].equals ("."))
        addrs.add (args[i++]);

      i++;

      String[] array = new String[addrs.size ()];
      addrs.toArray (array);
      ddata.addDnsEntry (new DnsEntryNS (name, array));
    }

    return ddata;
  }

  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */

  private static void testDomainCreateExt (Port port, String[] args)
    throws Exception
  {
    DomainData ddata = createDomainDataExt (false, args);
    if (ddata == null) return;

    DomainCreateRequest req = new DomainCreateRequest (ddata);
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainChangeProv (Port port, String[] args)
    throws Exception
  {
    DomainData ddata = createDomainDataExt (true, args);
    if (ddata == null) return;
    
    DomainChangeProvRequest req = new DomainChangeProvRequest (ddata);
    Response resp = req.execute (port);
    System.out.println (resp);
  }    

  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainDelete (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("domain name required");
      return;
    }
    
    DomainInfoRequest ireq = new DomainInfoRequest ();
    
    if (args.length == 1)
      ireq.setDomainName (args[0]);
     else
      ireq.setDomainName (args[0], args[1]);
    
    DomainInfoResponse iresp = ireq.execute (port);
    
    System.out.println (iresp);
    
    if (!iresp.isSuccess ())
    {
      System.out.println ("could not retrieve domain information");
      return;
    }
    
    DomainData ddata = iresp.getDomain ();
    
    // delete zone contact for domains without name servers
    
    List<ContactData> list = ddata.getContacts (Role.Zone);
    
    if (list.isEmpty () || list.get (0).getHandle () == null)
      ddata.clearContacts (Role.Zone);

    DomainDeleteRequest dreq = new DomainDeleteRequest (ddata);
    Response dresp = dreq.execute (port);
    System.out.println (dresp);
  }    
  
  

  

  
  
  /**
   *  test method for the domain check request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testDomainMigrateDescr (Port port, String[] args)
    throws Exception
  {
    if (args.length < 2)
    {
      System.err.println ("domainname [ idn ] handle");
      return;
    }
    
    DomainMigrateDescrRequest req = new DomainMigrateDescrRequest ();
    
    if (args.length == 2)
    {
      req.setDomainName (args[0]);
      req.setHolderHandle (args[1]);
    
    } else
    {
      req.setDomainName (args[0], args[1]);
      req.setHolderHandle (args[2]);
    }
    
    DomainMigrateDescrResponse resp = req.execute (port);
    
    System.out.println (resp);
  }    
  
   
  
  
  /**
   *  test method for the queue read request
   *
   *  @param port              the port to use
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testQueueRead (Port port, String[] args)
    throws Exception
  {
    Message.Type type = null;
    
    if (args.length > 0)
    {
      type = Message.Type.fromToken (args[0]);
      
      if (type == null)
      {
        System.err.println ("unknown type \"" + args[0] + "\" specified");
        return;
      }
    }
    
    Request req = new QueueReadRequest (type);
    
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  
  
  
  /**
   *  test method to remove a message from the queue
   *
   *  @param port              the port
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testQueueDelete (Port port, String[] args)
    throws Exception
  {
    if (args.length < 1)
    {
      System.err.println ("message ID required");
      return;
    }
    
    Request req = new QueueDeleteRequest (args[0]);
    Response resp = req.execute (port);
    System.out.println (resp);
  }
  
  
  
  /**
   *  test method that removes all messages (of the given type if specified)
   *
   *  @param port              the port
   *  @param args              additional arguments
   *  @throws Exception        on any exception
   */
  
  private static void testQueueClear (Port port, String[] args)
    throws Exception
  {
    Message.Type type = null;
    
    if (args.length > 0)
    {
      type = Message.Type.fromToken (args[0]);
      
      if (type == null)
      {
        System.err.println ("unknown type \"" + args[0] + "\" specified");
        return;
      }
    }
  
    while (true)
    {
      QueueReadRequest req = new QueueReadRequest (type);
      QueueReadResponse resp = req.execute (port);
      
      if (resp.isSuccess ())
      {
        Message msg = resp.getMessage ();
        if (msg == null) break;
        
        Request req2 = 
          new QueueDeleteRequest (msg.getMessageID (), msg.getType ());
        
        Response resp2 = req2.execute (port);
        
        if (!resp2.isSuccess ())
        {
          System.err.println ("delete failed:\n" + resp2);
          break;
        }
        
      } else
      {
        System.err.println ("queue read failed:\n" + resp);
        break;
      }
    }
  }


  /**
   *  test method for the creation of an AuthInfo 1
   *
   *  @param port              the port
   *  @param args              the arguments
   *  @throws Exception        on any exception
   */

  private static void testAuthInfo1Create (Port port, String... args)
    throws Exception
  {
    if (args.length != 4)
    {
      System.err.println ("<dn> <idn> <authinfo> <expire-date>");
      return;
    }

    DateFormat df = new SimpleDateFormat ("yyyyMMdd-HHmmss");
    df.setTimeZone (TimeZone.getTimeZone ("Europe/Berlin"));
    Date date;
    
    try
    {
      date = df.parse (args[3]);

    } catch (ParseException e)
    {
      System.err.println ("Invalid date, must be in format YYYYMMDD-HHMMSS");
      return;
    }

    Request req = new AuthInfo1CreateRequest (args[0], args[1], args[2], date);
    Response resp = req.execute (port);
    System.out.println (resp);
  }


  /**
   *  test method for the deletion of the AuthInfo 1
   *
   *  @param port              the port
   *  @param args              the arguments
   *  @throws Exception        on any exception
   */

  private static void testAuthInfo1Delete (Port port, String... args)
    throws Exception
  {
    if (args.length != 2)
    {
      System.err.println ("<dn> <idn>");
      return;
    }

    Request req = new AuthInfo1DeleteRequest (args[0], args[1]);
    Response resp = req.execute (port);
    System.out.println (resp);
  }




  /**
   *  test method for the creation of an AuthInfo 2
   *
   *  @param port              the port
   *  @param args              the arguments
   *  @throws Exception        on any exception
   */

  private static void testAuthInfo2Create (Port port, String... args)
    throws Exception
  {
    if (args.length != 2)
    {
      System.err.println ("<dn> <idn>");
      return;
    }

    Request req = new AuthInfo2CreateRequest (args[0], args[1]);
    Response resp = req.execute (port);
    System.out.println (resp);
  }



  /**
   *  test method for the change of DNSKEY data
   *
   *  @param port              the port
   *  @param args              the arguments
   *  @throws Exception        on any exception
   */

  private static void testDnsKeyUpdate (Port port, String... args)
    throws Exception
  {
    if (args.length % 4 != 2)
    {
      System.err.println ("<dn> <idn> [ <flags> <proto> <alg> <pubkey> ] ...");
      return;
    }

    String name = args[0];
    String encodedName = args[1];

    List<DnsEntryDNSKEY> keys = new ArrayList<DnsEntryDNSKEY> ();

    for (int i = 2; i < args.length; i += 4)
    {
      int flags;
      int alg;
      int proto;

      try
      {
        flags = Integer.parseInt (args[i]);
        proto = Integer.parseInt (args[i + 1]);
        alg = Integer.parseInt (args[i + 2]);

      } catch (NumberFormatException e)
      {
        System.err.println ("either flags, algorithm or protocol is invalid");
        e.printStackTrace (System.err);
        return;
      }

      keys.add (new DnsEntryDNSKEY (flags, proto, alg, args[i + 3]));
    }

    DomainInfoRequest infoReq = new DomainInfoRequest ();
    infoReq.setDomainName (name, encodedName);
    DomainInfoResponse infoResp = infoReq.execute (port);

    System.out.println (infoResp);

    if (!infoResp.isSuccess ())
    {
      System.err.println ("info request failed, aborting operation");
      return;
    }

    DomainData currentData = infoResp.getDomain ();
    DomainData newData = new DomainData (currentData, true);
    newData.clearDnsEntries ();

    for (DnsEntry entry: currentData.getDnsEntries ())
      if (!(entry instanceof DnsEntryDNSKEY))
        newData.addDnsEntry (entry);

    newData.addDnsEntries (keys);

    DomainUpdateRequest updateReq = new DomainUpdateRequest (newData);
    DomainUpdateResponse updateResp = updateReq.execute (port);

    System.out.println (updateResp);
  }


  /**
   *  wait for a certain time
   *
   *  @param args              the arguments
   *  @throws Exception        on exceptions
   */

  private static void wait (String[] args)
    throws Exception
  {
    if (args.length != 1)
    {
      System.err.println ("<duration msecs>");
      return;
    }

    long waitTime;

    try
    {
      waitTime = Long.parseLong (args[0]);

    } catch (NumberFormatException e)
    {
      System.err.println ("invalid duration");
      e.printStackTrace (System.err);
      return;
    }

    try
    {
      Thread.sleep (waitTime);

    } catch (InterruptedException e)
    {
      // ignored
    }
  }

  
  /**
   *  execute a command
   *
   *  @param port              the port
   *  @param args              the arguments
   *  @param offset            the index to the first argument to use
   *  @throws Exception        on exceptions
   */
  
  private static void execute (Port port, String[] args, int offset)
    throws Exception
  {
    String command = args[offset];
    String[] commandArgs = new String[args.length - offset - 1];
    
    System.arraycopy (args, offset + 1, commandArgs, 0, 
      args.length - offset - 1);

    if (command.equals ("queue-read"))
    {
      testQueueRead (port, commandArgs);

    } else if (command.equals ("queue-delete"))
    {
      testQueueDelete (port, commandArgs);
      
    } else if (command.equals ("QUEUE-CLEAR"))  // upper case for safety
    {
      testQueueClear (port, commandArgs);
      
    } else if (command.equals ("domain-check"))
    {
      testDomainCheck (port, commandArgs);

    } else if (command.equals ("domain-info"))
    {
      testDomainInfo (port, commandArgs);

    } else if (command.equals ("domain-create"))
    {
      testDomainCreate (port, commandArgs);

    } else if (command.equals ("domain-create-ext"))
    {
      testDomainCreateExt (port, commandArgs);

    } else if (command.equals ("domain-change-prov"))
    {
      testDomainChangeProv (port, commandArgs);

    } else if (command.equals ("domain-delete"))
    {
      testDomainDelete (port, commandArgs);

    } else if (command.equals ("domain-migrate-descr"))
    {
      testDomainMigrateDescr (port, commandArgs);
      
    } else if (command.equals ("contact-create"))
    {
      testContactCreate (port, commandArgs);

    } else if (command.equals ("contact-update"))
    {
      testContactUpdate (port, commandArgs);

    } else if (command.equals ("contact-check"))
    {
      testContactCheck (port, commandArgs);

    } else if (command.equals ("contact-info"))
    {
      testContactInfo (port, commandArgs);

    } else if (command.equals ("authinfo1-create"))
    {
      testAuthInfo1Create (port, commandArgs);

    } else if (command.equals ("authinfo1-delete"))
    {
      testAuthInfo1Delete (port, commandArgs);

    } else if (command.equals ("authinfo2-create"))
    {
      testAuthInfo2Create (port, commandArgs);

    } else if (command.equals ("dnskey-update"))
    {
      testDnsKeyUpdate (port, commandArgs);

    } else if (command.equals ("wait"))
    {
      wait (commandArgs);

    } else
    {
      System.err.println ("unknown command \"" + command + "\" specified");
    }
  }
  
  
  
  /**
   *  split a line into arguments. Quotes are observed
   *
   *  @param line             the line
   *  @return                 the parsed arguments
   */
  
  private String[] splitArgs (String line)
  {
    Matcher m = argPattern.matcher (line);
    List<String> args = new ArrayList<String> ();
    int pos = 0;
    
    while (m.find (pos))
    {
      String temp = m.group (1);
      if (temp == null) temp = m.group (2);
      args.add (temp);
      
      pos = m.end ();
    }
    
    String[] argArray = new String[args.size ()];
    args.toArray (argArray);
    return argArray;
  }
  
  
  
  
  /**
   *  parse a section of the batch file
   *
   *  @param stopWords                the words to stop parsing
   *  @return                         the parsed command or {@code null}
   *                                  if end of input has been encountered
   *                                  without reading a single command
   *  @throws IOException             on exceptions while reading
   */
  
  private Command parseBatchSection (String... stopWords)
    throws IOException
  {
    List<Command> commands = new ArrayList<Command> ();
    
    while (true)
    {
      while (parsedArgs == null)
      {
        String line = batchReader.readLine ();
        
        if (line == null)
        {
          if (commands.isEmpty ()) return null;
          break;
        }
        
        parsedArgs = splitArgs (line);
      }
      
      if (parsedArgs == null) break;
      if (parsedArgs.length == 0)
      {
        parsedArgs = null;
        continue;
      }
      
      String token = parsedArgs[0];
      boolean found = false;
      
      for (String temp: stopWords)
      {
        if (temp.equals (token))
        {
          found = true;
          break;
        }
      }
      
      if (found) break;
      
      if (token.equals ("[#"))
      { 
        int repeatCount = 1;
        
        if (parsedArgs.length > 1)
        {
          try
          {
            repeatCount = Integer.parseInt (parsedArgs[1]);
            
          } catch (NumberFormatException e)
          {
            // ignored
          }
        }
        
        parsedArgs = null;
        Command tempCommand = parseBatchSection ("#]");
        
        if (tempCommand != null) 
          commands.add (new RepeatCommand (tempCommand, repeatCount));
        
        parsedArgs = null;
        
      } else if (token.equals ("[|"))
      {
        int threadCount = 1;

        if (parsedArgs.length > 1)
        {
          try
          {
            threadCount = Integer.parseInt (parsedArgs[1]);
            
          } catch (NumberFormatException e)
          {
            // ignored
          }
        }
        
        List<Command> list = new ArrayList<Command> ();
        
        while (true)
        {
          parsedArgs = null;
          Command tempCommand = parseBatchSection ("|", "|]");
          
          if (tempCommand == null) break;
          list.add (tempCommand);
          
          if (parsedArgs == null || parsedArgs.length == 0 ||
            !parsedArgs[0].equals ("|")) break;
        }
        
        Command[] c = new Command[list.size ()];
        list.toArray (c);
        commands.add (new ParallelCommand (c, threadCount));
        
      } else
      {
        // normal command
        
        commands.add (new SingleCommand (parsedArgs));
        
      }
      
      parsedArgs = null;
    }
    
    if (commands.size () == 1) return commands.get (0);
    
    Command[] c = new Command[commands.size ()];
    commands.toArray (c);
    return new SequentialCommand (c);
  }
  
  
  
  
  /**
   *  parse the given batch file
   *
   *  @param reader            the reader
   *  @return                  the command
   *  @throws IOException      on i/o exceptions
   */
  
  private Command parseBatchFile (BufferedReader reader)
    throws IOException
  {
    batchReader = reader;
    parsedArgs = null;
    
    try
    {
      return parseBatchSection ();
      
    } finally
    {
      batchReader = null;
      parsedArgs = null;
    }
  }
  
  
  /**
   *  the main program
   *
   *  @param args              the command line arguments
   */
  
  private void doMain (String[] args)
  {
    if (args.length < 2)
    {
      System.err.println ("arguments: <properties> <command> ...");
      return;
    }
    
    try
    {
      Properties props = new Properties ();
      props.load (new FileInputStream (args[0]));
      
      TcpParams params = new TcpParams ();
      params.setupFromProperties (props,  "tcp.");
      ChannelFactory factory = new TcpChannelFactory (params);
      
      factory = new DebugChannelFactory (factory);
      
      String temp = props.getProperty ("xml");
      
      boolean useXml = "yes".equalsIgnoreCase (temp) || 
        "true".equalsIgnoreCase (temp);
      
      String userName = props.getProperty ("user");
      String password = props.getProperty ("password");

      Codec codec = new Codec (DefaultResponseFactory.getFactory (), useXml);

      PooledPort pport = new PooledPort (factory, codec, userName, password, 
        1, 2, 100);

      Port port = new DebugPort (pport);
      
      String command = args[1];
      
      if (command.startsWith ("@"))
      {
        String filename = command.substring (1);
        BufferedReader reader = null;
        
        if (filename.equals ("-"))
          reader = new BufferedReader (new InputStreamReader (System.in));
         else
          reader = new BufferedReader (new FileReader (filename));
        
        Command c = parseBatchFile (reader);
        
        long start = System.currentTimeMillis ();
        
        if (c != null) c.execute (port);
        
        long end = System.currentTimeMillis ();
        
        System.err.println ("execution time: " + (end - start) + "ms");
        
      } else
      {
        execute (port, args, 1);
      }
      
      pport.shutdown ();
      
    } catch (Exception e)
    {
      e.printStackTrace (System.err);
    }
  }
  
  
  
  /**
   *  main program
   *
   *  @param args              the command line arguments
   */
  
  public static void main (String[] args)
  {
    Test t = new Test ();
    t.doMain (args);
  }
  
  

  /**
   *  small interface to a class that executes commands on a given port
   *
   *  @version  $Revision: 15112 $
   *  @author   Klaus Malorny
   */
  
  private interface Command
  {
    /**
     *  executes the command
     *
     *  @param port                   the port
     *  @throws Exception             if the execution failed for some reason
     */
    
    public void execute (Port port)
      throws Exception;
  }
  
  
  
  /**
   *  class that executes a single command
   *
   *  @version  $Revision: 15112 $
   *  @author   Klaus Malorny
   */
  
  private static class SingleCommand implements Command
  {
    /** the command */
    
    private String[] command;
    
    
    
    /**
     *  constructor
     *
     *  @param theCommand              the command to execute
     */
    
    public SingleCommand (String[] theCommand)
    {
      command = theCommand;
    }
    
    /**
     *  execute the command
     *
     *  @param port                   the port
     *  @throws Exception             if the execution failed for some reason
     */

    @Override
    public void execute (Port port)
      throws Exception
    {
      Test.execute (port, command, 0);
    }
    
  }
  
  
  
  /**
   *  class that repeats a command a given number of times
   *
   *  @version  $Revision: 15112 $
   *  @author   Klaus Malorny
   */
  
  private static class RepeatCommand implements Command
  {
    /** the command to repeat */
    
    private Command command;
    
    
    /** the number of repetitions */
    
    private int repeatCount;
    
    
    /**
     *  constructor
     *
     *  @param theCommand                  the command
     *  @param theRepeatCount              the number of repetitions
     */
    
    public RepeatCommand (Command theCommand, int theRepeatCount)
    {
      command = theCommand;
      repeatCount = theRepeatCount;
    }

    /**
     *  execute the command
     *
     *  @param port                   the port
     *  @throws Exception             if the execution failed for some reason
     */

    @Override
    public void execute (Port port)
      throws Exception
    {
      for (int i = 0; i < repeatCount; i++)
        command.execute (port);
    }
  }
  
  
  /**
   *  class that executes the given commands sequentially
   *
   *  @version  $Revision: 15112 $
   *  @author   Klaus Malorny
   */
  
  private static class SequentialCommand implements Command
  {
    /** the commands to execute */
    
    private Command[] commands;
    
    
    /**
     *  constructor
     *
     *  @param theCommands             the commands to execute
     */
    
    public SequentialCommand (Command[] theCommands)
    {
      commands = theCommands;
    }

    
    
    /**
     *  execute the command
     *
     *  @param port                   the port
     *  @throws Exception             if the execution failed for some reason
     */

    @Override
    public void execute (Port port)
      throws Exception
    {
      for (Command command: commands) command.execute (port);
    }
  }
  
  
  
  /**
   *  class that executes the given commands in parallel
   *
   *  @version   $Revision: 15112 $
   *  @author    Klaus Malorny
   */
  
  private static class ParallelCommand implements Command
  {
    /** the commands to execute */
    
    private Command[] commands;
    
    
    /** the number of threads to use */
    
    private int threadCount;
    
    
    /**
     *  constructor
     *
     *  @param theCommands             the commands to execute
     *  @param theThreadCount          the maximum number of threads to use
     */
    
    public ParallelCommand (Command[] theCommands, int theThreadCount)
    {
      commands = theCommands;
      threadCount = theThreadCount;
    }
    
    
    
    /**
     *  execute the command
     *
     *  @param port                   the port
     *  @throws Exception             if the execution failed for some reason
     */

    @Override
    public void execute (final Port port)
      throws Exception
    {
      if (commands.length == 0) return;
    
      final Queue<Command> queue = new ConcurrentLinkedQueue<Command> ();
      for (Command command: commands) queue.add (command);
      
      int count = Math.min (threadCount, commands.length);
      
      Runnable r = new Runnable ()
        {
          @Override
          public void run ()
          {
            Command command;

            while ((command = queue.poll ()) != null)
            {
              try
              {
                command.execute (port);
              } catch (Exception e)
              {
                e.printStackTrace (System.err);
              }
            }
          }
        };
    
      Thread[] threads = new Thread[count - 1];
      
      for (int i = 0; i < count - 1; i++)
       threads[i] = new Thread (r);
       
      for (Thread t: threads) t.start ();
      r.run ();
      for (Thread t: threads) t.join ();
    }
  }  
    
    
  
}

