package brn.sim.handler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jist.swans.mac.MacAddress;
import jist.swans.misc.Event;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetMessage;
import jist.swans.route.AbstractRoute;
import brn.sim.DataManager;
import brn.sim.DataManager.DataContributor;
import brn.sim.data.AbstractDiagramData;
import brn.sim.data.AveragedTimeLine;
import brn.sim.data.DiagramData;
import brn.sim.data.DiagramDataHist;
import brn.swans.route.RouteDsrBrnMsg;
import brn.swans.route.cs.CandidateSelection.NetAddressWithMetric;

public class StatsRouteHandler extends DataContributor {

  public class NodeData {
    protected Map mapCsMetrics = null;
    protected DiagramData rtgDuplicate = null;
    protected DiagramData discard;
    protected DiagramData tryPassiveAck = null;
    protected DiagramData tryNetworkAck = null;
    protected DiagramData rtgCandidateSet = null;
    protected DiagramData rtgCsSize = null;
    protected DiagramData csMetric;
    protected DiagramData csMetricToSrc;
    protected DiagramData csMetricToDst;
    protected DiagramData[] receiveFlow;

    protected int id;
    private String category;
    private String subCategory = "Routing";
    private DiagramDataHist rtgCandidateSetHist;

    public NodeData(int id) {
      this.id = id;
      this.category = getCategory(id);
      this.mapCsMetrics = new HashMap();
      this.receiveFlow = new DiagramData[0];
    }

    public String getCategory(int id) {
      return "Node " + Integer.toString(id);
    }

    public String getCategory() {
      return category;
    }

    protected DiagramData getRtgCandidateSet() {
      if (null == rtgCandidateSet) {
        // cset collector
        rtgCandidateSet = new DiagramData(
            new String[] {category, subCategory, "candidate set"}, "packet", "id",
            "addr", "ip", AbstractDiagramData.MARK);
        addData(rtgCandidateSet, DataManager.LEVEL_ALL);
      }
      return rtgCandidateSet;
    }

    protected DiagramDataHist getRtgCandidateSetHist() {
      if (null == rtgCandidateSetHist) {
        rtgCandidateSetHist = new DiagramDataHist(
            new String[] {category, subCategory, "candidate set (hist)"},
            "addr", "ip", "occurence", "count");
        addData(rtgCandidateSetHist, DataManager.LEVEL_BASIC);
      }
      return rtgCandidateSetHist;
    }

    protected DiagramData getRtgDuplicate() {
      if (null == rtgDuplicate) {
        rtgDuplicate = new DiagramData(
            new String[] {category, subCategory, "duplicate vs time"},
                "time", "s", "duplicate packets", "n/a", AbstractDiagramData.MARK);
        addData(rtgDuplicate, DataManager.LEVEL_ADDITIONAL);
      }

      return rtgDuplicate;
    }

    protected DiagramData getRtgCsSize() {
      if (null == rtgCsSize) {
        rtgCsSize = new DiagramData(
            new String[] {category, subCategory, "candidate set size"},
                "time", "s", "cs size", "n/a", AbstractDiagramData.MARK);
        addData(rtgCsSize, DataManager.LEVEL_ALL);

        DiagramData rtgCsSizeAvg = new DiagramData(
            new AveragedTimeLine("candidate set size (avg)", sampleLen, AveragedTimeLine.MODE_A, 1.),
            new String[] {category, subCategory, "candidate set size (avg)"},
            "time", "s", "cs size", "n/a");
        rtgCsSize.addChain(rtgCsSizeAvg);
        addData(rtgCsSizeAvg, DataManager.LEVEL_BASIC);
      }

      return rtgCsSize;
    }

    public DiagramData getDiscard() {
      if (null == discard) {
        discard = new DiagramData(
            new String[] {category, subCategory, "discard vs time"},
            "time", "s", "discarded packets", "id", AbstractDiagramData.MARK);
        addData(discard, DataManager.LEVEL_ADDITIONAL);
      }
      return discard;
    }

    public DiagramData getCsMetric(List candidates) {
      DiagramData ret = (DiagramData) mapCsMetrics.get(candidates);
      if (null == ret) {
        String name = "cs";
        for (int i = 0; i < candidates.size(); i++)
          name += " " + ((NetAddressWithMetric)candidates.get(i)).ip;
        ret = new DiagramData(
            new String[] {category, subCategory, "CandidateSet Metric", name },
            "time", "s", "metric", "n/a", AbstractDiagramData.SOLID);
        addData(ret, DataManager.LEVEL_ALL);
        mapCsMetrics.put(new ArrayList(candidates), ret);
      }
      return ret;
    }

