//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2006 by PDSoft (Attila Padar)                *
//*                    http://mpxplay.cjb.net                              *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: timer/scheduler/signalling (timed function calls)

#include <mpxplay.h>
#include <mpxinbuf.h>
#include "newfunc.h"

#define MPXPLAY_TIMEDS_INITSIZE  32
//#define MPXPLAY_SIGNAL_EVENTS_MAX  3
#define MPXPLAY_TIMERS_STACKSIZE 32768
#define MTF_FLAG_LOCK 1

typedef struct mpxplay_timed_s{
 void *func;
 void *data;
 unsigned long timer_flags;
 unsigned long refresh_delay;  // or signal event (SIGNALTYPE)
 unsigned long refresh_counter;

 //unsigned int  signalevents;
 //unsigned long signal_flags[MPXPLAY_SIGNAL_EVENTS_MAX];
 //unsigned long signal_event[MPXPLAY_SIGNAL_EVENTS_MAX];

#ifdef __DOS__
 char *ownstackmem;     // for int08 functions
 void __far *oldstack;
 char __far *newstack;
#endif
}mpxplay_timed_s;

typedef void (*call_timedfunc_nodata)(void);
typedef void (*call_timedfunc_withdata)(void *);

static void newfunc_timer_delete_entry(mpxplay_timed_s *mtf);
static void mpxplay_timer_delete_int08_funcs(void);

extern struct mainvars mvps;

static volatile mpxplay_timed_s *mpxplay_timed_functions;
static volatile unsigned long mtf_storage,mtf_flags;
static volatile unsigned int oldint08_running;
volatile unsigned long int08counter,mpxplay_signal_events;

static unsigned int newfunc_timer_alloc(void)
{
 unsigned int newsize;
 mpxplay_timed_s *mtf;

 if(mtf_storage)
  newsize=mtf_storage*2;
 else
  newsize=MPXPLAY_TIMEDS_INITSIZE;

 mtf=pds_calloc(newsize,sizeof(mpxplay_timed_s));
 if(!mtf)
  return 0;
 funcbit_enable(mtf_flags,MTF_FLAG_LOCK);
 if(mpxplay_timed_functions){
  pds_memcpy(mtf,(void *)mpxplay_timed_functions,mtf_storage*sizeof(mpxplay_timed_s));
  pds_free((void *)mpxplay_timed_functions);
 }
 mpxplay_timed_functions=mtf;
 mtf_storage=newsize;
 funcbit_disable(mtf_flags,MTF_FLAG_LOCK);
 return newsize;
}

static void mpxplay_timer_close(void)
{
 mpxplay_timed_s *mtf=(mpxplay_timed_s *)mpxplay_timed_functions;
 if(mtf){
  unsigned int i;

  funcbit_enable(mtf_flags,MTF_FLAG_LOCK);
  mpxplay_timer_delete_int08_funcs();

#ifdef __DOS__
  for(i=0;i<mtf_storage;i++){
   if(mtf->ownstackmem)
    pds_free(mtf->ownstackmem);
   mtf++;
  }
#endif
  pds_free(mtf);
  mpxplay_timed_functions=NULL;
  funcbit_disable(mtf_flags,MTF_FLAG_LOCK);
 }
 mtf_storage=0;
}

static mpxplay_timed_s *newfunc_timer_search_entry(void *func)
{
 mpxplay_timed_s *mtf=(mpxplay_timed_s *)mpxplay_timed_functions;
 unsigned int i;

 if(!mtf)
  return NULL;

 for(i=0;i<mtf_storage;i++){
  if(mtf->func==func)
   return mtf;
  mtf++;
 }

 return NULL;
}

static mpxplay_timed_s *newfunc_timer_getfree_entry(void)
{
 mpxplay_timed_s *mtf;

 mtf=newfunc_timer_search_entry(NULL);

 if(!mtf)
  if(newfunc_timer_alloc())
   mtf=newfunc_timer_search_entry(NULL);

 return mtf;
}

static int newfunc_timer_add_entry(mpxplay_timed_s *mtf,void *func,void *data,unsigned int timer_flags,unsigned int refresh_delay)
{
#ifdef __DOS__
 if(timer_flags&MPXPLAY_TIMERFLAG_OWNSTACK){
  if(!mtf->ownstackmem){
   mtf->ownstackmem=(char *)pds_malloc(MPXPLAY_TIMERS_STACKSIZE+32);
   if(!mtf->ownstackmem)
    return -1;
   mtf->newstack=(char far *)(mtf->ownstackmem+MPXPLAY_TIMERS_STACKSIZE);
  }
 }
#endif
 mtf->func=func;
 mtf->data=data;
 mtf->timer_flags=timer_flags;
 mtf->refresh_delay=refresh_delay;
 mtf->refresh_counter=int08counter;
 //{
  //char sout[100];
  //fprintf(stdout,"add: %2d flags:%8.8X delay:%d\n",(int)(mtf-mpxplay_timed_functions),mtf->timer_flags,mtf->refresh_delay);
  //display_message(0,0,sout);
  //pds_textdisplay_printf(sout);
 //}
 return (int)(mtf-mpxplay_timed_functions); // index in mpxplay_timed_functions
}

