/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "timescale.h"

#include "sonik_util.h"

#include <kglobalsettings.h>
#include <kdebug.h>

#include <qpainter.h>

using Sonik::TimeScale;

namespace
{
  static const uint kHeight = 32;
  static const uint kTickHeight = 10;
  static const uint kTextPos = 12;
  static const uint kMarkerSize = 8;
  static const uint kMarkerWidth = 6;
}

TimeScale::TimeScale(QWidget *parent, const char *name )
  : QWidget(parent, name),
    mPos(0),
    mMaxTime(0),
    mZoom(1.0),
    mSampleRate(1),
    mTimeFormat(kSamples),
    mTickInterval(1)
{
  QFont fnt(KGlobalSettings::generalFont());
  fnt.setPointSize((fnt.pointSize() > 8) ? fnt.pointSize() - 2 : 6);
  setFont(fnt);

  calculateTickInterval();
  setFixedHeight(kHeight);
}

void TimeScale::paintEvent (QPaintEvent *paintEvent)
{
  QPainter p(this);
  QRect paintRect;
  QString str;
  QFont smallFont;
  int paintEnd;
  int curTick;
  int xPos;
  int deltaX;
  QRect labelRect;

  paintRect = paintEvent->rect();

  //Calculate length in samples of window
  paintEnd =
    QMIN(((uint)Sonik::screenToTime(paintRect.right(), mPos, mZoom)), mMaxTime);

  curTick = Sonik::screenToTime(paintRect.left(), mPos, mZoom);
  curTick -= curTick % mTickInterval;

  xPos = (int)(curTick * mZoom - mPos * mZoom);
  deltaX = (int)(mTickInterval * mZoom);
  labelRect = QRect(xPos, kTextPos,
                    (int)(mTickInterval * mZoom - 5), kHeight/2);

  while (curTick <= paintEnd)
  {
    if (xPos >= 0)
      p.drawLine(xPos, 0, xPos, kTickHeight);

    str = Sonik::toString(curTick, mSampleRate, mTimeFormat);
    p.drawText(labelRect, Qt::AlignLeft, str);
    curTick += mTickInterval;
    xPos += deltaX;
    labelRect.moveBy(deltaX, 0);
  }

  // tick for last sample
  xPos = (int)(mMaxTime * mZoom - mPos * mZoom);
  if (xPos >= paintRect.left() && xPos <= paintRect.right())
    p.drawLine(xPos, 0, xPos, kTickHeight);

  // markers
  for (QMap<int, MarkerInfo>::const_iterator m = mMarkers.begin();
       m != mMarkers.end();
       ++m)
  {
    xPos = markerScreenPos(*m, mPos, mZoom);

    if (xPos >= paintRect.left() && xPos <= paintRect.right())
    {
      switch ((*m).style)
      {
        case Line:
          p.drawLine(xPos, 0, xPos, kMarkerSize);
          break;

        case Triangle:
          p.moveTo(xPos, 0);
          p.lineTo(xPos - kMarkerWidth / 2, kMarkerSize);
          p.lineTo(xPos + kMarkerWidth / 2, kMarkerSize);
          p.lineTo(xPos, 0);
          break;

        case Tee:
          p.drawLine(xPos, 0, xPos, kMarkerSize);
          p.drawLine(xPos - kMarkerWidth / 2, kMarkerSize, xPos + kMarkerWidth / 2, kMarkerSize);
          break;
      }
    }
  }
}

void TimeScale::posChanged(int newPos)
{
  int deltaX;

  if (mZoom < 1.0f)
    // round down to zoom multiple
    newPos -= (newPos % (int)(1.0/mZoom));

  if (mPos != (uint)newPos)
  {
    if ((uint)newPos > mMaxTime)
      newPos = mMaxTime;

    deltaX = (int)(newPos * mZoom) - (int)(mPos * mZoom);
    mPos = newPos;

    scroll(-deltaX, 0);
  }
}

void TimeScale::zoomChanged(float newValue)
{
  mZoom = newValue;
  calculateTickInterval();
  update();
}

void TimeScale::setFormat(TimeFormat newFormat)
{
  mTimeFormat = newFormat;
  calculateTickInterval();
  update();
}

void TimeScale::setMaxTime(uint newMax)
{
  mMaxTime = newMax;
  if (mPos > mMaxTime)
    mPos = mMaxTime;
  calculateTickInterval();
  update();
}

void TimeScale::setSampleRate(uint newSampleRate)
{
  mSampleRate = newSampleRate;
  update();
}

void TimeScale::setMarker(int id, uint pos, MarkerBound bound, MarkerStyle style)
{
  removeMarker(id);

  if ((pos == 0 && bound == Lower) ||
      (pos == mMaxTime && bound == Upper))
    bound = Middle;

  if (pos <= mMaxTime)
  {
    MarkerInfo info = { pos, bound, style };
    mMarkers[id] = info;

    int scrPos = markerScreenPos(info, mPos, mZoom);

    update(scrPos - kMarkerWidth / 2 - 2, 0, kMarkerWidth + 4, kMarkerSize + 2);
  }
}

void TimeScale::removeMarker(int id)
{
  QMap<int, MarkerInfo>::iterator m = mMarkers.find(id);
  if (m != mMarkers.end())
  {
    int scrPos = markerScreenPos(*m, mPos, mZoom);
    mMarkers.erase(m);
    update(scrPos - kMarkerWidth / 2 - 2, 0, kMarkerWidth + 4, kMarkerSize + 2);
  }
}

void TimeScale::calculateTickInterval(void)
{
  int maxLabelWidth;

  //Calculate tick interval - aim for ~ 50 pixels
  mTickInterval = QMAX((int)(50 / mZoom), 1);

  //Adjust so that labels don't run into each other
  QString label = Sonik::widestFormatString(mMaxTime, mSampleRate, mTimeFormat);
  maxLabelWidth = fontMetrics().boundingRect(label).width() + 5;

  while ((maxLabelWidth / mZoom) > mTickInterval)
  {
    mTickInterval *= 2;
  }
}

int TimeScale::markerScreenPos(const MarkerInfo& m, uint scrollPos, float zoom)
{
  switch (m.bound)
  {
    case Lower:  return Sonik::timeToScreenL(m.pos, scrollPos, zoom); break;
    case Middle: return Sonik::timeToScreenM(m.pos, scrollPos, zoom); break;
    case Upper:  return Sonik::timeToScreenU(m.pos, scrollPos, zoom); break;
  }
  return -1;
}
