/* MULTI.C */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <io.h>
#include "tsr.h"

#define SEARCH_DIR   "C:\\SPOOL\\"
#define STACK_SIZE  4096    /* must be 16 byte boundaray */
#define SET_DTA     0x1a  /* SET Disk Transfer Address */
#define GET_DTA     0x2f  /* GET Disk  Transfer Address */

#define BACKGROUND_TICS 2
#define FOREGROUND_TICS 16
#define BACKGROUND_YIELD 0
#define FOREGROUND_YIELD 0

struct  prReq
{
    char level;
    char far *fname;
};

char    far *stack_ptr;   /* stack for our background TSR */
char    far *ptr;
unsigned ss_save;         /* slot for stack segment register */
unsigned sp_save;         /* slot for stack pointer register */
unsigned unsafe_flag = 0; /* set true by variouse interrupts */
int first_time = 1;       /* flag for first time in running background */

int my_psp;               /* our TSR's psp */
int foreground_psp;       /* PSP of interrupted foreground process */
int foreground_dta_seg;   /* DTA of interrupted foreground process */
int foreground_dta_off;
int ctr=0; 
int tic_count = 0;        /* counts timer tices */
int in_progress = 0;      /* true if we're in background process */

char search_work[65];       
struct ExtErr my_ErrInfo;
struct ExtErr foreground_ErrInfo;

int foreground_limit = FOREGROUND_TICS; /* foreground cycle limit */
int background_limit = BACKGROUND_TICS; /* background cycle limit */

char search_dir[65] = {SEARCH_DIR}; /* dir to search for spool files */
volatile int   int_28_active = 0;    /* true if activated by INT 28 */
volatile int   interval_timer;       /* for sleeping a number of tics */ 

/* old interrupt pointers are stored here */    
INTVECT old_int8, old_int9, old_int10, old_int13;
INTVECT old_int1B, old_int23, old_int24, old_int25;
INTVECT old_int26, old_int28;

/* prototypes for this module */
void main_loop();
void interrupt far new_int8(INTERRUPT_REGS);
void interrupt far new_int9(INTERRUPT_REGS);
void interrupt far new_int10(void);
void interrupt far new_int13(void);
void interrupt far new_int1B(INTERRUPT_REGS);
void interrupt far new_int23(INTERRUPT_REGS);
void interrupt far new_int24(INTERRUPT_REGS);
void interrupt far new_int25(void);
void interrupt far new_int26(void);
void interrupt far new_int28(INTERRUPT_REGS);
int  spooler_active(void);
int  search_spl_que(char * fname);
void suspend_foreground(void);
void suspend_background(void);

/* returns nonzero if PRINT installed */
int spooler_active()
{
    union REGS  regs;

    regs.x.ax = 0x0100;         /* PRINT install check */
    int86(0x2f,&regs,&regs);    /* call multiplex interrupt */
    return(regs.h.al == 0xff);  /* FF if installed */
}

/* returns nonzero if file is in the spooler queue */
int search_spl_que(char * fname)
{
    union REGS  regs;
    struct SREGS   sregs;
    char far *  que_ptr;
    char que_name[65];
    int i;
    int found = 0;

   if (spooler_active())
   {
      regs.x.ax = 0x0104;  /* get spooler status */
      int86x(0x2f,&regs,&regs,&sregs);
      /* on return from call DS:SI points to print queue */
      FP_SEG(que_ptr) = sregs.ds;
      FP_OFF(que_ptr) = regs.x.si;
      /* release hold on spooler, side effect of status*/
      regs.x.ax = 0x0105;
      int86x(0x2f,&regs,&regs,&sregs);
      while (*que_ptr && !found)  /* while items in queue */
      {
         for (i = 0; i < 65; i++)
            que_name[i] = *(que_ptr + i);
         if (found = !strcmpi(que_name,fname))
             break;
         que_ptr += 65;
      }
   }

   return(found);
}

