package brn.swans.radio.biterrormodels;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import jist.swans.Constants;

public class BitErrorMasks extends HashMap {

  public static int MAX_NUM_SAMPLES = 150;
  
  private static class MaskList extends LinkedList {
    
    private double rateAverage;
    private double chunkSuccessRate;
    private int bitLength;
    private int seenSamples;

    public MaskList(int length) {
      this.bitLength = length;
      this.rateAverage = 0;
      this.chunkSuccessRate = 0;
      this.seenSamples = 0;
    }

    public void put(BitErrorMask mask) {
      chunkSuccessRate = 0;
      if (size() < MAX_NUM_SAMPLES)
        add(mask);
      else if(Constants.random.nextDouble() < (double)MAX_NUM_SAMPLES / (double)seenSamples) {
        add(mask);
        removeFirst();
      }
      double rate = (double) mask.getNumErrors() / (double) bitLength;
      rateAverage = (rateAverage * seenSamples + rate) / (++seenSamples);
    }

    public double getRateAverage() {
      return rateAverage;
    }

    public BitErrorMask get() {
      BitErrorMask ret = (BitErrorMask) removeFirst();
      add(ret);
      return ret;
    }

    public double getChunkSuccessRate() {
      if (chunkSuccessRate == 0) {
        chunkSuccessRate = 1.0 - Math.pow(rateAverage, bitLength);
      }
      return chunkSuccessRate;
    }
  }

  public void insert(BitErrorMask mask, int in_length, byte in_rssi,
      byte in_rate) {
    Byte rssi = Byte.valueOf(in_rssi);
    Byte rate = Byte.valueOf(in_rate);
    Integer length = Integer.valueOf(in_length);
    Map rateMap = (Map) get(rate);
    if (rateMap == null) {
      rateMap = new TreeMap();
      put(rate, rateMap);
    }

    Map rssiMap = (Map) rateMap.get(rssi);
    if (rssiMap == null) {
      rssiMap = new TreeMap();
      rateMap.put(rssi, rssiMap);
    }
    MaskList list = (MaskList) rssiMap.get(length);
    if (list == null) {
      list = new MaskList(length);
      rssiMap.put(length, list);
    }

    list.put(mask);
  }

  BitErrorMask get(int in_length, byte in_rssi, byte in_rate) {
    MaskList list = lookup(in_length, in_rssi, in_rate);
    if (list == null)
      return null;
    return list.get();
  }
  
  public double getRateAverage(int in_length, byte in_rssi, byte in_rate) {
    MaskList list = lookup(in_length, in_rssi, in_rate);
    if (list == null) 
      return 0;
    return list.getRateAverage();
  }
  
  public double getChunkSuccessRate(int in_length, byte in_rssi, byte in_rate) {
    MaskList list = lookup(in_length, in_rssi, in_rate);
    if (list == null) 
      return 0;
    return list.getChunkSuccessRate();
  }
  
  private MaskList lookup(int in_length, byte in_rssi, byte in_rate) {
    Byte rssi = Byte.valueOf(in_rssi);
    Byte rate = Byte.valueOf(in_rate);
    Integer length = Integer.valueOf(in_length);

    SortedMap rateMap = (SortedMap) get(rate);
    if (rateMap == null)
      return null;

    SortedMap rssiMap = (SortedMap) rateMap.get(rssi);
    if (rssiMap == null) {
      rssiMap = (SortedMap) rateMap.get(rateMap.headMap(rssi).lastKey());
    }
    if (rssiMap == null) {
      rssiMap = (SortedMap) rateMap.get(rateMap.tailMap(rssi).firstKey());
    }

    if (rssiMap == null)
      return null;

    MaskList list = (MaskList) rssiMap.get(length);
    if (list == null) {
      list = (MaskList) rssiMap.get(rssiMap.headMap(length).lastKey());
    }
    return list;
  }

}