static void newfunc_timer_delete_entry(mpxplay_timed_s *mtf)
{
 if(mtf){
  mtf->func=NULL;
  mtf->timer_flags=0;
  /*{
   char sout[100];
   sprintf(sout,"del: %2d",(int)(mtf-mpxplay_timed_functions));
   //display_message(1,0,sout);
   pds_textdisplay_printf(sout);
  }*/
 }
}

//------------------------------------------------------------------------
unsigned long mpxplay_timer_secs_to_counternum(unsigned long secs)
{
 mpxp_int64_t cn;     // 1000.0ms/55.0ms = 18.181818 ticks per sec
 pds_fto64i((float)secs*(1000.0/55.0)*(float)INT08_DIVISOR_DEFAULT/(float)INT08_DIVISOR_NEW,&cn);
 return cn;
}

int mpxplay_timer_addfunc(void *func,void *data,unsigned int timer_flags,unsigned int refresh_delay)
{
 mpxplay_timed_s *mtf=NULL;

 if(!func)
  return -1;

 if(!(timer_flags&MPXPLAY_TIMERFLAG_MULTIPLY))
  mtf=newfunc_timer_search_entry(func); // update previous instance if exists

 if(!mtf)
  mtf=newfunc_timer_getfree_entry();

 if(!mtf)
  return -1;

 if(!data && (timer_flags&MPXPLAY_TIMERFLAG_MVPDATA))
  data=&mvps;

 return newfunc_timer_add_entry(mtf,func,data,timer_flags,refresh_delay);
}

int mpxplay_timer_modifyfunc(void *func,int timer_flags,int refresh_delay)
{
 mpxplay_timed_s *mtf;

 if(!func)
  return -1;
 mtf=newfunc_timer_search_entry(func);
 if(mtf){
  if(timer_flags>=0)
   mtf->timer_flags=timer_flags;
  if(refresh_delay>=0)
   mtf->refresh_delay=refresh_delay;
  return (int)(mtf-mpxplay_timed_functions);
 }
 return -1;
}

int mpxplay_timer_modifyhandler(void *func,int handlernum_index,int timer_flags,int refresh_delay)
{
 if((handlernum_index>=0) && (handlernum_index<=mtf_storage) && mpxplay_timed_functions){
  mpxplay_timed_s *mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[handlernum_index];
  if(mtf->func==func){
   if(timer_flags>=0)
    mtf->timer_flags=timer_flags;
   if(refresh_delay>=0)
    mtf->refresh_delay=refresh_delay;
   return handlernum_index;
  }
 }
 return -1;
}

void mpxplay_timer_deletefunc(void *func)
{
 mpxplay_timed_s *mtf;

 if(func){
  mtf=newfunc_timer_search_entry(func);
  if(mtf){
   if(mtf->timer_flags&MPXPLAY_TIMERFLAG_BUSY)
    funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERTYPE_REPEAT);
   else
    newfunc_timer_delete_entry(mtf);
  }
 }
}

void mpxplay_timer_deletehandler(void *func,int handlernum_index)
{
 if((handlernum_index>=0) && (handlernum_index<=mtf_storage) && mpxplay_timed_functions){
  mpxplay_timed_s *mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[handlernum_index];
  if(mtf->func==func){
   if(mtf->timer_flags&MPXPLAY_TIMERFLAG_BUSY)
    funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERTYPE_REPEAT);
   else
    newfunc_timer_delete_entry(mtf);
  }
 }
}

void mpxplay_timer_executefunc(void *func)
{
 mpxplay_timed_s *mtf;

 if(func){
  mtf=newfunc_timer_search_entry(func);
  if(mtf && mtf->func){
   if(mtf->data)
    ((call_timedfunc_withdata)(mtf->func))(mtf->data);
   else
    ((call_timedfunc_nodata)(mtf->func))();
  }
 }
}

//------------------------------------------------------------------------

