package brn.sim.data.dump;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;

import jist.runtime.JistAPI;
import jist.swans.misc.Event;
import jist.swans.misc.Message;
import jist.swans.radio.AbstractRadio.RadioDropEvent;
import jist.swans.radio.AbstractRadio.RadioReceiveEvent;
import jist.swans.radio.AbstractRadio.RadioSendEvent;

public class RadioDump {

  // ////////////////////////////////////////////////////////////////////
  // Constants

  public static final String KEY_MSG_DROPREASON = "msg_drop_reason";

  public static final String KEY_MSGSIZE = "msg_size";

  public static final String KEY_MSGNAME = "msg_name";

  public static final String KEY_TIME = "time";

  public static final String KEY_MSGHASH = "msg_hash";

  public static final String KEY_EVENT = "event";

  public static final String KEY_NODEID = "node_id";

  public static final String KEY_MSGCONTENT = "msg_content";

  public static final String KEY_LEVEL = "level";

  public static final String KEY_RADIO_WAVELENGTH = "radio_wavelength";

  public static final String KEY_RADIO_THRESHOLD = "radio_threshold_mW";

  public static final String KEY_RADIO_SENSITIVITY = "radio_sensitivity_mW";

  public static final String KEY_RADIO_POWER = "radio_power";

  public static final String KEY_RADIO_GAIN = "radio_gain";

  public static final String KEY_RADIO_BACKGROUND = "radio_background_mW";

  public static final String KEY_RADIO_BANDWIDTH = "radio_bandwidth";

  public static final String KEY_MSGPOWER = "msg_power_mW";

  public static final String KEY_MSGDURATION = "msg_duration";

  public static final String KEY_RADIOID = "radio_id";

  public static final long DEFAULT_DROP_MSG_DURATION = 100;

  public static final String EVENT_DROP = "d";

  public static final String EVENT_SEND = "s";

  public static final String EVENT_RECV = "r";

// TODO
//  public static final String EVENT_ENQ = "+";
//
//  public static final String EVENT_DEQ = "-";


  public static final String LEVEL_RADIO = "radio";


  public static final int METHOD_XML = 0;

  public static final int METHOD_PLAIN = 1;


  /**
   * Do not trace anything.
   */
  public final static int LEVEL_OFF = 0;

  /**
   * Trace only base information
   */
  public final static int LEVEL_BASE = 1;

  /**
   * Trace all information
   */
  public final static int LEVEL_ALL = 2;

  // ////////////////////////////////////////////////////////////////////
  // Locals

  /**
   * singleton reference to the outputter
   */
  protected Outputter outputter = null;

  /**
   * The level to produce traces.
   */
  protected int traceLevel = LEVEL_OFF;

  // ////////////////////////////////////////////////////////////////////
  // Initialization

  /**
   * Constructs the tracer object.
   *
   * @param writer the writer to use
   * @param method the output method to use (plain/xml)
   * @param level the tracing level (off/base/all)
   * @throws IOException
   */
  public RadioDump(Writer writer, int method, int level) throws IOException {
    init(writer, method, level);
  }

  public int getTraceLevel() {
    return traceLevel;
  }

  /**
   * Inits the tracer system.
   *
   * @param writer the writer to use
   * @param method the output method to use (plain/xml)
   * @param level the tracing level (off/base/all)
   * @throws IOException If the io makes trouble.
   */
  protected void init(Writer writer, int method, int level)
          throws IOException {
    switch (method) {
//      case METHOD_XML:
//        outputter = new XMLOutputter();
//        break;
      case METHOD_PLAIN:
        outputter = new PlainOutputter();
        break;
      default:
        outputter = new PlainOutputter();
        break;
    }
    outputter.init(writer);
    traceLevel = level;

    Event.addHandler(RadioReceiveEvent.class, new RadioReceiveHandler());
    Event.addHandler(RadioSendEvent.class, new RadioSendHandler());
    Event.addHandler(RadioDropEvent.class, new RadioDropHandler());
  }

  /**
   * Closes the tracer system and the underlying file.
   *
   * @throws IOException If the io makes trouble.
   */
  public void exit() throws IOException {
    if (null != outputter)
      outputter.exit();
    traceLevel = RadioDump.LEVEL_OFF;
  }