    public DiagramData getCsMetric() {
      if (null == csMetric) {
        csMetric = new DiagramData(
            new String[] {category, subCategory, "Candidate Metric" },
            "time", "s", "metric", "n/a", AbstractDiagramData.SOLID);
        addData(csMetric, DataManager.LEVEL_ALL);
      }
      return csMetric;
    }

    public DiagramData getCsMetricToSrc() {
      if (null == csMetricToSrc) {
        csMetricToSrc = new DiagramData(
            new String[] {category, subCategory, "Candidate Metric ToSrc" },
            "time", "s", "metric", "n/a", AbstractDiagramData.SOLID);
        addData(csMetricToSrc, DataManager.LEVEL_ALL);
      }
      return csMetricToSrc;
    }

    public DiagramData getCsMetricToDst() {
      if (null == csMetricToDst) {
        csMetricToDst = new DiagramData(
            new String[] {category, subCategory, "Candidate Metric ToDst" },
            "time", "s", "metric", "n/a", AbstractDiagramData.SOLID);
        addData(csMetricToDst, DataManager.LEVEL_ALL);
      }
      return csMetricToDst;
    }
    
    protected DiagramData getTryPassiveAck() {
      if (null == tryPassiveAck) {
        tryPassiveAck = new DiagramData(
            new String[] {category, subCategory, "passive ack tries vs time"}, 
              "time", "s", "try counter", "n/a", AbstractDiagramData.MARK);
        addData(tryPassiveAck, DataManager.LEVEL_ALL);

        DiagramData longRetryRated = new DiagramData(
            new AveragedTimeLine("passive ack tries vs time (avg)", 
                sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, subCategory, "passive ack tries vs time (avg)"},
            "time", "s", "avg tries", "n/a");
        tryPassiveAck.addChain(longRetryRated);
        addData(longRetryRated, DataManager.LEVEL_BASIC);
      }

      return tryPassiveAck;
    }

    protected DiagramData getTryNetworkAck() {
      if (null == tryNetworkAck) {
        tryNetworkAck = new DiagramData(
            new String[] {category, subCategory, "network ack tries vs time"}, 
              "time", "s", "tries counter", "n/a", AbstractDiagramData.MARK);
        addData(tryNetworkAck, DataManager.LEVEL_ALL);

        DiagramData longRetryRated = new DiagramData(
            new AveragedTimeLine("network ack tries vs time (avg)", 
                sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, subCategory, "network ack tries vs time (avg)"},
            "time", "s", "avg tries", "n/a");
        tryNetworkAck.addChain(longRetryRated);
        addData(longRetryRated, DataManager.LEVEL_BASIC);
      }

      return tryNetworkAck;
    }

    public DiagramData getReceiveFlow(int id) {
      try {
        if (null == receiveFlow[id]) {
          receiveFlow[id] = new DiagramData(
              new String[] {category, subCategory, "receive flow " + id},
              "packet", "id", "src node", "id", AbstractDiagramData.MARK);
          addData(receiveFlow[id], DataManager.LEVEL_ADDITIONAL);

        }
      }
      catch (ArrayIndexOutOfBoundsException e) {
        if (id > 50000)
          throw new RuntimeException("id to large");
        DiagramData[] tmp = new DiagramData[id + 100];
        System.arraycopy(receiveFlow, 0, tmp, 0, receiveFlow.length);
        receiveFlow = tmp;
        return getReceiveFlow(id);
      }
      return receiveFlow[id];
    }

  }

  public class GlobalData {
    private static final String category = "Global";

    protected DiagramDataHist rtgCandidateSetHist = null;
    protected DiagramData rtgDuplicate = null;
    protected DiagramData rtgDuplicateId = null;
    protected DiagramData rtgCsSize = null;
    protected DiagramData discard;
    protected DiagramDataHist rtgDuplicateHist;
    protected DiagramDataHist tryPassiveAckHist;
    protected DiagramDataHist tryNetworkAckHist;

    protected DiagramData getRtgDuplicateId() {
      if (null == rtgDuplicateId) {
        rtgDuplicateId = new DiagramData(
            new String[] {category, "Routing", "duplicate id vs time"},
            "time", "s", "duplicate packets", "packet id", AbstractDiagramData.MARK);
        addData(rtgDuplicateId, DataManager.LEVEL_ADDITIONAL);
      }

      return rtgDuplicateId;
    }