void mpxplay_timer_reset_counters(void)
{
 mpxplay_timed_s *mtf=(mpxplay_timed_s *)mpxplay_timed_functions;
 unsigned int i,clearint08=!(mvps.aui->card_handler->infobits&SNDCARD_INT08_ALLOWED); // ???
 if(!mtf)
  return;
 if(funcbit_test(mtf_flags,MTF_FLAG_LOCK))
  return;
 for(i=0;i<mtf_storage;i++){
  if(mtf->func){

   if((mtf->timer_flags&MPXPLAY_TIMERTYPE_REPEAT) && !(mtf->timer_flags&MPXPLAY_TIMERTYPE_SIGNAL)){
    if(int08counter>mtf->refresh_delay)
     mtf->refresh_counter=int08counter-mtf->refresh_delay;
    else
     mtf->refresh_counter=0;
   }

   if(clearint08)
    funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERTYPE_INT08);
  }
  mtf++;
 }
 funcbit_disable(mpxplay_signal_events,MPXPLAY_SIGNALMASK_TIMER); // !!!
}

#define MPXPLAY_TIMER_MAINCYCLE_EXCLUSION (MPXPLAY_TIMERTYPE_INT08|MPXPLAY_TIMERFLAG_BUSY)

void mpxplay_timer_execute_maincycle_funcs(void) // not reentrant!
{
 unsigned int i;
 //unsigned long used_signal_events=0;
 unsigned long signal_events;
 if(!mpxplay_timed_functions)
  return;
 if(funcbit_test(mtf_flags,MTF_FLAG_LOCK))
  return;
 signal_events=mpxplay_signal_events;
 funcbit_disable(mpxplay_signal_events,MPXPLAY_SIGNALMASK_TIMER);
 for(i=0;i<mtf_storage;i++){
  mpxplay_timed_s *mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[i];
  void *mtf_func=mtf->func;
  if(mtf_func && !(mtf->timer_flags&MPXPLAY_TIMER_MAINCYCLE_EXCLUSION)){

   if(mtf->timer_flags&MPXPLAY_TIMERTYPE_SIGNAL){
    if(signal_events&mtf->refresh_delay){

     if(mtf->timer_flags&MPXPLAY_TIMERFLAG_INDOS){ // ???
      if(pds_filehand_check_infilehand())    //
       continue;                             //
      if(pds_indos_flag())                   //
       continue;                             //
     }                                       //

     funcbit_enable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);

     if(mtf->data)
      ((call_timedfunc_withdata)(mtf_func))(mtf->data);
     else
      ((call_timedfunc_nodata)(mtf_func))();

     mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[i]; // function may modify mpxplay_timed_functions (alloc -> new begin pointer)

     if(!(mtf->timer_flags&MPXPLAY_TIMERTYPE_REPEAT))
      newfunc_timer_delete_entry(mtf);

     funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);
     //funcbit_enable(used_signal_events,mtf->refresh_delay); // ???
     funcbit_enable(mpxplay_signal_events,MPXPLAY_SIGNALTYPE_REALTIME); // ???
    }
   }else{
    if(int08counter>=(mtf->refresh_counter+mtf->refresh_delay)){

     if(mtf->timer_flags&MPXPLAY_TIMERFLAG_LOWPRIOR){
      if(!mpxplay_check_buffers_full(&mvps))
       continue;
     }

     if(mtf->timer_flags&MPXPLAY_TIMERFLAG_INDOS){ // ???
      if(pds_filehand_check_infilehand())    //
       continue;                             //
      if(pds_indos_flag())                   //
       continue;                             //
     }                                       //

     funcbit_enable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);

     mtf->refresh_counter=int08counter;

     if(mtf->data)
      ((call_timedfunc_withdata)(mtf_func))(mtf->data);
     else
      ((call_timedfunc_nodata)(mtf_func))();

     mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[i];

     if(!(mtf->timer_flags&MPXPLAY_TIMERTYPE_REPEAT))
      newfunc_timer_delete_entry(mtf);

     funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);

     if(!mtf->refresh_delay)
      funcbit_enable(mpxplay_signal_events,MPXPLAY_SIGNALTYPE_REALTIME);
    }
   }

  }
 }
 if(mpxplay_signal_events&MPXPLAY_SIGNALMASK_TIMER)
  funcbit_enable(mpxplay_signal_events,MPXPLAY_SIGNALTYPE_REALTIME);
 //funcbit_disable(mpxplay_signal_events,used_signal_events); // ???
 //funcbit_disable(mpxplay_signal_events,MPXPLAY_SIGNALMASK_TIMER);
}

#if defined(__DOS__) && defined(__WATCOMC__)