void main_loop()
{
    struct find_t c_file;
    union REGS regs;
    struct SREGS sregs;
    struct prReq prRequest;
    struct prReq far * ptr;

    while (1)
    {   
      strcpy(search_work,search_dir);
      strcat(search_work,"*.SPL");   /* create dir search string */
   
      interval_timer = 18 * 30;     /* search every 30 seconds */
      while (interval_timer)    /* wait between each dir search */
          background_limit = BACKGROUND_YIELD;  /* yield for fgrnd */

      if (!_dos_findfirst(search_work,_A_NORMAL,&c_file))
      {
         /* if spooler installed, dos 3.xx+ and file size > 0 */
         if (spooler_active() && _osmajor >= 3 && c_file.size)
         {
           strcpy(search_work,search_dir);
           strcat(search_work,c_file.name); /* full pathname */
           prRequest.level = 0;
           prRequest.fname = search_work;
           regs.x.ax = 0x0101;
           ptr = &prRequest;
           sregs.ds = FP_SEG(ptr);
           regs.x.dx= FP_OFF(ptr);
           int86x(0x2f,&regs,&regs,&sregs);

           while (search_spl_que(search_work))  /* wait till done */
           {
               interval_timer = 18 * 30;  /* sleep for 30 seconds */
               while (interval_timer)
                   background_limit = BACKGROUND_YIELD;
           }

           unlink(search_work);     /* delete file */
           background_limit = BACKGROUND_YIELD;
        }
      }
   }
}

union REGS regs;
struct SREGS sregs;

void suspend_foreground()
{
    /* SWAP TO BACKGROUND */
    tic_count = 0;
    /* save old handlers */
    old_int1B= _dos_getvect(0x1B); 
    old_int23= _dos_getvect(0x23); 
    old_int24= _dos_getvect(0x24); 

    /* set our interrupt handlers */
    _dos_setvect(0x1b,new_int1B);
    _dos_setvect(0x23,new_int23);
    _dos_setvect(0x24,new_int24);

    /* save current PSP and set to ours */
    foreground_psp = GetPSP();
    SetPSP(my_psp);

    /* get foreground DTA */
    regs.h.ah = GET_DTA;
    intdosx(&regs, &regs, &sregs); 
    foreground_dta_seg = sregs.es;
    foreground_dta_off = regs.x.bx;

    /* set up our DTA */
    regs.h.ah = SET_DTA;
    regs.x.dx = 0x80;   /* use default in PSP area */
    sregs.ds = my_psp;
    intdosx(&regs, &regs, &sregs); 

    /* save error info */
    GetExtErr(&foreground_ErrInfo);

    if (! first_time)
        SetExtErr(&my_ErrInfo);

    in_progress = 1;
    background_limit = BACKGROUND_TICS; /* set default limit */
}

void suspend_background()
{
    /* SWAP TO FOREGROUND */

    /* put back original DTA */
    regs.h.ah = SET_DTA;
    regs.x.dx = foreground_dta_off; 
    sregs.ds  = foreground_dta_seg;
    intdosx(&regs, &regs, &sregs); 

    /* put back original PSP */
    SetPSP(foreground_psp);

    /* put back original INTS */
    _dos_setvect(0x1b,old_int1B);
    _dos_setvect(0x23,old_int23);
    _dos_setvect(0x24,old_int24);

    /* get error info */
    GetExtErr(&my_ErrInfo);
    SetExtErr(&foreground_ErrInfo);

    tic_count = 0;  
    in_progress = 0;
    int_28_active = 0;
    foreground_limit = FOREGROUND_TICS; /* set default limit */
}

/**********
* TIMER TICK INTERRUPT HANDLER
**********/
void interrupt far new_int8(INTERRUPT_REGS r)
{
    tic_count++;
    
    if (interval_timer)
        interval_timer--;

    if ((in_progress && (tic_count >= background_limit) &&  
       !DosBusy() && !unsafe_flag) || 
       (in_progress && int_28_active && !Int28DosBusy() && 
           (tic_count >=background_limit)))
    {
      suspend_background();
      restore_stack();
    }
    else if ((!in_progress && (tic_count >= foreground_limit) &&
        !DosBusy() && !unsafe_flag) ||
       (!in_progress && int_28_active && !Int28DosBusy() && 
           (tic_count >=foreground_limit)))
    {
      set_stack();
      suspend_foreground();
      if (first_time)
      {
         first_time = 0;
         timer_int_chain();
      }
    }
   old_int8();    /* call old handler */
} 

