package nxm.sys.test.junit.net;

import java.net.*;
import nxm.sys.lib.*;
import nxm.sys.net.*;
import nxm.sys.test.junit.NMTestCase;

/** Test cases for DatagramData.
    @author  J. Czechowski IV
 */
public final class DatagramDataTest extends NMTestCase {

  private static boolean runFromMain = false; // false means run as junit, not from main()

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
   */
  public DatagramDataTest(String methodName) {
    super(methodName, null);
  }

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
      @param midas      The midas context testTo use when running the tests.
   */
  public DatagramDataTest(String methodName, Midas midas) {
    super(methodName, midas);
  }

  /**
   * Test the read() and other methods
   * @since 3.7.2 have test run as junit test
   */
  public void testRead () {
    boolean isNeXtOpt = Shell.isNeXtOpt();
    if (!isNeXtOpt) {
      try {
        doTest();
      } catch (Exception e) {
        fail( "Failed with exception:"+e);
      }
    }
  }

  public static class MulticastWriter extends Thread {
    String         name;
    int            count;
    double         pause;
    DatagramSocket socket;
    InetAddress    address;
    int            port;
    boolean        done;
    Throwable      error;

    public MulticastWriter (String name, int count, double pause, InetAddress address, MulticastSocket socket, int port) throws Exception {
      super("MulticastWriter: "+name);
      this.name    = name;
      this.count   = count;
      this.pause   = pause;
      this.socket  = socket;
      this.address = address;
      this.port    = port;
      this.done    = false;
      this.error   = null;

      socket.joinGroup(address);
    }

    public void run () {
      try {
        for (int i = 0; i < count; i++) {
          String xml = "<XML><MESSAGE NAME=\""+name+"\" COUNT=\""+i+"\"/></XML>";
          byte[] buf = xml.getBytes();
          DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);

          socket.send(packet);
          Time.sleep(pause);
        }
      } catch (Exception e) {
        error = e;
      }
      done = true;
    }

    public void waitFor () {
      while (!done) {
        Time.sleep(0.1);
      }
      if (error != null) {
        throw new RuntimeException("Error writing to socket on thread "+this, error);
      }
    }
  }

  @SuppressWarnings("deprecation") // Since 3.9.0
  public static class MulticastReader extends Thread {
    String         name;
    DatagramData   dgd;
    boolean        stop;
    boolean        done;
    Throwable      error;

    public MulticastReader (String name, String host, int port) {
      super("MulticastReader: "+name);
      this.name   = name;
      this.dgd    = new DatagramData(host, port);
      this.done   = false;
      this.error  = null;

//      dgd.setTimeOut(5000);
      dgd.setTimeOut(100);
      dgd.setSAP(false);
      dgd.setRTP(false);
      if (!dgd.open()) {
        throw new MidasException("Could not open DatagramData with host="+host+" port="+port);
      }
    }

    public void run () {
      try {
        String expName  = name;
        int    expCount = 0;

        while (!stop) {
          byte[] buf = dgd.read();

          if (buf != null) {
            String xml      = new String(buf);
            Table  tbl      = XmlFile.fastXmlToTable(xml, "_", "CDATA", 0, true);
            String actName  = tbl.getS("MESSAGE.NAME", "ERROR");
            int    actCount = tbl.getL("MESSAGE.COUNT", -1);

            if (DatagramDataTest.runFromMain) {
              // run not as junit, but from main() for debug
              if (!actName.equals(expName)) {
                Shell.warning(name+": expected "+expName+" "+expCount+" got "+actName+" "+actCount);
              }
              else if (actCount != expCount) {
                Shell.warning(name+": expected "+expName+" "+expCount+" got "+actName+" "+actCount);
              }
              else {
                Shell.info(name+": got "+actName+" "+actCount);
                expCount++;
              }
            }
            else {
              // run as junit test
              assertEquals("message name",expName,actName);
              assertEquals("message conut",expCount,actCount);
              expCount++;
            }
          }
        }
      } catch (Exception e) {
        error = e;
      }
      done = true;
    }

    public void waitFor () {
      stop = true;
      while (!done) {
        Time.sleep(0.1);
      }
      if (error != null) {
        throw new RuntimeException("Error writing to socket on thread "+this, error);
      }
    }
  }


  public static void main (String[] args) throws Exception {
    runFromMain = true;
    doTest();
  }

  public static void doTest () throws Exception {
    MulticastWriter[] writers = new MulticastWriter[4];
    MulticastReader[] readers = new MulticastReader[writers.length];

    int    count = 128;
    double pause = 0.01;

    for (int i = 0; i < writers.length; i++) {
      String name = "NUM"+i;
      String host = "232.0.1."+i;
      int    port = 9000;
      if (runFromMain) Shell.info("host = "+host+" port = "+port);

      InetAddress     address = InetAddress.getByName(host);
      MulticastSocket socket  = new MulticastSocket(port);

      writers[i] = new MulticastWriter(name, count, pause, address, socket, port);
      readers[i] = new MulticastReader(name, host, port);
    }

    if (runFromMain) Shell.info("START");
    for (int i = 0; i < readers.length; i++) {
      readers[i].start();
    }
    for (int i = 0; i < writers.length; i++) {
      writers[i].start();
    }
    for (int i = 0; i < writers.length; i++) {
      writers[i].waitFor();
    }
    for (int i = 0; i < readers.length; i++) {
      readers[i].waitFor();
    }

    if (runFromMain) Shell.info("DONE");
  }

}
