package brn.swans.radio.biterrormodels;

import java.util.Iterator;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;

import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;

public abstract class BitErrorMask {
  protected int numErrors = 0;

  public static class NoErrors extends BitErrorMask{
    private NoErrors() {}

    protected void apply(BitErrorData data, int length) {}

    @Override
    public void setError(int position) {
      throw new RuntimeException("operation not supported");
    }
  }

  public static class UnspecifiedErrors extends BitErrorMask {
    private UnspecifiedErrors() {}

    //TODO: it's debatable if this should be allowed.
    //It happens if recvCorruptErrors is present and the BitErrorModel is None
    protected void apply(BitErrorData data, int length) {
      data.dataErrors = true;
    }


  }

  public static class SpecifiedErrors extends BitErrorMask {
    protected SortedSet mask;
    public SpecifiedErrors(SortedSet mask) {
      this.mask = mask;
    }

    public SpecifiedErrors(int length) {
      mask = new TreeSet();
    }

    public void setError(int position) {
      super.setError(position);
      mask.add(Integer.valueOf(position));
    }

    protected void apply(BitErrorData data, int start) {

      if (!data.cloned) {
        data.data = (byte[])data.data.clone();
        data.cloned = true;
      }

      if (!data.dataErrors) {
        data.dataErrors = true;
      }

      int byteStart = (int)(start / 8);
      Iterator maskIter = mask.iterator();
      while(maskIter.hasNext()) {
        int pos = ((Integer)maskIter.next()).intValue();
          data.data[(int)(pos / 8 + byteStart)] ^= 1 << (pos % 8);
      }

    }

    /*public boolean hasError(int i) {
      return mask.contains(Integer.valueOf(i));
    }*/

    public double errVal(int i) {
      return mask.contains(Integer.valueOf(i)) ? 1.0 : 0.0;
    }
  }

  public static class BitErrorData {
    public BitErrorData(byte[] data) {
      this.data = data;
    }

    public BitErrorData(byte[] data, int truncated) {
      this.data = Util.copyOf(data, truncated);
      cloned = true;
      dataErrors = true;
    }

    public byte data[];
    public boolean plcpErrors = false;
    public boolean dataErrors = false;
    public boolean cloned = false;
  }



  public static UnspecifiedErrors unspecifiedErrors = new UnspecifiedErrors();
  public static NoErrors noErrors = new NoErrors();

  public int getNumErrors() {
    return numErrors;
  }

  public void setError(int position) {
    numErrors++;
  }

  protected abstract void apply(BitErrorData data, int start);


  /**
   * inserts bit errors into the payload of a PhyMessage
   */
  public static BitErrorData apply(byte[] origData, MessageAnno anno) {
    if (anno == null) {
      return new BitErrorData(origData);
    }

    Object truncation = anno.get(MessageAnno.ANNO_RADIO_PACKET_TRUNCATED);
    BitErrorData returnData = null;
    if (truncation != null ) {
      returnData = new BitErrorData(origData, ((Integer)truncation).intValue());
    } else {
      returnData = new BitErrorData(origData);
    }


    SortedMap chunks = (SortedMap)anno.get(MessageAnno.ANNO_RADIO_PACKET_BER);
    if (chunks == null) {
      return returnData;
    }

    Iterator chunkIterator = chunks.keySet().iterator();
    if (chunkIterator.hasNext()) {
      Integer start = (Integer)chunkIterator.next();
      Integer end = Integer.valueOf(0);
      BitErrorMask mask = (BitErrorMask)chunks.get(start);

      while(chunkIterator.hasNext() && start.intValue() < 0) {
        end = (Integer)chunkIterator.next();
        if (end.intValue() >= 0) {
          if (mask != noErrors) {
            returnData.plcpErrors = true;
            return returnData;
          }
        } else if (!chunkIterator.hasNext()) {
          if (mask != noErrors) {
            returnData.plcpErrors = true;
          }
          return returnData;
        }
        start = end;
        mask = (BitErrorMask)chunks.get(start);
      }

      while(chunkIterator.hasNext()) {
        end = ((Integer)chunkIterator.next());
        if (end.intValue() > 0) {
          mask.apply(returnData, start);
        }
        start = end;
        mask = (BitErrorMask)chunks.get(start);
      }
      mask.apply(returnData, start);
    }
    return returnData;
  }


}