    protected DiagramData getRtgDuplicate() {
      if (null == rtgDuplicate) {
        rtgDuplicate = new DiagramData(
            new String[] {category, "Routing", "duplicate vs time"},
            "time", "s", "node", "id", AbstractDiagramData.MARK);
        addData(rtgDuplicate, DataManager.LEVEL_ADDITIONAL);
      }

      return rtgDuplicate;
    }

    protected DiagramDataHist getRtgDuplicateHist() {
      if (null == rtgDuplicateHist) {
        rtgDuplicateHist = new DiagramDataHist(
            new String[] {category, "Routing", "duplicate (hist)"},
            "node", "id", "duplicates", "no.");
        addData(rtgDuplicateHist, DataManager.LEVEL_BASIC);
      }

      return rtgDuplicateHist;
    }

    protected DiagramDataHist getRtgCandidateSet() {
      if (null == rtgCandidateSetHist) {
        rtgCandidateSetHist = new DiagramDataHist(
            new String[] {category, "Routing", "candidate set (hist)"},
            "addr", "ip", "occurence", "count");
        addData(rtgCandidateSetHist, DataManager.LEVEL_BASIC);
      }
      return rtgCandidateSetHist;
    }

    protected DiagramData getRtgCsSize() {
      if (null == rtgCsSize) {
        rtgCsSize = new DiagramData(
            new String[] {category, "Routing", "candidate set size"},
                "time", "s", "cs size", "n/a", AbstractDiagramData.MARK);
        addData(rtgCsSize, DataManager.LEVEL_ALL);

        DiagramData rtgCsSizeAvg = new DiagramData(
            new AveragedTimeLine("candidate set size (avg)", sampleLen, AveragedTimeLine.MODE_A, 1.),
            new String[] {category, "Routing", "candidate set size (avg)"},
            "time", "s", "cs size", "n/a");
          rtgCsSize.addChain(rtgCsSizeAvg);
        addData(rtgCsSizeAvg, DataManager.LEVEL_BASIC);
      }

      return rtgCsSize;
    }

    public DiagramData getDiscard() {
      if (null == discard) {
        discard = new DiagramData(
            new String[] {category, "Routing", "discard vs time"},
            "time", "s", "discarded packets", "id", AbstractDiagramData.MARK);
        addData(discard, DataManager.LEVEL_ADDITIONAL);
      }
      return discard;
    }

    public DiagramDataHist getTryPassiveAckHist() {
      if (null == tryPassiveAckHist) { 
        tryPassiveAckHist = new DiagramDataHist(
            new String[] {category, "Routing", "passive ack tries (hist)"},
            "node", "id", "tries", "count");
        addData(tryPassiveAckHist, DataManager.LEVEL_BASIC);
      }

      return tryPassiveAckHist;
    }

    public DiagramDataHist getTryNetworkAckHist() {
      if (null == tryNetworkAckHist) { 
        tryNetworkAckHist = new DiagramDataHist(
            new String[] {category, "Routing", "network ack tries (hist)"},
            "node", "id", "tries", "count");
        addData(tryNetworkAckHist, DataManager.LEVEL_BASIC);
      }

      return tryNetworkAckHist;
    }
  }

  private static final String ID = "StatsRouteHandler";

  private double sampleLen;

  private NodeData[] nodeData;

  private GlobalData globalData;


  public StatsRouteHandler(double sampleLen) {
    this.sampleLen = sampleLen;
    this.nodeData = new NodeData[20];
    this.globalData = new GlobalData();
  }


  protected NodeData getNodeData(int id) {
    try {
      if (null == nodeData[id])
        nodeData[id] = new NodeData(id);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      if (id > 50000)
        throw new RuntimeException("id to large");
      NodeData[] tmp = new NodeData[id + 100];
      System.arraycopy(nodeData, 0, tmp, 0, nodeData.length);
      nodeData = tmp;
      return getNodeData(id);
    }
    return nodeData[id];
  }

  /*
   * (non-Javadoc)
   * @see brn.sim.DataManager.DataContributor#getId()
   */
  public String getId() {
    return ID;
  }

