#include <math.h>
#include "SampleDifference.h"
#include "OneSample.h"
#include "RawIR.h"

SampleDifference::SampleDifference(OneSample * init_this_sample)
 : this_sample(init_this_sample), total_time(0), log_total_time(0.0) {

   OneDifference init_diff;
   init_diff.count = 0;
   init_diff.length = 0;
   unsigned int len = this_sample->Length();
   for (unsigned int i = 0; i < len; ++i) {
      init_diff.length += BGIR_DURATION((*this_sample)[i]);
   }
   diffs.push_back(init_diff);
}

SampleDifference::~SampleDifference() {
}

void
SampleDifference::CompareSample(OneSample * other_sample) {
   unsigned int    this_len       = this_sample->Length();
   unsigned int    this_ndx       = 0;
   unsigned int    other_len      = other_sample->Length();
   unsigned int    other_ndx      = 0;
   unsigned short this_blip      = 0;
   unsigned short other_blip     = 0;
   bool            this_is_pulse  = false;
   bool            other_is_pulse = false;
   unsigned short clock          = 0;
   for ( ; ; ) {

      // When we use up all the ticks in the last pulse or space we were
      // looking at (a "blip"), move on to the next one (not that I expect
      // to see zero length blips, but be paranoid and use while instead of
      // if anyway).

      while ((this_blip == 0) && (this_ndx < this_len)) {
         this_blip       = (*this_sample)[this_ndx++];
         this_is_pulse   = (BGIR_IS_MARK(this_blip) != 0);
         this_blip       = BGIR_DURATION(this_blip);
      }
      while ((other_blip == 0) && (other_ndx < other_len)) {
         other_blip      = (*other_sample)[other_ndx++];
         other_is_pulse  = (BGIR_IS_MARK(other_blip) != 0);
         other_blip      = BGIR_DURATION(other_blip);
      }

      // Find out which blip ends first and how many ticks till that
      // transition.

      unsigned short ticks = this_blip;
      if (other_blip < ticks) {
         ticks = other_blip;
      }

      // If we are all out of ticks in this or the other, then we are
      // through with comparison.

      if (ticks == 0) {
         break;
      }

      // If the next span of ticks is is for a pulse in one with a space in
      // the other (or vice versa), then record a difference increment for
      // that clock range.

      if (this_is_pulse != other_is_pulse) {
         IncrementRange(clock, ticks);
      }

      // Done with this span of clock ticks, move on to the next pulse or
      // space change in this or the other.

      clock += ticks;
      this_blip -= ticks;
      other_blip -= ticks;
   }
}

void
SampleDifference::FinishDiffs() {
   unsigned int len = diffs.size();
   for (unsigned int i = 0; i < len; ++i) {
      total_time += diffs[i].length;
      log_total_time += log((double)(diffs[i].length));
   }
}

void
SampleDifference::IncrementRange(unsigned short clock, unsigned short ticks) {
   unsigned int    diffs_ndx   = 0;
   unsigned short diffs_clock = 0;
   unsigned short diffs_blip  = 0;

   // Find current diffs range where clock time we want to increment starts

   while (diffs_ndx < diffs.size()) {
      diffs_blip = diffs[diffs_ndx].length;
      if ((diffs_clock + diffs_blip) <= clock) {
         ++diffs_ndx;
         diffs_clock += diffs_blip;
      } else {
         break;
      }
   }

   if (diffs_ndx >= diffs.size()) {
      // Looks like the clock time is after the end of my range,
      // just ignore it.
      return;
   }

   // First split diffs array at the starting clock tick time (if required)
   // and position to the diffs entry that starts exactly at "clock"

   unsigned short start_offset = clock - diffs_clock;
   if (start_offset > 0) {
      SplitDiffsAt(diffs_ndx, start_offset);
      ++diffs_ndx;
   }

   // Now increment all diffs entries till we find one that extends to a time
   // past clock+tick

   while ((ticks > 0) && (diffs_ndx < diffs.size())) {
      diffs_blip = diffs[diffs_ndx].length;
      if (diffs_blip > ticks) {
         break;
      }
      diffs[diffs_ndx].count += 1;
      ticks -= diffs_blip;
      diffs_clock += diffs_blip;
      ++diffs_ndx;
   }

   // Now split the final diffs entry at the ending time and increment
   // the first part of the split entries.

   if ((ticks > 0) && (diffs_ndx < diffs.size())) {
      SplitDiffsAt(diffs_ndx, ticks);
      diffs[diffs_ndx].count += 1;
   }
}

void
SampleDifference::SplitDiffsAt(
   unsigned int    diffs_ndx,
   unsigned short tick_off) {

   unsigned int new_ndx = diffs.size();
   OneDifference od;
   diffs.push_back(od);
   while (new_ndx > diffs_ndx) {
      diffs[new_ndx] = diffs[new_ndx - 1];
      --new_ndx;
   }
   diffs[diffs_ndx].length = tick_off;
   diffs[diffs_ndx+1].length -= tick_off;
}