void call_func_ownstack_withdata(void *data,void *func,void far **oldstack,char far **newstack);
#pragma aux call_func_ownstack_withdata parm [eax][edx][ebx][ecx] = \
  "mov word ptr [ebx+4],ss" \
  "mov dword ptr [ebx],esp" \
  "lss esp,[ecx]" \
  "call edx" \
  "lss esp,[ebx]"

void call_func_ownstack_withdata_sti(void *data,void *func,void far **oldstack,char far **newstack);
#pragma aux call_func_ownstack_withdata_sti parm [eax][edx][ebx][ecx] = \
  "mov word ptr [ebx+4],ss" \
  "mov dword ptr [ebx],esp" \
  "lss esp,[ecx]" \
  "sti"\
  "call edx" \
  "cli"\
  "lss esp,[ebx]"

void call_func_ownstack_nodata(void *func,void far **oldstack,char far **newstack);
#pragma aux call_func_ownstack_nodata parm [eax][edx][ebx] = \
  "mov word ptr [edx+4],ss" \
  "mov dword ptr [edx],esp" \
  "lss esp,[ebx]" \
  "call eax" \
  "lss esp,[edx]"

void call_func_ownstack_nodata_sti(void *func,void far **oldstack,char far **newstack);
#pragma aux call_func_ownstack_nodata_sti parm [eax][edx][ebx] = \
  "mov word ptr [edx+4],ss" \
  "mov dword ptr [edx],esp" \
  "sti"\
  "lss esp,[ebx]" \
  "call eax" \
  "lss esp,[edx]"\
  "cli"

/*void call_func_ownstack_withdata(void *data,void *func,void **oldstack,char **newstack);
#pragma aux call_func_ownstack_withdata parm [eax][edx][ebx][ecx] = \
  "mov dword ptr [ebx],esp" \
  "mov esp,dword ptr [ecx]" \
  "push eax"\
  "call edx" \
  "mov esp,dword ptr [ebx]"

void call_func_ownstack_withdata_sti(void *data,void *func,void **oldstack,char **newstack);
#pragma aux call_func_ownstack_withdata_sti parm [eax][edx][ebx][ecx] = \
  "mov dword ptr [ebx],esp" \
  "mov esp,dword ptr [ecx]" \
  "sti"\
  "push eax"\
  "call edx" \
  "cli"\
  "mov esp,dword ptr [ebx]"

void call_func_ownstack_nodata(void *func,void **oldstack,char **newstack);
#pragma aux call_func_ownstack_nodata parm [eax][edx][ebx] = \
  "mov dword ptr [edx],esp" \
  "mov esp,dword ptr [ebx]" \
  "call eax" \
  "mov esp,dword ptr [edx]"\

void call_func_ownstack_nodata_sti(void *func,void **oldstack,char **newstack);
#pragma aux call_func_ownstack_nodata_sti parm [eax][edx][ebx] = \
  "mov dword ptr [edx],esp" \
  "sti"\
  "mov esp,dword ptr [ebx]" \
  "call eax" \
  "mov esp,dword ptr [edx]"\
  "cli"*/

#endif // __DOS__ && __WATCOMC__

#define MPXPLAY_TIMER_MAX_PARALELL_INT08_THREADS 8

#define MPXPLAY_TIMER_INT08_EXCLUSION (MPXPLAY_TIMERTYPE_SIGNAL|MPXPLAY_TIMERFLAG_BUSY)

