// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid.analysis;

import de.caff.asteroid.Communication;
import de.caff.asteroid.server.DatagramListener;
import de.caff.asteroid.server.DatagramSender;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.util.concurrent.LinkedBlockingQueue;

/**
 *  Class used to save the communication (in and outgoing datagrams) to a file.
 *  The file format is very simple:
 *  The first seven bytes are 'A', 'S', 'T', '-', 'D', 'M', 'P',
 *  following by a byte with the version number of the file format
 *  (currently 0).
 *  After that there are either an 'I' (incoming datagram) followed by a timestamp followed
 *  by the 1026 bytes of the incoming datagram or an 'O' (oh, outgoing datagram), followed by
 *  a timestamp, followed by the 8 bytes of an outgoing datagram, as long as data is saved.
 *  The timestamp is saved lowest byte, second-lowest byte, ... ,highest byte, so
 *  <code>0x8877665544332211L</code> is saved as
 *  <code>0x11, 0x22, ..., 0x88</code>.
 *
 *  This class is part of a solution for a
 *  <a href="http://www.heise.de/ct/creativ/08/02/details/">competition by the German computer magazine c't</a>.
 */
public class DatagramDumper
        implements DatagramListener,
                   Runnable, 
                   FileFormat
{

  /**
   *  Helper class holding one queue element with
   *  data and timestamp.
   */
  private static class QueueElement
  {
    private long timestamp;
    private byte[] data;

    /**
     * Constructor.
     * @param packet packet to add to queue
     */
    private QueueElement(DatagramPacket packet)
    {
      timestamp = System.currentTimeMillis();
      data = new byte[packet.getLength()];
      System.arraycopy( packet.getData(), 0, data, 0, packet.getLength());
    }

    public void writeTo(OutputStream os)
            throws IOException
    {
      os.write(data.length == MAME_DATAGRAM_SIZE ?
              INCOMING_MARKER :
              OUTGOING_MARKER);
      long ts = timestamp;
      for (int b = Long.SIZE/Byte.SIZE;  b > 0;  --b) {
        os.write(((int)ts) & 0xFF);
        ts >>= Byte.SIZE;
      }
      os.write(data);
    }
  }
  /** The output stream. */
  private OutputStream out;
  /** The queue where we move the datagrams from the communcation thread to the write thread. */
  private LinkedBlockingQueue<QueueElement> queue = new LinkedBlockingQueue<QueueElement>();

  /**
   *  Constructor.
   *  @param filename the file where to write the results
   *  @throws IOException on file open/write problems
   */
  public DatagramDumper(String filename)
          throws IOException
  {
    out = new FileOutputStream(filename); // NOTE: deliberately unbuffered
    out.write(DECAFF_INTRO);
  }

  /**
   *  Insert a datagram into the queue.
   *  @param packet datagram packet
   */
  private void insertDatagram(DatagramPacket packet)
  {
    if (out != null) {
      try {
        queue.put(new QueueElement(packet));
      } catch (InterruptedException e) {
        // what now?
        e.printStackTrace(System.err);
      }
    }
  }

  /**
   * Called when an incoming datagram was received.
   * <p/>
   * This method is called in the communication thread.
   *
   * @param packet packet received
   * @param sender datagram sender used for answers
   */
  public void datagramReceived(DatagramPacket packet, DatagramSender sender)
  {
    insertDatagram(packet);
  }

  /**
   * Called when an outgoing datagram was sent.
   * <p/>
   * This method is called in the communication thread.
   *
   * @param packet sent packet
   */
  public void datagramSent(DatagramPacket packet)
  {
    insertDatagram(packet);
  }

  /**
   * Run the threaded code.
   * @see Thread#run()
   */
  public void run()
  {
    try {
      while (true) {
        try {
          QueueElement element = queue.take();
          element.writeTo(out);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    } catch (IOException e) {
      e.printStackTrace(System.err);
      // because this is probably file system full or similar,
      // we just drop out
      System.err.println("Dumper dies due to i/o error!");
      try {
        out.close();
      } catch (IOException e1) {
        // okay
      } finally {
        // this indicates no more need to fill the quue
        out = null;
      }
    }
  }

  /**
   *  Create a dumper in an own thread and register it with the communication.
   *  @param com      communication
   *  @param filename file to dump to
   *  @return the dumper's thread
   *  @throws IOException on file open and (early) file write errors
   */
  public static Thread registerDumper(Communication com, String filename)
          throws IOException
  {
    DatagramDumper dumper = new DatagramDumper(filename);
    com.addDatagramListener(dumper);
    Thread thread = new Thread(dumper, "dumper");
    thread.start();
    thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
        public void uncaughtException(Thread t, Throwable e)
        {
          System.err.println("Dumper thread passed out.");
          e.printStackTrace(System.err);
          System.exit(1);
        }
      });
    return thread;
  }
}
