/*
 *
 *    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 "select.h"
#include "partwidget.h"
#include "data.h"

#include "sonik_sigproc.h"

#include <kaction.h>
#include <klocale.h>
#include <kgenericfactory.h>
#include <kdebug.h>

using Sonik::SelectPlugin;

namespace
{
  static const size_t kSearchBufSize = 1024;
}

SelectPlugin::SelectPlugin(QObject* parent, const char* name,
                             const QStringList& args)
  : Sonik::Edit("select", i18n("Select"), parent, name, args)
{
  new KAction(i18n("Start To Next Zero Crossing"), 0,
              this, SLOT(uiEditSelectMoveStartNextZeroCross()),
              actionCollection(),
              "edit_select_move_start_next_zerocross");
  new KAction(i18n("Start To Previous Zero Crossing"), 0,
              this, SLOT(uiEditSelectMoveStartPrevZeroCross()),
              actionCollection(),
              "edit_select_move_start_prev_zerocross");
  new KAction(i18n("End To Next Zero Crossing"), 0,
              this, SLOT(uiEditSelectMoveEndNextZeroCross()),
              actionCollection(),
              "edit_select_move_end_next_zerocross");
  new KAction(i18n("End To Previous Zero Crossing"), 0,
              this, SLOT(uiEditSelectMoveEndPrevZeroCross()),
              actionCollection(),
              "edit_select_move_end_prev_zerocross");
  new KAction(i18n("Start To Next Peak"), 0,
              this, SLOT(uiEditSelectMoveStartNextPeak()),
              actionCollection(),
              "edit_select_move_start_next_peak");
  new KAction(i18n("Start To Previous Peak"), 0,
              this, SLOT(uiEditSelectMoveStartPrevPeak()),
              actionCollection(),
              "edit_select_move_start_prev_peak");
  new KAction(i18n("End To Next Peak"), 0,
              this, SLOT(uiEditSelectMoveEndNextPeak()),
              actionCollection(),
              "edit_select_move_end_next_peak");
  new KAction(i18n("End To Previous Peak"), 0,
              this, SLOT(uiEditSelectMoveEndPrevPeak()),
              actionCollection(),
              "edit_select_move_end_prev_peak");
}

SelectPlugin::~SelectPlugin()
{
}

QWidget* SelectPlugin::makeConfigPage(QWidget*)
{
  return 0;
}

void SelectPlugin::applyConfigPage()
{
}

void SelectPlugin::uiEditSelectMoveStartNextZeroCross()
{
  off_t p = mWidget->selectionStart();
  moveSelectionStart(findZeroCrossing(p, 1));
}

void SelectPlugin::uiEditSelectMoveStartPrevZeroCross()
{
  off_t p = mWidget->selectionStart();
  moveSelectionStart(findZeroCrossing(p, -1));
}

void SelectPlugin::uiEditSelectMoveEndNextZeroCross()
{
  off_t p = mWidget->selectionStart() + mWidget->selectionLength();
  moveSelectionEnd(findZeroCrossing(p, 1));
}

void SelectPlugin::uiEditSelectMoveEndPrevZeroCross()
{
  off_t p = mWidget->selectionStart() + mWidget->selectionLength();
  moveSelectionEnd(findZeroCrossing(p, -1));
}

void SelectPlugin::uiEditSelectMoveStartNextPeak()
{
  off_t p = mWidget->selectionStart();
  moveSelectionStart(findPeak(p, 1));
}

void SelectPlugin::uiEditSelectMoveStartPrevPeak()
{
  off_t p = mWidget->selectionStart();
  moveSelectionStart(findPeak(p, -1));
}

void SelectPlugin::uiEditSelectMoveEndNextPeak()
{
  off_t p = mWidget->selectionStart() + mWidget->selectionLength();
  moveSelectionEnd(findPeak(p, 1));
}

void SelectPlugin::uiEditSelectMoveEndPrevPeak()
{
  off_t p = mWidget->selectionStart() + mWidget->selectionLength();
  moveSelectionEnd(findPeak(p, -1));
}

void SelectPlugin::moveSelectionStart(off_t start)
{
  off_t delta = start - mWidget->selectionStart();

  if (start != -1)
    mWidget->select(start,
                    mWidget->selectionLength() - delta);
}

void SelectPlugin::moveSelectionEnd(off_t end)
{
  off_t  origStart  = mWidget->selectionStart();

  if (end != -1)
    mWidget->select(origStart, end - origStart);
}

off_t SelectPlugin::findZeroCrossing(off_t p, int dir)
{
  SampleBuffer buf(kSearchBufSize);
  const Sample *s;
  Sample l;

  assert((dir == 1) || (dir == -1));

  getNextBlock(p, dir, buf);
  s = buf.data();
  l = *s;
  ++s; p += dir;

  while (p >= 0 && p <= (off_t)mData->length())
  {
    while (s != buf.end())
    {
      if ((l <= 0 && *s > 0) || (l >= 0 && *s < 0))
        return p;

      l = *s;
      ++s; p += dir;
    }

    getNextBlock(p, dir, buf);
    s = buf.data();
  }

  return -1;
}

off_t SelectPlugin::findPeak(off_t p, int dir)
{
  SampleBuffer buf(kSearchBufSize);
  const Sample *s;
  Sample l[2];

  assert((dir == 1) || (dir == -1));

  getNextBlock(p, dir, buf);
  s = buf.data();
  l[1] = *s;
  ++s; p += dir;
  l[0] = *s;
  ++s; p += dir;

  while (p >= 0 && p <= (off_t)mData->length())
  {
    while (s != buf.end())
    {
      if ((l[1] <= l[0] && l[0] >= *s) ||
          (l[1] >= l[0] && l[0] <= *s))
        return p - 1;

      l[1] = l[0];
      l[0] = *s;
      ++s; p += dir;
    }

    getNextBlock(p, dir, buf);
    s = buf.data();
  }

  return -1;
}

void SelectPlugin::getNextBlock(off_t p, int dir, Sonik::SampleBuffer& buf)
{
    if (dir == -1)
    {
      kdDebug(60606) << "SelectPlugin::getNextBlock: "
                     << "getting " << p - kSearchBufSize
                     << "\n";

      mData->data(0, p - kSearchBufSize, kSearchBufSize, buf);
      Sonik::reverse(buf);
    }
    else
    {
      kdDebug(60606) << "SelectPlugin::getNextBlock: "
                     << "getting " << p
                     << "\n";

      mData->data(0, p, kSearchBufSize, buf);
    }
}

//
// Factory definition
//
K_EXPORT_COMPONENT_FACTORY(libsonik_editselect,
                           KGenericFactory<SelectPlugin>(
                             "sonikpart-edit-select")
                           );