  // ////////////////////////////////////////////////////////////////////
  // Basic tracing logic

  public abstract class AbstractHandler implements Event.Handler {
    protected StringBuffer builder = new StringBuffer();

    public void handle(String event, long time, int nodeId, Message msg, String level) {
      builder.setLength(0);

      if (LEVEL_OFF == traceLevel)
        return;

      // Base tracing
      if (LEVEL_BASE <= traceLevel) {
        // KEY_EVENT, event
        builder.append(event);
        builder.append(' ');
        // KEY_TIME, JistAPI.getTime()
        builder.append(time);
        builder.append(' ');
        // KEY_NODEID, nodeId
        builder.append(nodeId);
        builder.append(' ');
        // KEY_MSGHASH, msg.hashCode()
        builder.append(msg.hashCode());
        builder.append(' ');
        // KEY_MSGNAME, msg.getClass().getName()
        builder.append(msg.getClass().getName());
        builder.append(' ');
        // KEY_MSGSIZE, msg.getSize()
        builder.append(msg.getSize());
        builder.append(' ');
        // KEY_LEVEL, level
        builder.append(level);
        builder.append(' ');
      }

      // All tracing
      if (LEVEL_ALL <= traceLevel) {
        // KEY_MSGCONTENT, msg.toString()
        builder.append(msg.toString());
        builder.append(' ');
      }
    }
  }

  // ////////////////////////////////////////////////////////////////////
  // Tracing logic