void mpxplay_timer_execute_int08_funcs(void)
{
 static unsigned int recall_counter;
 unsigned int i;
 if(!mpxplay_timed_functions)
  return;
 if(funcbit_test(mtf_flags,MTF_FLAG_LOCK))
  return;
 if(recall_counter>=MPXPLAY_TIMER_MAX_PARALELL_INT08_THREADS)
  return;
 recall_counter++;
 for(i=0;(i<mtf_storage) && mpxplay_timed_functions;i++){
  mpxplay_timed_s *mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[i];
  void *mtf_func=mtf->func;
  if(mtf_func && (mtf->timer_flags&MPXPLAY_TIMERTYPE_INT08) && !(mtf->timer_flags&MPXPLAY_TIMER_INT08_EXCLUSION)){
   if(int08counter>=(mtf->refresh_counter+mtf->refresh_delay)){

    if(mtf->timer_flags&MPXPLAY_TIMERFLAG_INDOS){
     if(oldint08_running)
      continue;
     if(pds_filehand_check_infilehand())
      continue;
     if(pds_indos_flag())
      continue;
    }

    funcbit_enable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);

    mtf->refresh_counter=int08counter;

#if defined(__DOS__) && defined(__WATCOMC__)
    if(mtf->timer_flags&MPXPLAY_TIMERFLAG_OWNSTACK){
     mtf->newstack=(char far *)(mtf->ownstackmem+MPXPLAY_TIMERS_STACKSIZE);
     if(mtf->data){
      if(mtf->timer_flags&MPXPLAY_TIMERFLAG_STI)
       call_func_ownstack_withdata_sti(mtf->data,mtf_func,&mtf->oldstack,&mtf->newstack);
      else
       call_func_ownstack_withdata(mtf->data,mtf_func,&mtf->oldstack,&mtf->newstack);
     }else{
      if(mtf->timer_flags&MPXPLAY_TIMERFLAG_STI)
       call_func_ownstack_nodata_sti(mtf_func,&mtf->oldstack,&mtf->newstack);
      else
       call_func_ownstack_nodata(mtf_func,&mtf->oldstack,&mtf->newstack);
     }
    }else
#endif
    { // no stack, no sti
     if(mtf->data)
      ((call_timedfunc_withdata)(mtf_func))(mtf->data);
     else
      ((call_timedfunc_nodata)(mtf_func))();
    }

    if(!mpxplay_timed_functions)
     break;

    mtf=(mpxplay_timed_s *)&mpxplay_timed_functions[i];

    if(!(mtf->timer_flags&MPXPLAY_TIMERTYPE_REPEAT))
     newfunc_timer_delete_entry(mtf);

    funcbit_disable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);
   }
  }
 }

 if(recall_counter>0)
  recall_counter--;
}

static void mpxplay_timer_delete_int08_funcs(void)
{
 unsigned int i,retry,countend=int08counter+64;
 if(!mpxplay_timed_functions)
  return;
 do{
  mpxplay_timed_s *mtf=(mpxplay_timed_s *)mpxplay_timed_functions;
  retry=0;
  for(i=0;i<mtf_storage;i++,mtf++){
   if(mtf->func && (mtf->timer_flags&MPXPLAY_TIMERTYPE_INT08)){
    if(!(mtf->timer_flags&MPXPLAY_TIMERFLAG_BUSY) || (int08counter>countend)){
     funcbit_enable(mtf->timer_flags,MPXPLAY_TIMERFLAG_BUSY);
     newfunc_timer_delete_entry(mtf);
    }else
     retry=1;
   }
  }
 }while(retry);
}

//----------------------------------------------------------------------
// INT08 handling (interrupt decoder/dma_monitor/etc.)
#ifdef __DOS__
extern unsigned int  intsoundcontrol;
extern unsigned long intdec_timer_counter;

static unsigned int oldint08_timercount;
void (__far __interrupt *oldint08_handler)();

void loades(void);
#pragma aux loades = "push ds" "pop es"

void savefpu(void);
#pragma aux savefpu = "sub esp,200" "fsave [esp]"

void clearfpu(void);
#pragma aux clearfpu = "finit"

void restorefpu(void);
#pragma aux restorefpu = "frstor [esp]" "add esp,200"

void cld(void);
#pragma aux cld="cld"

static void __interrupt __loadds newhandler_08(void)
{
 savefpu();
 clearfpu();
#ifdef __WATCOMC__
 loades();
#endif

 int08counter++; // for the general timing

 intdec_timer_counter+=INT08_DIVISOR_NEW; // for CPU usage (at interrupt-decoder)

 oldint08_timercount+=INT08_DIVISOR_NEW; // for the old-int08 handler

 if((oldint08_timercount&0xFFFF0000) && !oldint08_running){
  oldint08_running=1;
  oldint08_timercount-=0x00010000;
  oldint08_handler();
  cld();
  oldint08_running=0;
 }else{
  outp(0x20,0x20);
 }

 mpxplay_timer_execute_int08_funcs();

 restorefpu();
}

#endif // __DOS__

void newfunc_newhandler08_init(void)
{
#ifdef __DOS__
 if(!oldint08_handler){
  oldint08_handler=(void (__far __interrupt *)())pds_dos_getvect(MPXPLAY_TIMER_INT);
  pds_dos_setvect(MPXPLAY_TIMER_INT,newhandler_08);
  outp(0x43, 0x34);
  outp(0x40, (INT08_DIVISOR_NEW&0xff));
  outp(0x40, (INT08_DIVISOR_NEW>>8));
 }
#endif
}

void newfunc_newhandler08_close(void)
{
 funcbit_disable(intsoundcontrol,INTSOUND_DECODER);
 mpxplay_timer_close();
#ifdef __DOS__
 if(oldint08_handler){
  pds_dos_setvect(MPXPLAY_TIMER_INT,oldint08_handler);
  outp(0x43, 0x34);
  outp(0x40, 0x00);
  outp(0x40, 0x00);
 }
#endif
}
