/*
 * Copyright (C) 2008 Instituto Nokia de Tecnologia. All rights reserved.
 *
 * This file is part of QEdje.
 *
 * QEdje 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 3 of the License, or
 * (at your option) any later version.
 *
 * QEdje 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 QEdje.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file incorporates work covered by the license described in the
 * file COPYING.libedje.
 */

#include "qrunningprogram.h"

#include <cstdlib>
#include <math.h>

#include "qedjerealpart.h"

static const qreal PI = 3.14159265358979323846;


/*!
  \param program pointer to the associated data container.
  \param edje the QEdje instance this part belongs to.
  \param duration transition duration in miliseconds.
  \param tweenMode curve shape.
*/
QRunningProgram::QRunningProgram(QEdjeProgram *program, QEdje *edje,
                                 int duration, TweenMode tweenMode,
                                 QList<QEdjeRealPart*> targets)
    : _targets(targets), _edje(edje), _duration(duration), _mode(tweenMode),
      _program(program), _timerId(0), _currentValue(0)
{
    if (_program->inRange <= 0.0) {
        _delay = int(_program->inFrom * 1000);
    } else {
        qreal r = ((qreal)qrand() / RAND_MAX) * _program->inRange;
        _delay = int((_program->inFrom + r) * 1000);
    }
    _delayId = 0;
    _edje->_runningPrograms.append(this);
}

/*!
  \brief Destroys a running program.

  Sets the final state in the parts affected and do not run the
  "after" programs. If the program wasn't running yet (waiting on
  delay time before starts) it won't set the final state.
*/
QRunningProgram::~QRunningProgram()
{
    if (!_edje)
        return;

    // We don't do last iteration for a program that was still delaying
    bool wasRunning = (_timerId != 0);
    endProgram();

    if (wasRunning)
        _edje->runLastIteration(_targets, _program, QEdje::doNotExecuteAfter);
}

/*!
  \brief Starts program execution.

  Can also be called to reset program to initial position when already running.
  If there's a delay (keyword "in") in the program, start the timer for the
  delay, letting the real start of the animation for later.
*/
void QRunningProgram::start()
{
    if (!_delayId && _delay > 0.0)
        _delayId = startTimer(_delay);
    else
        realStart();
}

/*!
  \brief Internal function to start the timer.

  \sa start()
*/
void QRunningProgram::realStart()
{
    if (!_timerId)
        _timerId = startTimer(40); // 25 fps

    _stopwatch.start();
}

/*!
  \brief Stop program and internal timers.
*/
void QRunningProgram::stopTimers()
{
    if (_timerId) {
        killTimer(_timerId);
        _timerId = 0;
    }

    if (_delayId) {
        killTimer(_delayId);
        _delayId = 0;
    }
}

/*!
  \brief Called by internal timer on regular intervals.
*/
void QRunningProgram::timerEvent(QTimerEvent *event)
{
    int id = event->timerId();

    if (id == _timerId) {
        event->accept();

        int elapsedTime = _stopwatch.elapsed();
        qreal newValue = elapsedTime < _duration ?
            valueForTime(elapsedTime) : 1.0;

        if (newValue != _currentValue) {
            _currentValue = newValue;
            updateValue();
        }

    } else if (_delayId && (id == _delayId)) {
        event->accept();
        killTimer(_delayId);
        _delayId = 0;
        realStart();

    } else {
        event->ignore();
    }
}

/*!
  \brief Update target state based on '_currentValue'.

  It's meant to be called after '_currentValue' has been updated to a new value
  and then sets the new object state for each of its targets.
  Also checks for end of program condition and performs necessary cleanup.

  \todo Check for Edje->delete_me before running.

  ref.: int _edje_program_run_iterate(Edje_Running_Program *runp,
                                      double tim)
*/
void QRunningProgram::updateValue()
{
    qDebug() << "updateValue";
    qDebug() << state();

    if (_currentValue < 1.0) {
        _edje->iterateState(_currentValue, _targets);
    } else {
        endProgram();
        _edje->runLastIteration(_targets, _program, QEdje::doExecuteAfter);
        _edje = NULL;
        deleteLater();
    }
}

/*!
    Cleans up, removing all references to this program.
*/
void QRunningProgram::endProgram()
{
    stopTimers();
    foreach (QEdjeRealPart *rp, _targets) {
        if (rp->runningProgram() == this)
            rp->setRunningProgram(NULL);
    }
    _edje->_runningPrograms.removeAll(this);
}

/*!
  \brief Return curve value for a given time.

  Return animation progress in range [0..1] for a specific time based on
  the curve shape defined in '_mode'.

  \sa QRunningProgram::QRunningProgram
*/
qreal QRunningProgram::valueForTime(int msec) const
{
    qreal pos = msec / qreal(_duration);

    switch (_mode) {
    case TWEEN_MODE_SINUSOIDAL:
        return (1.0 - cosf(pos * PI)) / 2.0;
    case TWEEN_MODE_ACCELERATE:
        return 1.0 - sinf((PI / 2.0) + (pos * PI / 2.0));
    case TWEEN_MODE_DECELERATE:
        return sinf(pos * PI / 2.0);
    case TWEEN_MODE_LINEAR:
        return pos;
    default:
        return 0.0;
    }
}