  /**
   * Registers all route handlers.
   */
  public void registerHandlers() {
    super.registerHandlers();

    // Add route handlers

    Event.addHandler(AbstractRoute.DuplicateEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.DuplicateEvent ev = (AbstractRoute.DuplicateEvent) event;
        RouteDsrBrnMsg msg = (RouteDsrBrnMsg) ev.data;
        // process only data packets
        if (null == msg.getContent())
          return;
        
        RouteDsrBrnMsg.Option optId = msg.getOption(RouteDsrBrnMsg.OPT_ID);
        int id = ((RouteDsrBrnMsg.OptionId)optId).getId();

        getNodeData(ev.nodeId).getRtgDuplicate().addNextTimePoint(ev.time, id);
        globalData.getRtgDuplicateId().addNextTimePoint(ev.time, id);
        globalData.getRtgDuplicate().addNextTimePoint(ev.time, ev.nodeId);
        globalData.getRtgDuplicateHist().addNextValue(ev.nodeId);
      }
    });

    Event.addHandler(AbstractRoute.CandSelectedEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.CandSelectedEvent ev = (AbstractRoute.CandSelectedEvent) event;
        globalData.getRtgCsSize().addNextTimePoint(ev.time, ev.candidates.size());
        getNodeData(ev.nodeId).getRtgCsSize().addNextTimePoint(ev.time, ev.candidates.size());

        DiagramData candidateSet = getNodeData(ev.nodeId).getRtgCandidateSet();
        DiagramDataHist candidateSetHist1 = getNodeData(ev.nodeId).getRtgCandidateSetHist();
        DiagramDataHist candidateSetHist2 = globalData.getRtgCandidateSet();

        for (int i = 0; i < ev.candidates.size(); i++) {
          MacAddress macAddr = (MacAddress) ev.candidates.get(i);
          candidateSet.addNextPoint(ev.packetId + .001*i, macAddr.getId());
          candidateSetHist1.addNextValue(macAddr.getId());
          candidateSetHist2.addNextValue(macAddr.getId());
        }
      }
    });

    Event.addHandler(AbstractRoute.DiscardEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.DiscardEvent ev = (AbstractRoute.DiscardEvent) event;
        NetMessage.Ip ipMsg = (NetMessage.Ip) ev.data;
        int id = -1;

        if (ipMsg.getPayload() instanceof RouteDsrBrnMsg) {
          RouteDsrBrnMsg msg = (RouteDsrBrnMsg) ipMsg.getPayload();
          RouteDsrBrnMsg.Option optId = msg.getOption(RouteDsrBrnMsg.OPT_ID);
          id = ((RouteDsrBrnMsg.OptionId)optId).getId();

          getNodeData(ev.nodeId).getDiscard().addNextTimePoint(ev.time, id);
        }

        globalData.getDiscard().addNextTimePoint(ev.time, id);
      }
    });

    Event.addHandler(AbstractRoute.CandMetricEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.CandMetricEvent ev = (AbstractRoute.CandMetricEvent) event;
//        getNodeData(ev.nodeId).getCsMetric(ev.candidates).
//          addNextTimePoint(ev.time, ev.metricToSrc + ev.metricToDst);
        NodeData nodeData2 = getNodeData(ev.nodeId);
        nodeData2.getCsMetric().
          addNextTimePoint(ev.time, ev.metricToSrc + ev.metricToDst);
        nodeData2.getCsMetricToSrc().
          addNextTimePoint(ev.time, ev.metricToSrc);
        nodeData2.getCsMetricToDst().
          addNextTimePoint(ev.time, ev.metricToDst);
      }
    });
    
    Event.addHandler(AbstractRoute.RetryEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.RetryEvent ev = (AbstractRoute.RetryEvent) event;
        NodeData nodeData = getNodeData(ev.nodeId);
        if (ev.passiveRetry) {
          nodeData.getTryPassiveAck().addNextTimePoint(ev.time, ev.tryCount);
          globalData.getTryPassiveAckHist().addNextValue((double) ev.nodeId);
        }
        else {
          nodeData.getTryNetworkAck().addNextTimePoint(ev.time, ev.tryCount);
          globalData.getTryNetworkAckHist().addNextValue((double) ev.nodeId);
        }
      }
    });
    
    Event.addHandler(AbstractRoute.PacketForwardedEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractRoute.PacketForwardedEvent ev = (AbstractRoute.PacketForwardedEvent) event;
        if (null == ev.anno)
          return;

        Integer packetId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_PACKETID);
        Integer flowId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_FLOWID);
        if (null == packetId || null == flowId)
          return;

        NodeData nodeData = getNodeData(ev.nodeId);
        nodeData.getReceiveFlow(flowId).addNextPoint(packetId, ev.lastIp.getId());
      }
    });

  }

}