/**********
* KEYBOARD INTERRUPT HANDLER
**********/
void interrupt far new_int9(INTERRUPT_REGS r)
{
   unsafe_flag++;
   old_int9();     
   if (in_progress)
      background_limit = BACKGROUND_YIELD; /* set to swap to fgrnd */  
   foreground_limit = 18;               /* since user hit keyboard */
   unsafe_flag--;
} 

/*********
* CTRL-BREAK INTERRUPT HANDLER
*********/
void interrupt far new_int1B(INTERRUPT_REGS r)
{
    /* do nothing */
} 
                  
/**********
* CTRL-C INTERRUPT HANDLER
**********/
void interrupt far new_int23(INTERRUPT_REGS r)
{
    /* do nothing */
} 

/**********
* CRTITICAL ERROR INTERRUPT HANDLER
**********/
void interrupt far new_int24(INTERRUPT_REGS r)
{
    if (_osmajor >= 3)
        r.ax = 3;   /* fail dos function */
    else
        r.ax = 0;        
} 

/**********
* DOS IDLE INTERRUPT HANDLER
**********/
void interrupt far new_int28(INTERRUPT_REGS r)
{
    if (!in_progress && !Int28DosBusy() && !unsafe_flag && 
        tic_count > foreground_limit)
    {
      foreground_limit = FOREGROUND_YIELD;   /* stop foreground */
      int_28_active = 1;      
      _enable();              /* STI */
      while (int_28_active)
          ;        /*spin waiting for task swap to bckgrnd*/
    }
   (*old_int28)();    /* call old handler */
} 

main()
{
    unsigned        memtop;
    unsigned        dummy;
    void far* far*  tmpptr;

    puts("Multi-Tasking PRINT spooler installing");

    if (_osmajor < 3)
    {
        puts("Error: MS-DOS version 3.00 or greater required");
        exit(1);
    }

    if (! spooler_active())
        puts("Warning: Print Spooler not active");

    InitInDos();
    my_psp = GetPSP();

    /* MALLOC a stack for our TSR section */ 
    stack_ptr = malloc(STACK_SIZE);      
    stack_ptr += STACK_SIZE;

    ptr = stack_ptr;
    *(--stack_ptr) = 0xF2;  /* set up stack as if an an IRET was done*/
    *(--stack_ptr) = 0x02;
    stack_ptr -= 4;
    tmpptr = stack_ptr;
    *(tmpptr) = main_loop;

    /* get interrupt vectors */
    old_int8  = _dos_getvect(0x08); /* timer int */
    old_int9  = _dos_getvect(0x09); /* keyboard int */
    old_int10 = _dos_getvect(0x10); /* video int */
    old_int13 = _dos_getvect(0x13); /* disk int */
    old_int25 = _dos_getvect(0x25); /* sector read int */
    old_int26 = _dos_getvect(0x26); /* sector write int */
    old_int28 = _dos_getvect(0x28); /* dos idle int */

    init_intr();    /* init asm variables */ 

    _dos_setvect(0x08,new_int8); 
    _dos_setvect(0x09,new_int9); 
    _dos_setvect(0x10,new_int10); 
    _dos_setvect(0x13,new_int13); 
    _dos_setvect(0x25,new_int25); 
    _dos_setvect(0x26,new_int26); 
    _dos_setvect(0x28,new_int28); 

#define PARAGRAPHS(x)   ((FP_OFF(x) + 15) >> 4)

    /* release unused heap to MS-DOS */
    /* All MALLOCS for TSR section must be done in TSR_INIT() */
    /* calculate top of memory, shrink block, and go TSR */
    segread(&sregs);    
    memtop = sregs.ds + PARAGRAPHS(ptr) - _psp;
    
    _dos_setblock(memtop, _psp, &dummy);
    _dos_keep(0, memtop);
}

