//yield.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2009-2014
 *
 *  This file is part of RoarAudio PlayList Daemon,
 *  a playlist management daemon for RoarAudio.
 *  See README for details.
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  RoarAudio 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 software; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

#include "rpld.h"

#define INTERVAL 100000 /* in µs */
#define WATCHDOG_SCALER 20 /* in units of INTERVAL */

static int    _ay_ptasks   = RPLD_YIELD_TASK_NONE;
static int    _ay_ntasks   = RPLD_YIELD_TASK_NONE;
static int    _ay_block    = RPLD_YIELD_NONBLOCK;
static size_t _ay_count    = 0;
static size_t _ay_pause    = 0;
#ifdef ROAR_FT_FEATURE_WATCHDOG
static int    _ay_watchdog_enabled = 0;
static int    _ay_watchdog = 0;
#endif

static void _task_plugin_update(int block) {
 (void)block;
 rpld_plugin_appsched(ROAR_DL_APPSCHED_UPDATE);
}

static void _task_plugin_tick(int block) {
 (void)block;
 rpld_plugin_appsched(ROAR_DL_APPSCHED_TICK);
}

#ifdef ROAR_FT_FEATURE_WATCHDOG
static void _task_watchdog(int block) {
 static int tickc = 0;
 (void)block;

 tickc++;

 if ( tickc != WATCHDOG_SCALER )
  return;

 tickc = 0;
 roar_watchdog_tick();
}
#endif

static const struct {
 int task;
 int block;
 void (*func)(int block);
} _g_yield_tasks[] = {
 {RPLD_YIELD_TASK_NETWORK,       RPLD_YIELD_NONBLOCK, network_check      },
 {RPLD_YIELD_TASK_PLAYBACK,      RPLD_YIELD_NONBLOCK, playback_check     },
 {RPLD_YIELD_TASK_PLUGIN_UPDATE, RPLD_YIELD_NONBLOCK, _task_plugin_update},
 {RPLD_YIELD_TASK_PLUGIN_TICK,   RPLD_YIELD_NONBLOCK, _task_plugin_tick  },
#ifdef ROAR_FT_FEATURE_WATCHDOG
 {RPLD_YIELD_TASK_WATCHDOG,      RPLD_YIELD_NONBLOCK, _task_watchdog     },
#endif
 {-1, 1, NULL}
};

void yield(int ptasks, int ntasks, int block) {
 int i;

 ptasks |= ntasks;
 ptasks -= ntasks;

 // always do watchdog.
 ptasks |= RPLD_YIELD_TASK_WATCHDOG;

 if ( ptasks == 0 )
  return;

 for (i = 0; _g_yield_tasks[i].task != -1; i++) {
  if ( !(_g_yield_tasks[i].task & ptasks) )
   continue;

  if ( _g_yield_tasks[i].block > block )
   continue;

  _g_yield_tasks[i].func(block);
 }
}

#ifdef HAVE_FUNC_UALARM
static void on_sig_alarm (int signum) {
 (void)signum;
 ROAR_DBG("on_sig_alarm(signum=%i): _ay_ptasks=0x%x, _ay_ntasks=0x%x, _ay_block=0x%x",
             signum, _ay_ptasks, _ay_ntasks, _ay_block);
#ifdef ROAR_FT_FEATURE_WATCHDOG
 if ( _ay_pause || !_ay_count ) {
  yield(RPLD_YIELD_TASK_WATCHDOG, RPLD_YIELD_TASK_NONE, RPLD_YIELD_NONBLOCK);
 } else {
#endif
  yield(_ay_ptasks, _ay_ntasks, _ay_block);
#ifdef ROAR_FT_FEATURE_WATCHDOG
 }
#endif
}
#endif

static void _timer_enable(void) {
#ifdef HAVE_FUNC_UALARM
 signal(SIGALRM,  on_sig_alarm);
 ualarm(INTERVAL, INTERVAL);
#endif
}

static void _timer_disable(void) {
#ifdef ROAR_FT_FEATURE_WATCHDOG
 if ( _ay_watchdog )
  return;
#endif

#ifdef HAVE_FUNC_UALARM
 signal(SIGALRM,  SIG_IGN);
 ualarm(0, 0);
#endif
}

void yield_auto(enum yield_auto cmd, int ptasks, int ntasks, int block) {
 switch (cmd) {
  case RPLD_YIELD_START:
    if ( !_ay_count ) {
     _ay_ptasks = RPLD_YIELD_TASK_ALL;
     _ay_ntasks = RPLD_YIELD_TASK_NONE;
     _ay_block  = RPLD_YIELD_LONGBLOCK;
    }
    _ay_count++;
    _ay_ptasks &= ptasks;
    _ay_ntasks |= ntasks;
    if ( _ay_block > block )
     _ay_block = block;

    if ( _ay_count != 1 )
     return;
    if ( _ay_pause )
     return;
    // start timer.
    _timer_enable();
   break;
  case RPLD_YIELD_STOP:
    if ( !_ay_count ) {
     ROAR_WARN("yield_auto(cmd=RPLD_YIELD_STOP, ptasks=0x%x, ntasks=0x%x, block=%i): auto yield stopped before started. This is a programming error.", ptasks, ntasks, block);
     return;
    }
    _ay_count--;
    if ( !_ay_count) {
     // stop timer.
     _timer_disable();
    }
   break;
  case RPLD_YIELD_PAUSE:
    _ay_pause++;
    if ( _ay_pause == 1 && _ay_count )
     _timer_disable();
   break;
  case RPLD_YIELD_UNPAUSE:
    if ( !_ay_pause ) {
     ROAR_WARN("yield_auto(cmd=RPLD_YIELD_UNPAUSE, ptasks=0x%x, ntasks=0x%x, block=%i): auto yield unpaused before paused. This is a programming error.", ptasks, ntasks, block);
     return;
    }
    _ay_pause--;
    if ( _ay_pause == 0 && _ay_count )
     _timer_enable();
   break;
  case RPLD_YIELD_TRIGGER:
    if ( _ay_count && !_ay_pause )
     yield(_ay_ptasks, _ay_ntasks, _ay_block);
   break;
 }
}

void yield_watchdog_active(int active) {
#ifdef ROAR_FT_FEATURE_WATCHDOG
 _ay_watchdog_enabled = active;
 if ( !active && _ay_watchdog )
  yield_watchdog_stop();
#else
 (void)active;
#endif
}

void yield_watchdog_start(void) {
#ifdef ROAR_FT_FEATURE_WATCHDOG
 if ( !_ay_watchdog_enabled )
  return;

 roar_watchdog_start(ROAR_WATCHDOG_CONF_STOPPABLE|ROAR_WATCHDOG_CONF_CLOCK_EXTERNAL, 1000, NULL);
 _ay_watchdog = 1;
 _timer_enable();
#endif
}

void yield_watchdog_stop(void) {
#ifdef ROAR_FT_FEATURE_WATCHDOG
 if ( !_ay_watchdog )
  return;

 roar_watchdog_stop();
 _ay_watchdog = 0;
 if ( !_ay_count )
  _timer_disable();
#endif
}

void yield_watchdog_trigger(void) {
#ifdef ROAR_FT_FEATURE_WATCHDOG
 roar_watchdog_trigger();
#endif
}

//ll
