package brn.swans.radio.biterrormodels;

import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;

import jist.swans.Constants;
import brn.swans.radio.TransmissionMode;
import brn.swans.radio.RadioNoiseAdditiveBER.ChunkErrorRates;
import brn.swans.radio.biterrormodels.BitErrorMask.SpecifiedErrors;

public class Accumulated extends BitErrorModel {

  private class NumOnly extends BitErrorMask {

    private int length;

    public NumOnly(int length) {
      this.length = length;
    }

    @Override
    protected void apply(BitErrorData data, int start) {
      data.dataErrors = true;
    }

    public void addErrors(int num) {
      numErrors += num;
    }

  }

  @Override
  protected BitErrorMask createErrors(int length, double snr,
      TransmissionMode mode) {
    BitErrorMask mask = BitErrorMask.noErrors;
    double ber = mode.getBitErrorRate(snr);
    boolean bitErrors = false;
    for (int pos = 0; pos < length && ber > .0; ++pos) {
      if (Constants.random.nextDouble() <= ber) {
        if (!bitErrors) {
          mask = new NumOnly(length);
          bitErrors = true;
        }
        mask.setError(pos);
      }
    }
    return mask;
  }

  public void postProcess(ChunkErrorRates rates) {
    if (!rates.errorsPresent()) return;
    SortedMap chunkErrors = rates.getBitErrors();
    SortedMap newChunkErrors = new TreeMap();


    NumOnly mostErrors = null;
    Integer mostErrorsPos = null;
    Iterator chunkIter = chunkErrors.keySet().iterator();
    while (chunkIter.hasNext()) {
      Integer pos = (Integer)chunkIter.next();
      BitErrorMask mask = (BitErrorMask) chunkErrors.get(pos);
      if (mask != BitErrorMask.noErrors) {
        NumOnly errors = (NumOnly) mask;
        if (mostErrors == null
            || errors.length / errors.numErrors < mostErrors.length
                / mostErrors.numErrors) {
          mostErrors = errors;
          mostErrorsPos = pos;
        }
      }
    }


      chunkIter = chunkErrors.keySet().iterator();
      while (chunkIter.hasNext()) {
        Integer nextPos = (Integer)chunkIter.next();
        BitErrorMask mask = (BitErrorMask)chunkErrors.get(nextPos);
        if (mask == BitErrorMask.noErrors) {
          newChunkErrors.put(nextPos, mask);
        } else if (mask != mostErrors) {
          NumOnly nextNum = (NumOnly)mask;
          if (mostErrors.length - mostErrors.numErrors >= nextNum.numErrors) {
            mostErrors.numErrors += nextNum.numErrors;
            assert(mostErrors.numErrors <= mostErrors.length);
            newChunkErrors.put(nextPos, BitErrorMask.noErrors);
          } else {
            nextNum.numErrors -= mostErrors.length - mostErrors.numErrors;
            mostErrors.numErrors = mostErrors.length;
            newChunkErrors.put(mostErrorsPos, allocateErrors(mostErrors));
            // just use the next chunk when there are too many errors to fit into mostErrors
            // that should rarely occur
            mostErrorsPos = nextPos;
            mostErrors = nextNum;
          }
        }
      }
      if (mostErrors != null)
        newChunkErrors.put(mostErrorsPos, allocateErrors(mostErrors));
      chunkErrors.clear();
      chunkErrors.putAll(newChunkErrors);
  }

  private SpecifiedErrors allocateErrors(NumOnly numErrors) {
    SpecifiedErrors errors = new SpecifiedErrors(numErrors.length);

    int startPos = 0;
    if (numErrors.length != numErrors.numErrors)
      startPos = Constants.random.nextInt((int)(numErrors.length - numErrors.numErrors));
    for (int i = 0; i < numErrors.numErrors; ++i) {
      errors.setError(startPos + i);
    }
    return errors;
  }

}
