

#include <tools/track.h>


//*****************************************************************************************
//    Static fields
//*****************************************************************************************

TTrack::Id TTrack::m_lastId = 0;


//*****************************************************************************************
//    TTrackTransform implementation
//*****************************************************************************************

TAffine TTrackTransform::makeTiltTransform(const TAffine &a) {
  double l1 = a.a11*a.a11 + a.a21*a.a22;
  double l2 = a.a11*a.a11 + a.a21*a.a22;
  double l = std::max(l1, l2);
  double k = l > TConsts::epsilon*TConsts::epsilon ? 1/sqrt(l) : 0;
  return TAffine( a.a11*k, a.a12*k, 0,
                  a.a21*k, a.a22*k, 0 );
}


//*****************************************************************************************
//    TTrackIntrOrig implementation
//*****************************************************************************************

TTrackPoint
TTrackIntrOrig::interpolate(double index) {
  return track.original ? track.calcPointFromOriginal(track.originalIndexByIndex(index))
                        : track.interpolateLinear(index);
}


//*****************************************************************************************
//    TTrack implementation
//*****************************************************************************************

TTrack::TTrack(
  TInputState::DeviceId deviceId,
  TInputState::TouchId touchId,
  const TInputState::KeyHistory::Holder &keyHistory,
  const TInputState::ButtonHistory::Holder &buttonHistory,
  bool hasPressure,
  bool hasTilt,
  double timeOffset
):
  id(++m_lastId),
  deviceId(deviceId),
  touchId(touchId),
  keyHistory(keyHistory),
  buttonHistory(buttonHistory),
  hasPressure(hasPressure),
  hasTilt(hasTilt),
  original(),
  timeOffset(timeOffset),
  rootTimeOffset(timeOffset),
  pointsRemoved(),
  pointsAdded(),
  fixedPointsAdded(),
  m_pointsFixed()
  { }

TTrack::TTrack(const TTrack &original, double timeOffset):
  id(++m_lastId),
  deviceId(original.deviceId),
  touchId(original.touchId),
  keyHistory(original.keyHistory),
  buttonHistory(original.buttonHistory),
  hasPressure(original.hasPressure),
  hasTilt(original.hasTilt),
  original(&original),
  timeOffset(timeOffset),
  rootTimeOffset(original.rootTimeOffset + timeOffset),
  pointsRemoved(),
  pointsAdded(),
  fixedPointsAdded(),
  m_pointsFixed()
  { }

const TTrack*
TTrack::root() const
  { return original ? original->root() : this; }

int
TTrack::level() const
  { return original ? original->level() + 1 : 0; }

int
TTrack::floorIndex(double index, double *outFrac) const {
  int i = floorIndexNoClamp(index);
  if (i > size() - 1) {
    if (outFrac) *outFrac = 0.0;
    return size() - 1;
  }
  if (i < 0) {
    if (outFrac) *outFrac = 0.0;
    return 0;
  }
  if (outFrac) *outFrac = std::max(0.0, index - (double)i);
  return i;
}

void
TTrack::push_back(const TTrackPoint &point, bool fixed) {
  assert(m_points.empty() || !m_points.back().final);
  m_points.push_back(point);
  TTrackPoint &p = m_points.back();
  if (m_points.size() <= 1) {
    p.length = 0;
  } else {
    const TTrackPoint &prev = *(m_points.rbegin() + 1);

    // fix originalIndex
    if (p.originalIndex < prev.originalIndex)
        p.originalIndex = prev.originalIndex;

    // fix time
    p.time = std::max(p.time, prev.time + TToolTimer::step);

    // calculate length
    p.length = prev.length + tdistance(p.position, prev.position);
  }
  ++pointsAdded;
  if (fixed) fix_all();
}

void
TTrack::pop_back(int count) {
  if (count > size()) count = size();
  if (count <= 0) return;
  assert(size() - count >= m_pointsFixed);
  m_points.resize(size() - count);
  if (pointsAdded > count)
    { pointsAdded -= count; return; }
  if (pointsAdded > 0)
    { count -= pointsAdded; pointsAdded = 0; }
  pointsRemoved += count;
}

void
TTrack::fix_points(int count) {
  count = std::min(count, previewSize());
  assert(count >= 0);
  if (count <= 0) return;
  m_pointsFixed += count;
  fixedPointsAdded += count;
}


TPointD
TTrack::calcTangent(double index, double distance) const {
  double minDistance = 10.0*TConsts::epsilon;
  if (distance < minDistance) distance = minDistance;
  TTrackPoint p = calcPoint(index);
  TTrackPoint pp = calcPoint(indexByLength(p.length - distance));
  TPointD dp = p.position - pp.position;
  double lenSqr = dp.x*dp.x + dp.y*dp.y;
  return lenSqr > TConsts::epsilon*TConsts::epsilon ? dp*(1.0/sqrt(lenSqr)) : TPointD();
}

double
TTrack::rootIndexByIndex(double index) const {
  return original
       ? original->rootIndexByIndex( originalIndexByIndex(index) )
       : index;
}

TTrackPoint
TTrack::calcRootPoint(double index) const {
  return original
       ? original->calcRootPoint( originalIndexByIndex(index) )
       : calcPoint(index);
}