  public class RadioReceiveHandler extends AbstractHandler {
    public void handle(Event event) {
      if (LEVEL_OFF == traceLevel)
        return;

      RadioReceiveEvent ev = (RadioReceiveEvent) event;
      super.handle(EVENT_RECV, ev.time, ev.radioInfo.getId(), ev.data, LEVEL_RADIO);

      if (LEVEL_BASE <= traceLevel) {
        // KEY_MSGPOWER, power_mW
        builder.append(ev.power_mW);
        builder.append(' ');
        // KEY_MSGDURATION, duration
        builder.append(ev.end - ev.start);
        builder.append(' ');
        // KEY_RADIOID, radioInfo.getId()
        builder.append(ev.radioInfo.getId());
        builder.append(' ');
      }

      if (LEVEL_ALL <= traceLevel) {
        // KEY_RADIO_WAVELENGTH, radioInfo.getWaveLength()
        builder.append(ev.radioInfo.getWaveLength());
        builder.append(' ');
        // KEY_RADIO_THRESHOLD, radioInfo.getThresholdSnr(msg)
        builder.append(ev.radioInfo.getThresholdSnr(ev.data));
        builder.append(' ');
        // KEY_RADIO_SENSITIVITY, radioInfo.getSensitivity_mW()
        builder.append(ev.radioInfo.getSensitivity_mW());
        builder.append(' ');
        // KEY_RADIO_POWER, radioInfo.getPower()
        builder.append(ev.radioInfo.getPower(ev.data));
        builder.append(' ');
        // KEY_RADIO_GAIN, radioInfo.getGain()
        builder.append(ev.radioInfo.getGain());
        builder.append(' ');
        // KEY_RADIO_BACKGROUND, radioInfo.getBackground_mW()
        builder.append(ev.radioInfo.getBackground_mW());
        builder.append(' ');
        // KEY_RADIO_BANDWIDTH, radioInfo.getBandwidth()
        builder.append(ev.radioInfo.getBandwidth());
        builder.append(' ');
      }

      try {
        // Write the data
        outputter.write(builder.toString());
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

  public class RadioSendHandler extends AbstractHandler {
    public void handle(Event event) {
      if (LEVEL_OFF == traceLevel)
        return;

      RadioSendEvent ev = (RadioSendEvent) event;
      super.handle(EVENT_SEND, ev.time, ev.radioInfo.getId(), ev.data, LEVEL_RADIO);

      if (LEVEL_BASE <= traceLevel) {
        // KEY_MSGDURATION, duration
        builder.append(ev.duration);
        builder.append(' ');
        // KEY_RADIOID, radioInfo.getId()
        builder.append(ev.radioInfo.getId());
        builder.append(' ');
      }

      if (LEVEL_ALL <= traceLevel) {
        // KEY_RADIO_WAVELENGTH, radioInfo.getWaveLength()
        builder.append(ev.radioInfo.getWaveLength());
        builder.append(' ');
        // KEY_RADIO_THRESHOLD, radioInfo.getThresholdSnr(msg)
        builder.append(ev.radioInfo.getThresholdSnr(ev.data));
        builder.append(' ');
        // KEY_RADIO_SENSITIVITY, radioInfo.getSensitivity_mW()
        builder.append(ev.radioInfo.getSensitivity_mW());
        builder.append(' ');
        // KEY_RADIO_POWER, radioInfo.getPower()
        builder.append(ev.radioInfo.getPower(ev.data));
        builder.append(' ');
        // KEY_RADIO_GAIN, radioInfo.getGain()
        builder.append(ev.radioInfo.getGain());
        builder.append(' ');
        // KEY_RADIO_BACKGROUND, radioInfo.getBackground_mW()
        builder.append(ev.radioInfo.getBackground_mW());
        builder.append(' ');
        // KEY_RADIO_BANDWIDTH, radioInfo.getBandwidth()
        builder.append(ev.radioInfo.getBandwidth());
        builder.append(' ');
      }

      try {
        // Write the data
        outputter.write(builder.toString());
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

  public class RadioDropHandler extends AbstractHandler {
    public void handle(Event event) {
      if (LEVEL_OFF == traceLevel)
        return;

      RadioDropEvent ev = (RadioDropEvent) event;
      super.handle(EVENT_DROP, ev.time, ev.radioInfo.getId(), ev.data, LEVEL_RADIO);

      if (LEVEL_BASE <= traceLevel) {
        // KEY_MSG_DROPREASON, reason
        builder.append(ev.reason);
        builder.append(' ');
        // KEY_MSGPOWER, power_mW
        builder.append(ev.power_mW);
        builder.append(' ');
        // KEY_MSGDURATION, duration
        builder.append(ev.duration);
        builder.append(' ');
        // KEY_RADIOID, radioInfo.getId()
        builder.append(ev.radioInfo.getId());
        builder.append(' ');
      }

      // Base tracing
      if (LEVEL_ALL <= traceLevel) {
        // KEY_RADIO_WAVELENGTH, radioInfo.getWaveLength()
        builder.append(ev.radioInfo.getWaveLength());
        builder.append(' ');
        // KEY_RADIO_THRESHOLD, radioInfo.getThresholdSnr(msg)
        builder.append(ev.radioInfo.getThresholdSnr(ev.data));
        builder.append(' ');
        // KEY_RADIO_SENSITIVITY, radioInfo.getSensitivity_mW()
        builder.append(ev.radioInfo.getSensitivity_mW());
        builder.append(' ');
        // KEY_RADIO_POWER, radioInfo.getPower()
        builder.append(ev.radioInfo.getPower(ev.data));
        builder.append(' ');
        // KEY_RADIO_GAIN, radioInfo.getGain()
        builder.append(ev.radioInfo.getGain());
        builder.append(' ');
        // KEY_RADIO_BACKGROUND, radioInfo.getBackground_mW()
        builder.append(ev.radioInfo.getBackground_mW());
        builder.append(' ');
        // KEY_RADIO_BANDWIDTH, radioInfo.getBandwidth()
        builder.append(ev.radioInfo.getBandwidth());
        builder.append(' ');
      }

      try {
        // Write the data
        outputter.write(builder.toString());
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

  // ////////////////////////////////////////////////////////////////////
  // Writing helpers

  /**
   * outputter interface for trace data.
   *
   * @author kurth
   */
  protected static interface Outputter {

    // ////////////////////////////////////////////////////////////////////
    // Methods

    /**
     * Inits the outputter
     *
     * @param writer writer to write the trace data to.
     * @throws IOException If the io makes trouble.
     */
    public void init(Writer writer) throws IOException;

    /**
     * Closes the outputter and the underlying file.
     *
     * @throws IOException If the io makes trouble.
     */
    public void exit() throws IOException;

    /**
     * Puts out the trace data.
     *
     * @param line the text string to put out
     */
    public void write(String line) throws IOException;
  }

  /**
   * XML outputter for trace data. Used as singleton.
   *
   * @author kurth
   */
//  protected static class XMLOutputter implements Outputter {
//
//    // ////////////////////////////////////////////////////////////////////
//    // Constants
//
//    public final static String ELEMENTNAME_ROOT = "traces";
//
//    public final static String ELEMENTNAME_ENTRY = "trace";
//
//    // ////////////////////////////////////////////////////////////////////
//    // Locals
//
//    /**
//     * writer interface
//     */
//    private XMLWriter writer;
//
//    // ////////////////////////////////////////////////////////////////////
//    // Initialization
//
//    /**
//     * Constructs an xml outputter.
//     */
//    public XMLOutputter() {
//    }
//
//    // ////////////////////////////////////////////////////////////////////
//    // Methods
//
//    /**
//     * @inheritDoc
//     */
//    public void init(Writer writer) throws IOException {
//      // FileWriter fileWriter = new FileWriter(fileName);
//      this.writer = new XMLWriter(writer);
//      try {
//        this.writer.startDocument();
//        this.writer.startElement("", ELEMENTNAME_ROOT, ELEMENTNAME_ROOT,
//                new AttributesImpl());
//      } catch (SAXException e) {
//        throw new IOException(e.getMessage());
//      }
//    }
//
//    /**
//     * @inheritDoc
//     */
//    public void exit() throws IOException {
//      try {
//        writer.endElement("", ELEMENTNAME_ROOT, ELEMENTNAME_ROOT);
//        writer.endDocument();
//      } catch (SAXException e) {
//        throw new IOException(e.getMessage());
//      }
//      writer.flush();
//      writer.close();
//    }
//
//    /**
//     * @inheritDoc
//     */
//    public void write(List attributes) throws IOException {
//      writeTrace(ELEMENTNAME_ENTRY, attributes);
//    }
//
//    /**
//     * Puts out the map containing the tracing map.
//     *
//     * @param elementName The element name to use with XML.
//     * @param attributes  Map of tracing data.
//     * @throws IOException If the OSi makes trouble.
//     */
//    private void writeTrace(String elementName, List attributes)
//            throws IOException {
//      DefaultElement element = new DefaultElement(elementName);
//
//      Iterator iter = attributes.iterator();
//      while (null != iter && iter.hasNext()) {
//        Map.Entry entry = (Map.Entry) iter.next();
//        element
//                .addAttribute((String) entry.getKey(), (String) entry.getValue());
//      }
//      writer.write(element);
//    }
//  }

  /**
   * Outputter for plain output to file.
   *
   * @author kurth
   */
  protected static class PlainOutputter implements Outputter, JistAPI.DoNotRewrite {

    // ////////////////////////////////////////////////////////////////////
    // Locals

    /**
     * writer interface
     */
    private Writer writer;

    private StringBuffer builder;

    // ////////////////////////////////////////////////////////////////////
    // Initialization

    /**
     * Constructs an xml outputter.
     */
    public PlainOutputter() {
      this.writer = null;
      this.builder = null;
    }

    // ////////////////////////////////////////////////////////////////////
    // Methods

    /**
     * @inheritDoc
     */
    public void init(Writer writer) throws IOException {
      this.builder = new StringBuffer();
      this.writer = new BufferedWriter(writer);
      this.writer.write("# JiST/SWANS BRN tracer v.01 " + new Date() + "\n");
    }

    /**
     * @inheritDoc
     */
    public void exit() throws IOException {
      this.writer.write("# JiST/SWANS BRN tracer v.01 " + new Date() + "\n");
      writer.flush();
      writer.close();
    }

    /**
     * @inheritDoc
     */
    public void write(String line) throws IOException {
      // Recycle builder
      builder.setLength(0);

      // Iterate the attribute list
      builder.append(line);
      builder.append("\n");
      writer.write(builder.toString());
    }
  }

}
