_TEXT          SEGMENT PUBLIC 'CODE'
               ASSUME  CS:_TEXT,DS:_TEXT,ES:_TEXT,SS:_TEXT

               ORG     80H
DTA            LABEL   BYTE

               ORG     100H
START:         JMP     INITIALIZE

;              DATA AREA
;              ---------
CR             EQU     13
LF             EQU     10
FF             EQU     12
CTRL_Z         EQU     26
SPACE          EQU     32
BOX            EQU     254
BELL           EQU     7
TAB            EQU     9
ESC_SCAN       EQU     1
ENTER_SCAN     EQU     1CH
Y_SCAN         EQU     15H
N_SCAN         EQU     31H
F1_SCAN        EQU     3BH
F2_SCAN        EQU     3CH
F6_SCAN        EQU     40H
F7_SCAN        EQU     41H
F8_SCAN        EQU     42H
KB_FLAG        EQU     17H

APP_LEN        EQU     29                      ;Chars per appointment.
APP_HEIGHT     EQU     16                      ;Appointment rows.
NOTE_LEN       EQU     73                      ;Notepad line length.
NOTE_HEIGHT    EQU      3                      ;Notepad rows.
DATE_LEN       EQU     11                         ;Date length in ASCII.
NOTEPAD_LEN    EQU     (81 - 4) + 81 + (81 - 4)   ;Total notepad size.

DATA_RECORD    STRUC
DATE_BINARY    DW      2 DUP (?)
DATE_ASCII     DB      DATE_LEN DUP (?)
APPOINT_TEXT   DB      (APP_HEIGHT * APP_LEN * 2) DUP (?)
NOTEPAD_TEXT   DB      NOTEPAD_LEN DUP (?)
DATA_RECORD    ENDS

BUFFER         DB      (SIZE DATA_RECORD - ($ - OFFSET DTA)) DUP (?)

SIGNATURE      DB      SPACE,CR,CR,LF
COPYRIGHT      DB      "SCHEDULE 1.0 (C) 1989 Ziff Communications Co.",CR,LF
PROGRAMMER     DB      "PC Magazine ",BOX," Michael J. Mefford",CR,LF,LF,"$"
               DB      CTRL_Z

COLOR_ATTRIBS  STRUC
B              DB     017H                     ;White on blue.
H              DB     071H                     ;Blue on white.
Y              DB     01EH                     ;Yellow on blue.
C              DB     03BH                     ;Bright cyan on cyan.
A              DB     01BH                     ;Bright cyan on blue.
P              DB     07EH                     ;Yellow on white.
COLOR_ATTRIBS  ENDS

COLOR          COLOR_ATTRIBS  <>

COLOR_ATTR     COLOR_ATTRIBS  <>
MONO_ATTR      COLOR_ATTRIBS  <07H, 70H, 70H, 70H, 07H, 07H>

MONO_FLAG      DB      0                       ; =1 if forced Black and white.
NOTE           DW      1046                    ;C note

DOS_SEGMENT    DW      ?                    ;Segment of internal DOS flags.
INDOS_OFFSET   DW      ?                    ;Offset of INDOS flag.
ERRFLAG_OFFSET DW      ?                    ;Offset of critical error flag.
PROGRAM_STATUS DB      0                    ;Popup status; non-zero=popped up.

BUSY_FLAGS     LABEL   WORD
FLAG_10h       DB      0                    ;Status of interrupt 10h.
FLAG_13h       DB      0                    ;Status of interrupt 13h.
BACK_FLAGS     LABEL   WORD
FLAG_8h        DB      0                    ;Status of interrupt 8h.
FLAG_28h       DB      0                    ;Status of interrupt 28h.

REQUEST_FLAG   DB      0                    ;Status of processing request.
SS_REGISTER    DW      ?                    ;SS register storage.
SP_REGISTER    DW      ?                    ;SP register storage.
OLDPSP         DW      ?                    ;PSP segment storage.

BIOS_ACTIVE_PAGE       EQU     62H
ACTIVE_PAGE    DB      ?
ADDR_6845      DW      ?
BIOS_CRT_MODE          EQU     49H
CRT_MODE       DB      ?
CRT_COLS       DW      ?
CRT_LEN        DW      ?
CRT_START      DW      ?
CRT_DATA_LENGTH        EQU     $ - CRT_MODE

CRT_WIDTH      DW      ?                    ;Width in bytes of CRT.
CRT_ROWS       DB      ?
VIDEO_SEG      DW      ?
STATUS_REG     DW      ?
CURSOR_MODE    DW      ?                    ;Cursor shape.
CURSOR_POS     DW      ?                    ;Cursor position.
CURSOR_ADDR    DW      ?                    ;Cursor CRTC address.

DOS_VERSION    DW      ?
TSR_SEGMENT    DW      ?

OLD8           DW      ?,?                     ;Old interrupt addresses.
OLD9           DW      ?,?
OLD10          DW      ?,?
OLD13          DW      ?,?
OLD28          DW      ?,?

OLD1B          DW      ?,?
OLD23          DW      ?,?
OLD24          DW      ?,?
OLD_DTA        DW      ?,?
BREAK          DB      ?                       ;Ctrl break state.

CLOCK          DB      "xx:xxxm",0

STATE          DW      CALENDAR                ;Pop up screen.
EXIT_FLAG      DB      0                       ; =1 if Hotkey pressed.

CTRL_STATE     EQU     4
ALT_STATE      EQU     8

COMBO          DB      "C"
HOT_KEY_SCAN   DB      2EH                     ;"C"
HOT_SHIFT_KEY  DB      ALT_STATE

MODIFY_FLAG    DB      0                       ; =1 if changes made in apps.

PORT_A         EQU     60H
PORT_B         EQU     61H
COMMAND_PORT   EQU     20H
EOI            EQU     20H

MATCHING       STRUC
RESERVED       DB      21 DUP (?)
ATTRIBUTE      DB              ?
FILE_TIME      DW              ?
FILE_DATE      DW              ?
SIZE_LOW       DW              ?
SIZE_HIGH      DW              ?
FILE_NAME      DB      13 DUP (?)
MATCHING       ENDS

;---------------------------------------------------------------------------;
; Code Format: 0 = compressed string; followed by string length and char.   ;
;              1 = display drop shade char.                                 ;
;              2 = color follows.                                           ;
;              3 = string end; if followed by -1 then menu end.             ;
;              4 = drop shade string end followed by string length.         ;
;              5 = insert date                                              ;
;              6 = insert appointment blocks                                ;
;              7 = skip count / 2 follows.                                  ;
;---------------------------------------------------------------------------;
CAL_MENU       LABEL   BYTE
DB 2,B
DB " F2 Save  F3 Today  F4 Purge  F5 Print   ÄÙ= Appoint.  PgUp/Dn=Month  Esc=Exit ",3
DB " ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ",3
DB " ³     Schedule þ PC Magazine þ Michael J. Mefford ³ ",2,Y,SPACE
CAL_DATE       LABEL   BYTE
DB                                                       "                ",7,14,2,B," ³ ",3
DB " ÆÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÏÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍµ ",3
DB " ³   "
DAYS           LABEL   BYTE
DB      "Sun    ³   Mon    ³   Tue    ³   Wed    ³   Thu    ³   Fri    ³   Sat    ³ ",3
DB " ÃÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄ´ ",3,-1

CAL_DUP        LABEL   BYTE
DB " ÃÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁÅÄÄÁÁÁÁÁÁÁÁ´ ",3
CAL_MIDDLE     LABEL   BYTE
DB " ³",7 DUP (5,6,"´"),SPACE,3
DB " ³",7 DUP (SPACE,SPACE,6,"´"),SPACE,3,-1

DB " ÀÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÁÄÄÁÁÁÁÁÁÁÁÙ ",3
DB " Appointment block 1 hour periods; 6:00am - 1:30 1st row; 2:00pm - 9:30 2nd row ",3,-1


APPOINTMENT_BLOCKS     LABEL BYTE              ;Storage for small block chars.
BLOCK_COUNT    EQU     31 * 16
DB          BLOCK_COUNT DUP (SPACE)

APP_MENU       LABEL   BYTE
DB 2,B
DB " F2 Save  F3 Today  F4 Purge  F5 Print  F6 Clear Line   PgUp/Dn=Day    Esc=Exit ",3
DB " ÚÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ",3
DB " Æµ Appointment þ PC Magazine þ Michael J. Mefford ³ ",2,Y,SPACE
APP_DATE       LABEL   BYTE
DB                                                       "                ",7,14,2,B," ³ ",3
DB " ÆØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍµ ",3
APP_START      LABEL   BYTE
APP_COL_SPACE    EQU   7                       ;Distance from end left to start right.
APP_ROW_SPACE    EQU   16                      ;Distance from end right to start left.
APP_LEFT_START   EQU   $ + 12
APP_RIGHT_START  EQU   APP_LEFT_START + APP_LEN + APP_COL_SPACE
APP_NOTE_SPACE   EQU   8
DB " Æµ  6:00am                                2:00                               ³ ",3
DB " Æµ  6:30                                  2:30                               ³ ",3
DB " Æµ  7:00                                  3:00                               ³ ",3
DB " Æµ  7:30                                  3:30                               ³ ",3
DB " Æµ  8:00                                  4:00                               ³ ",3
DB " Æµ  8:30                                  4:30                               ³ ",3
DB " Æµ  9:00                                  5:00                               ³ ",3
DB " Æµ  9:30                                  5:30                               ³ ",3
DB " Æµ 10:00                                  6:00                               ³ ",3
DB " Æµ 10:30                                  6:30                               ³ ",3
DB " Æµ 11:00                                  7:00                               ³ ",3
DB " Æµ 11:30                                  7:30                               ³ ",3
DB " Æµ 12:00pm                                8:00                               ³ ",3
DB " Æµ 12:30                                  8:30                               ³ ",3
DB " Æµ  1:00                                  9:00                               ³ ",3
DB " Æµ  1:30                                  9:30                               ³ ",3
DB " ÆØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ[Mini Notepad]ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍµ ",3
DB " Æµ "
NOTEPAD        LABEL   BYTE
DB     "                                                                          ³ ",3
DB " Æµ                                                                           ³ ",3
DB " Æµ                                                                           ³ ",3
APP            LABEL   BYTE
DB " ÀÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ",3,-1
APP_WIDTH      EQU     $ - APP - 1

;----------------------------------------------;
DISK_FULL_MSG  DB 2,H,"É",      0,25,"Í",       "»",3
               DB     "º  Not enough disk space. º",1
               DB     "º     Press any key.      º",1
               DB     "È",      0,25,"Í",       "¼",1,4,27

PERMANENT_MSG  DB 2,H,"É",      0,25,"Í",       "»",3
               DB     "º Do you wish to save the º",1
               DB     "º change to disk?  Y/N    º",1
               DB     "È",      0,25,"Í",       "¼",1,4,27

PERMANENT_FAIL DB 2,H,"É",      0,25,"Í",       "»",3
               DB     "º  Update failed.  Press  º",1
               DB     "º  any key to continue.   º",1
               DB     "È",      0,25,"Í",       "¼",1,4,27

PRINTER_NO     DB 2,H,"É",      0,35,"Í",                 "»",3
               DB     "º Select printer by pressing 1 or 2 º",1
               DB     "È",      0,35,"Í",                 "¼",1,4,37


SAVED_MSG      DB 2,H,"É",0,9, "Í»",3
               DB     "º  Saved  º",1
               DB     "È",0,9, "Í¼",1,4,11

DISK LABEL BYTE
DB 2,H,"É",      0,27,"Í",         "»",3
DB     "º",      0,27,SPACE,       "º",1
DISK_NAME     LABEL  BYTE
DB     "º   Error reading drive X   º",1,3,-1

ERR_BOT LABEL BYTE
DB     "º   (R)etry or (A)bort ?    º",1
DB     "º",      0,27,SPACE,       "º",1
DB     "È",      0,27,"Í",         "¼",1,4,ERR_WIDTH

PRN LABEL BYTE
DB 2,H,"É",      0,27,"Í",           "»",3
DB     "º",      0,27,SPACE,         "º",1
DB     "º",0,7," Printer Error",0,7," º",1,3,-1

ERR_WIDTH      EQU     29
ERR_HEIGHT     EQU     6
ERR_ROW        EQU     ((25 - ERR_HEIGHT) / 2) + 3
ERR_COL        EQU     ((80 - ERR_WIDTH) / 2)


PURGE_MSG      DB 2,H,"É",0,41,               "Í",              "»",3
               DB     "º Appointment Purge Date: ",2,P
PUR_DATE       DB                               "                ",2,H,"º",1
               DB     "º   Use: PgUp/Dn to change Purge Date     º",1
               DB     "º   F7 Purge up to, but excluding Date    º",1
               DB     "º   F8 Purge Date Only     Esc to Cancel  º",1
               DB     "È",0,41,               "Í",              "¼",1,4,43

ARCHIVE_MSG    DB 2,H,"É",0,17,        "Í»",3
               DB     "º  Archive  Y/N?  º",1
               DB     "È",0,17,        "Í¼",1,4,19

PURGING_MSG    DB 2,H,"É",0,11,  "Í»",3
               DB     "º  Purging  º",1
               DB     "È",0,11,  "Í¼",1,4,13

;------------------------------------------------------------------------------
;Execution comes here thru interrupt 9 every time a key is pressed or released.
;------------------------------------------------------------------------------
KEYBOARD       PROC    NEAR
               PUSH    AX
               PUSH    DS

               MOV     AX,40H
               MOV     DS,AX
               MOV     AH,DS:[KB_FLAG]         ;Get keyboard shift status
               PUSH    CS
               POP     DS
KEYSTROKE:     IN      AL,PORT_A
               TEST    AH,HOT_SHIFT_KEY        ;Our shift key?
               JZ      KB_EXIT                 ;No, then exit
               CMP     AL,HOT_KEY_SCAN         ;Our hotkey?
               JNZ     KB_EXIT
               CMP     PROGRAM_STATUS,0        ;Popup routine already active?
               JNZ     KB_EXIT                 ;Yes, then ignore keypress

               IN      AL,PORT_B               ;Retrieve Port B.
               OR      AL,80H                  ;Turn bit 7 on to reset
               JMP     $ + 2                   ;I/O delay.
               OUT     PORT_B,AL               ;Reset KBD.
               AND     AL,NOT 80H              ;Turn bit 7 back off.
               JMP     $ + 2                   ;I/O delay.
               OUT     PORT_B,AL               ;Restore port.

               CLI                             ;Interrupts off.
               MOV     AL,EOI                  ;Send End Of Interrupt
               OUT     COMMAND_PORT,AL         ; to 8259A PIC.

               MOV     REQUEST_FLAG,18         ;Try to popup for one second.
               POP     DS
               POP     AX
               IRET

KB_EXIT:       POP     DS
               POP     AX
               JMP     DWORD PTR CS:OLD9       ;Call keyboard handling routine.
KEYBOARD       ENDP

;------------------------------------------------------------------------------
;Interrupt 8 handling routine.
;------------------------------------------------------------------------------
LAST_MIN       DB      ?                       ;Last minute.
BELL_FLAG      DB      0                       ; =1 if chiming.
BELL_CNT       DB      ?                       ; no. of chimes.
POPUP_FLAG     DB      0                       ; =1 if alarm or day rollover.
NOPOPUP_FLAG   DB      0                       ; =1 if ignore POPUP_FLAG.
NOBELL_FLAG    DB      0                       ; =1 if ignore chime.
NOMIDNIGHT_FLAG DB     0                       ; =1 if ignore rollover pop up.
ALARM_FLAG     DB      0                       ; =1 only on alarm to avoid homing cursor.
REREAD_FLAG    DB      0                       ; =1 if forced reread of disk.

TIMER          PROC    NEAR
               CLI
               PUSHF
               CALL    CS:DWORD PTR OLD8
               PUSH    AX
               PUSH    BX
               PUSH    CX
               PUSH    DX
               PUSH    SI
               PUSH    DI
               PUSH    DS
               PUSH    ES
               PUSH    BP
               CLD
               MOV     BX,CS
               MOV     DS,BX
               CMP     PROGRAM_STATUS,0        ;Already popped up?
               JNZ     CK_TIME
               CMP     FLAG_8h,0
               JNZ     CK_TIME
               CMP     POPUP_FLAG,1            ;Alarm or rollover popup?
               JNZ     CK_REQ_FLAG
               MOV     REQUEST_FLAG,18         ;Request to popup for 1 sec.
CK_REQ_FLAG:   CMP     REQUEST_FLAG,0
               JZ      CK_TIME

CK_TSRSTATE:   INC     FLAG_8h
               STI
               CALL    TSR_STATE               ;Save to enter Dos? 
               JC      DECTIME
               CALL    MAIN
               JMP     SHORT TIMER_DONE


DECTIME:       CMP     REQUEST_FLAG,0
               JZ      TIMER_DONE
               DEC     REQUEST_FLAG
               JZ      TIMER_BEEP
               CMP     REQUEST_FLAG,9          ;Beep twice, once every 1/2 sec.
               JNZ     TIMER_DONE
TIMER_BEEP:    CALL    BEEP
TIMER_DONE:    CLI
               DEC     FLAG_8h

TIMER_END:     POP     BP
               POP     ES
               POP     DS
               POP     DI
               POP     SI
               POP     DX
               POP     CX
               POP     BX
               POP     AX
               IRET

;----------------------------------------------;
CK_TIME:       STI
               MOV     AX,40H
               MOV     ES,AX
               MOV     CX,ES:[6CH]             ;TIMER_LOW
               MOV     AX,ES:[6EH]             ;TIMER_HIGH
               MOV     BP,AX                   ;Save hour.
               PUSH    CS
               POP     ES

               MOV     BL,"a"                  ;Convert time to 12 hour version.
               CMP     AX,24
               JZ      GOT_MUNDI
               CMP     AX,11
               JBE     GOT_MUNDI
               MOV     BL,"p"
GOT_MUNDI:     OR      AX,AX
               JNZ     CK_NOON
               MOV     AX,12
CK_NOON:       CMP     AX,12
               JBE     GOT_HOUR
               SUB     AX,12
GOT_HOUR:      MOV     DI,OFFSET CLOCK
               XOR     SI,SI                   ;Suppress leading zero.
               CALL    STORE_NUMBER

               MOV     AX,CX
               XOR     DX,DX
               MOV     CX,1093                 ;Convert ticks to minutes.
               DIV     CX
               INC     SI                      ;Store leading zero.
               MOV     CL,CLOCK + 4            ;Retrieve current minute.
               MOV     LAST_MIN,CL             ;Store new current minute.
               CALL    STORE_NUMBER
               MOV     [DI - 1],BL             ;Store am or pm.

CK_DISP_CLOCK: CMP     PROGRAM_STATUS,2        ;If not popped up, no
               JNZ     CK_BELL                 ; clock display.
DISP_TIME:     MOV     AX,2
               CALL    CALC_ADDR
               ADD     DI,70 * 2
               MOV     BH,COLOR.Y
               MOV     SI,OFFSET CLOCK
               MOV     DX,STATUS_REG
               PUSH    ES
               MOV     ES,VIDEO_SEG
               JMP     SHORT WRITE_CLOCK
NEXT_CLOCK:    CALL    WRITE_SCREEN            ;Display clock.
WRITE_CLOCK:   LODSB
               OR      AL,AL
               JNZ     NEXT_CLOCK
               POP     ES

CK_BELL:       CMP     BELL_FLAG,0             ;Is alarm chiming?
               JZ      CK_MINUTE
               JMP     DO_BELL

CK_MINUTE:     MOV     AX,WORD PTR CLOCK + 3   ;Retrieve current minute.
               CMP     AH,LAST_MIN             ;Same as last minute?
               JNZ     CK_ALARM
               JMP     TIMER_END               ;If yes, done.
CK_ALARM:      CMP     AX,"1" SHL 8 + "0"      ;Else, is it :01?
               JZ      CK_ROLLOVER             ;If yes, check if 12:01.
               SUB     AX,"0" SHL 8 + "0"      ;Else, adjust to binary.
               OR      AH,AH                   ;Minute zero?
               JZ      ADJUST_TIME
TIMER_LILLY:   JMP     TIMER_END               ;If no, done.

CK_ROLLOVER:   OR      BP,BP                   ;Is it 12:01am?
               JNZ     TIMER_LILLY             ;If no, done.
               MOV     REREAD_FLAG,1           ;Else, reread disk alarms.
               CMP     PROGRAM_STATUS,0        ;Are we popped up?
               JNZ     FIX_DATE                ;If yes, fix date now.
               CMP     NOMIDNIGHT_FLAG,1       ;Else, ignore rollover?
               JZ      TIMER_LILLY             ;If yes, done.
               MOV     POPUP_FLAG,1            ;Else, popup request.
               JMP     TIMER_LILLY
FIX_DATE:      CALL    UPDATE_DATE             ;Fix date.
               JMP     TIMER_LILLY

ADJUST_TIME:   SUB     BP,6                    ; 6:00am
               JS      EXIT_TIMER
               CMP     BP,15                   ; 9:00pm
               JA      EXIT_TIMER
               OR      AL,AL                   ; xx:00
               JZ      CK_MATCH
               CMP     AL,3                    ; xx:30
               JNZ     EXIT_TIMER
               AND     AL,1                    ;Convert to index.

CK_MATCH:      CMP     PROGRAM_STATUS,0        ;Popped up?
               JNZ     EXIT_TIMER              ;If yes, no alarms.
               CBW
               SHL     BP,1                    ;Hour * 2.
               ADD     AX,BP                   ; + half hour index.
               MOV     DI,AX
               CMP     BYTE PTR ALARMS[DI],1   ;Appointment?
               JNZ     EXIT_TIMER              ;If no, done.

               CMP     NOPOPUP_FLAG,1          ;No pop up request by user?
               JZ      CK_BELL_FLAG            ;If yes, check chime.
               MOV     ALARM_FLAG,1            ;Else, flag no cursor change
               MOV     APP_INDEX,APP_LEFT      ; when disk reread to today.
               MOV     DX,DI
               XCHG    DH,DL                   ;Place cursor on appointment.
               MOV     DL,12
               CMP     DH,16
               JB      MOVE_CURSOR
               MOV     APP_INDEX,APP_RIGHT
               SUB     DH,16
               MOV     DL,48
MOVE_CURSOR:   ADD     DH,4                    ;Starts on fourth row.
               MOV     APP_CURSOR,DX

CK_BELL_FLAG:  CMP     NOBELL_FLAG,1           ;No chime request by user?
               JNZ     START_BELL
               MOV     POPUP_FLAG,1            ;If yes, just pop up.
               JMP     SHORT EXIT_TIMER

START_BELL:    MOV     BELL_CNT,14 * 2         ;14 chirps for chime.
               MOV     BELL_FLAG,1
               CALL    SETUP_BELL
DO_BELL:       CALL    FLIP_BELL

EXIT_TIMER:    JMP     TIMER_END

TIMER          ENDP

;-------------------------------------------------------------
; INPUT: SI = 0 not to suppress leading zero; SI = 1 suppress.
STORE_NUMBER:  MOV     BH,10
               DIV     BH
               ADD     AX,"00"
               OR      SI,SI
               JNZ     STORE_IT
               CMP     AL,"0"
               JNZ     STORE_IT
               MOV     AL,SPACE
STORE_IT:      STOSB
               XCHG    AH,AL
               STOSB
               INC     DI
               RET

;------------------------------------------------------------------------------
;Interrupt 10h handling routine.
;------------------------------------------------------------------------------
VIDEO          PROC    NEAR
               INC     CS:FLAG_10H             ;No popup while in a int 10h.
               PUSHF
               CLI
               CALL    DWORD PTR CS:OLD10
               DEC     CS:FLAG_10H
               IRET
VIDEO          ENDP

;------------------------------------------------------------------------------
;Interrupt 13h handling routine.
;------------------------------------------------------------------------------
BDISK          PROC    FAR
               PUSHF
               INC     CS:FLAG_13H             ;No popup while disk activity.
               CLI
               CALL    DWORD PTR CS:OLD13      ;Call BIOS routine
               PUSHF
               DEC     CS:FLAG_13H
               POPF
               STI
               RET     2                       ;Preserve flags.
BDISK          ENDP

;------------------------------------------------------------------------------
;Interrupt 28h handling routine.
;------------------------------------------------------------------------------
BACKPROC       PROC    NEAR
               PUSH    AX
               PUSH    DS
               MOV     AX,CS
               MOV     DS,AX
               CLI
               PUSHF
               CALL    DWORD PTR OLD28
               CMP     REQUEST_FLAG,0          ;Popup requested?
               JZ      BP_EXIT
               CMP     BACK_FLAGS,0            ;Save to popup?
               JNZ     BP_EXIT
               CMP     PROGRAM_STATUS,0        ;Already popped up?
               JNZ     BP_EXIT

               INC     FLAG_28h
               STI
               CLD
               PUSH    BX
               PUSH    CX
               PUSH    DX
               PUSH    SI
               PUSH    DI
               PUSH    ES
               PUSH    BP
               CALL    TSR_STATE               ;Save to enter Dos?
               JC      BP_DONE
               CALL    MAIN
BP_DONE:       POP     BP
               POP     ES
               POP     DI
               POP     SI
               POP     DX
               POP     CX
               POP     BX
               DEC     FLAG_28h
BP_EXIT:       POP     DS
               POP     AX
               IRET
BACKPROC       ENDP

;----------------------------------------------;
TSR_STATE:     PUSH    AX
               CMP     BUSY_FLAGS,0
               JNZ     TSR_BUSY

               MOV     ES,DOS_SEGMENT          ;Check INDOS flag.
               MOV     BX,INDOS_OFFSET
               MOV     AL,ES:[BX]
               MOV     BX,ERRFLAG_OFFSET       ;Check critical error flag.
               MOV     AH,ES:[BX]
               XOR     BX,BX
               CMP     BL,FLAG_28h
               RCL     BL,1
               CMP     BX,AX
               JC      TSR_END

; Checking for hardware interrupts will avoid lost of chars in Async.
               MOV     AX,00001011B
               OUT     20H,AL
               JMP     $ + 2
               IN      AL,20H
               CMP     AH,AL
               JC      TSR_END

TSR_OK:        CLC
               JMP     SHORT TSR_END

TSR_BUSY:      STC
TSR_END:       POP     AX
               RET

;-----------------------------------------------;
; This is the new Critical Error interrupt 24h. ;
;-----------------------------------------------;
ERR_CURSOR     DW      ?                       ; Current cursor position.
ERR_CORNER     DW      ?                       ; Address for error message.
ABORT          DB      0                       ; =1 if user chose to abort.

IOERR:         STI
               PUSH    BX
               PUSH    CX
               PUSH    DX
               PUSH    SI
               PUSH    DI
               PUSH    BP
               PUSH    DS
               PUSH    ES

               MOV     BX,CS
               MOV     DS,BX
               MOV     ES,BX
               CLD
               ADD     AL,"A"
               MOV     DISK_NAME + 24,AL       ;Convert and save drive letter.

               MOV     SI,OFFSET DISK          ;Disk or printer critical error?
               TEST    AH,10000000B
               JZ      SAVE_IT
               MOV     SI,OFFSET PRN

SAVE_IT:       PUSH    SI                      ;Save screen contents we are
               MOV     BH,ACTIVE_PAGE          ; going to write over.
               MOV     AH,3
               INT     10H
               MOV     ERR_CURSOR,DX           ;Save cursor position and
               CALL    HIDE_CURSOR             ; hide it off screen.

               MOV     AX,ERR_ROW              ;Popup error message.
               CALL    CALC_ADDR
               ADD     AX,ERR_COL * 2
               MOV     ERR_CORNER,AX

               MOV     SI,AX
               MOV     DI,OFFSET ERR_SAVE
               MOV     CX,ERR_HEIGHT + 1
               MOV     BP,ERR_WIDTH + 2
               CALL    DO_SAVE

               POP     SI
               MOV     DI,ERR_CORNER
               CALL    POP_WINDOW
               MOV     DI,ERR_CORNER
               MOV     AX,CRT_WIDTH
               ADD     DI,AX
               ADD     DI,AX
               ADD     DI,AX
               MOV     SI,OFFSET ERR_BOT
               CALL    POP_WINDOW
               CALL    BEEP

WAIT_KEY:      XOR     AH,AH                   ;Get a user response.
               INT     16H
               MOV     AL,1
               CMP     AH,13H                  ;Was it scan code for "R"?
               JZ      ERR_END
               CMP     AH,1EH                  ;Was it "A"?
               JNZ     WAIT_KEY                ;If no, wait until valid
               MOV     ABORT,1                 ; response.
               MOV     AL,3                    ;Fail.
               CMP     DOS_VERSION,300H        ;DOS 2 can't handle fail request.
               JAE     ERR_END
               XOR     AL,AL

ERR_END:       PUSH    AX
               MOV     DI,ERR_CORNER           ;Restore screen.
               MOV     SI,OFFSET ERR_SAVE
               MOV     CX,ERR_HEIGHT + 1
               MOV     BP,ERR_WIDTH + 2
               CALL    DO_RESTORE
               MOV     DX,ERR_CURSOR
               CALL    SET_CURSOR              ;Restore cursor position.
               POP     AX

               POP     ES                      ;Restore registers.
               POP     DS
               POP     BP
               POP     DI
               POP     SI
               POP     DX
               POP     CX
               POP     BX
IOEXIT:        IRET

;------------------------------------------------------------------------------
;MAIN is the routine called to pop up the window.
;------------------------------------------------------------------------------
MAIN           PROC    NEAR
               MOV     PROGRAM_STATUS,1        ;Flag in process of popping up.
               MOV     REQUEST_FLAG,0          ;Reset request counter.
               MOV     SS_REGISTER,SS          ;Setup own stack.
               MOV     SP_REGISTER,SP
               MOV     AX,CS
               MOV     SS,AX
               MOV     SP,OFFSET OUR_STACK
               STI
               MOV     ES,AX

               CALL    GET_BIOS_DATA
               MOV     AL,CRT_MODE             ;Is it video mode BW80, CO80
               CMP     AL,3                    ; or MONO?
               JZ      MAIN1
               CMP     AL,2
               JZ      MAIN1
               CMP     AL,7
               JZ      MAIN1
QUICK_EXIT:    CMP     POPUP_FLAG,1            ;If no, can't popup; beep to tell
               JZ      EXIT                    ; user only if was hotkey popup.
               CALL    BEEP

;----------------------------------------------;
; MAIN exit.                                   ;
;----------------------------------------------;
EXIT:          CLI
               MOV     SS,SS_REGISTER          ;Restore stack.
               MOV     SP,SP_REGISTER
               MOV     PROGRAM_STATUS,0
               RET

;----------------------------------------------;
MAIN1:         CMP     CRT_COLS,80             ;At least 80 columns?
               JB      QUICK_EXIT

GET_COLORS:    CALL    GET_CUR_ADDR            ;Save cursor address.
               CMP     MONO_FLAG,1             ;Forced Black and white?
               JZ      DO_MONO
               MOV     SI,OFFSET COLOR_ATTR    ;Select color or mono
               CMP     CRT_MODE,3              ; attributes.
               JZ      GOT_ATTR
DO_MONO:       MOV     SI,OFFSET MONO_ATTR
GOT_ATTR:      MOV     DI,OFFSET COLOR
               MOV     CX,SIZE COLOR_ATTRIBS
               REP     MOVSB

MAIN5:         MOV     AH,51H
               CALL    INT21_PSP               ;Get PSP.
               MOV     OLDPSP,BX
               PUSH    CS
               POP     BX
               MOV     AH,50H                  ;Set PSP.
               CALL    INT21_PSP

               MOV     AH,2FH
               INT     21H
               MOV     OLD_DTA[0],BX           ;Get DTA.
               MOV     OLD_DTA[2],ES
               MOV     DX,OFFSET DTA
               MOV     AH,1AH                  ;Set DTA.
               INT     21H

               MOV     AX,3300H                ;Get break.
               INT     21H
               MOV     BREAK,DL
               XOR     DL,DL                   ;Break off.
               MOV     AX,3301H
               INT     21H

MAIN6:         PUSH    CS
               POP     ES
               CALL    IOSET                   ;Set up critical error handler.
               CALL    UPDATE_DATE
               CALL    SAVE_SCREEN
               MOV     EXIT_FLAG,0
               CMP     POPUP_FLAG,1            ;Alarm or rollover?
               JNZ     READY
               MOV     POPUP_FLAG,0
               MOV     STATE,OFFSET APPOINTMENT  ;If yes, popup in appointments
               CALL    CTODAY                    ; on today.
READY:         MOV     PROGRAM_STATUS,2          ;Flag so clock displayed.

;------------------------------;
;    M A I N    L O O P        ;
;------------------------------;
NEXT_STATE:    CALL    HIDE_CURSOR
               CALL    STATE
               CMP     EXIT_FLAG,1             ;If Esc Calendar or Hotkey, exit.
               JNZ     NEXT_STATE

ESCAPE:        MOV     PROGRAM_STATUS,1        ;Disable clock display.
               CALL    RESTORE_SCREEN

               MOV     BX,OLDPSP               ;Restore PSP.
               MOV     AH,50H
               CALL    INT21_PSP

               PUSH    DS                      ;Restore DTA.
               LDS     DX,DWORD PTR OLD_DTA
               MOV     AH,1AH
               INT     21H
               POP     DS

               MOV     DL,BREAK                ;Restore BREAK.
               MOV     AX,3301H
               INT     21H

               CALL    IORESET                 ;Restore critical handler.

               MOV     BH,ACTIVE_PAGE          ;Restore cursor.
               MOV     DX,CURSOR_POS
               MOV     AH,2
               INT     10H
               MOV     DX,ADDR_6845            ;Recover CRTC base address
               MOV     CX,CURSOR_ADDR
               MOV     AL,14
               OUT     DX,AL
               INC     DX
               MOV     AL,CH
               OUT     DX,AL
               DEC     DX
               MOV     AL,15
               OUT     DX,AL
               INC     DX
               MOV     AL,CL
               OUT     DX,AL
               JMP     EXIT
MAIN           ENDP

;----------------------------------------------;
INT21_PSP:     CMP     DOS_VERSION,30AH        ;PSP call DOS 3 or above.
               JB      INT21_PSP2
               INT     21H
               RET

INT21_PSP2:    PUSH    DS                      ;Else, fake via error.
               MOV     DI,ERRFLAG_OFFSET
               MOV     DS,DOS_SEGMENT
               INC     BYTE PTR [DI]
               INT     21H
               DEC     BYTE PTR [DI]
               POP     DS
               RET

;------------------------------------------;
; OUTPUT: ZF=1 if aborted.  CY=1 if failed ;
;------------------------------------------;
ZR             EQU     1000000B                ;Bit for zero flag in register.

INT21_IO:      INT     21H
               PUSH    AX                      
               LAHF                            ;Save flag's results of DOS call.
               AND     AH,NOT ZR               ;Assume zero flag zero.
               CMP     ABORT,1                 ;Was there an abort from
               JNZ     INT21_IO_END            ; critical error?
               OR      AH,ZR                   ;If yes, turn zero bit on.
               MOV     ABORT,0                 ;Reset abort flag.
INT21_IO_END:  SAHF                            ;Return results in flags reg.
               POP     AX
               RET

;----------------------------------------------;

UPDATE_DATE:   MOV     AH,2AH                  ;Get the date from DOS.
               INT     21H
               MOV     YEAR_TODAY,CX
               MOV     DAY_MON_TODAY,DX
               RET

;**********************************************;
;       F U N C T I O N   C A L L S
;**********************************************;
MONTHS         DB      "JanFebMarAprMayJunJulAugSepOctNovDec"
NUMDAYS        DB       31,28,31,30,31,30,31,31,30,31,30,31

YEAR_CAL       DW      ?                       ;Year displayed.
DAY_MON_CAL    LABEL   WORD
DAY_CAL        DB      ?                       ;Day cursor is on.
MONTH_CAL      DB      ?                       ;Month displayed.

YEAR_TODAY     DW      ?                       ;Current year.
DAY_MON_TODAY  LABEL   WORD
DAY_TODAY      DB      ?                       ;Current day.
MONTH_TODAY    DB      ?                       ;Current month.

YEAR_CUR       DW      ?                       ;Last year displayed.
DAY_MON_CUR    LABEL   WORD
DAY_CUR        DB      ?                       ;Last day displayed.
MONTH_CUR      DB      -1                      ;Last month displayed.

WEEKDAY        DB      ?                       ;Day of week for 1st.
LAST_DAY       DB      ?                       ;Last day of month.
DAY_COUNTER    DB      ?                       ;Day display counter.
BLOCK_COUNTERS LABEL   WORD
BLOCK_COUNTER  DB      ?,?                     ;Block display counters.
BLOCK_ROW      DW      0                       ;Block row index.

CTABLE         DB  3CH,   3DH,    3EH,       3FH
               DB  4BH,   4DH,    48H,       50H,       47H,       4FH
               DB  49H,   51H,    84H,       76H,       77H,       75H
               DB  73H,   74H
CTABLE_LEN     EQU     $ - CTABLE
               DW  SAVE,  CTODAY, PPURGE,    PRINT
               DW  CLEFT, CRIGHT, CUP,       CDOWN,     CHOME,     CEND
               DW  CPGUP, CPGDN,  CCTRLPGUP, CCTRLPGDN, CCTRLHOME, CCTRLEND
               DW  CHOME, CEND

CALENDAR:      CALL    CK_DATE                 ;See if date changed.

               MOV     SI,OFFSET CAL_MENU      ;Pop up calendar screen.
               CALL    POP_MENU

               MOV     DAY_COUNTER,0           ;Counters for day and
               MOV     BLOCK_COUNTERS,0        ; cursor display.
               MOV     SI,OFFSET CAL_MIDDLE
               CALL    POP_WINDOW
               MOV     CX,5
NEXT_CALROW:   PUSH    CX
               MOV     SI,OFFSET CAL_DUP
               CALL    POP_WINDOW
               POP     CX
               LOOP    NEXT_CALROW
               INC     SI
               CALL    POP_WINDOW

               CALL    GETKEY                  ;Get a keystroke.
               JC      CALENDAR_DONE           ;Exit if Esc or hotkey.
               CMP     AL,ENTER_SCAN           ;If Enter, then go to
               JZ      GOTO_APP                ; appointment screen.
               CMP     AL,31H                  ;If "N" scan code.
               JZ      GOTO_NOTE
               CMP     AL,F6_SCAN              ; or F6, go directly
               JNZ     CAL_DISPATCH            ; to Notepad.
GOTO_NOTE:     MOV     STATE,OFFSET APPOINTMENT
               CALL    CK_DATE
               MOV     APP_CURSOR,A_NOTE_CURSOR
               MOV     APP_INDEX,APP_NOTE
               JMP     SHORT CALENDAR_END

GOTO_APP:      MOV     STATE,OFFSET APPOINTMENT
               JMP     SHORT CALENDAR_END

CAL_DISPATCH:  MOV     SI,YEAR_CAL             ;Pass variables in registers
               MOV     DL,DAY_CAL              ; to calendar keyboard
               MOV     DH,MONTH_CAL            ; functions.
               MOV     BL,WEEKDAY
               MOV     BH,LAST_DAY
               MOV     DI,OFFSET CTABLE
               MOV     CX,CTABLE_LEN
               CALL    DISPATCH
               CMP     EXIT_FLAG,1
               JZ      CALENDAR_END
               JMP     CALENDAR

CALENDAR_DONE: MOV     EXIT_FLAG,1             ;Tell MAIN to exit.
CALENDAR_END:  RET

;----------------------------------------------;
; INPUT: SI=YEAR_CAL; DL=DAY_CAL; DH=MONTH_CAL; BL=WEEKDAY; BH=LAST_DAY

CTODAY:        MOV     SI,YEAR_TODAY           ;Today's date.
               MOV     DX,DAY_MON_TODAY
               JMP     SHORT CFUNC_UPDATE

CPGDN:         INC     DH                      ;Next month.
               CMP     DH,12                   ;Past Dec?
               JBE     CFUNC_UPDATE
               MOV     DH,1                    ;Jan.
               INC     SI                      ;Next year.
               JMP     SHORT CK_YEAR

CPGUP:         DEC     DH                      ;Previous month.
               JNZ     CFUNC_UPDATE            ;Before Jan?
               MOV     DH,12                   ;Dec.
               DEC     SI                      ;Previous year.
               JMP     SHORT CK_YEAR

CCTRLPGDN:     INC     SI                      ;Next year.
               JMP     SHORT CK_YEAR

CCTRLPGUP:     DEC     SI                      ;Previous year.

CK_YEAR:       CMP     SI,10000                ;Stay within Gregorian
               JAE     CFUNC_END               ; calendar.
               CMP     SI,1582
               JA      CFUNC_UPDATE
               JMP     SHORT CFUNC_END

CLEFT:         DEC     DL                      ;Previous day.
               JNZ     CFUNC_UPDATE
               JMP     SHORT CFUNC_END

CRIGHT:        INC     DL                      ;Next day.
               CMP     DL,BH
               JBE     CFUNC_UPDATE
               JMP     SHORT CFUNC_END

CUP:           MOV     AL,7                    
               SUB     AL,BL                   ;Adjust for weekday.
               CMP     DL,AL                   ;If first week, ignore
               JBE     CFUNC_END
               SUB     DL,7                    ;Previous week.
               JA      CFUNC_UPDATE
               MOV     DL,1                    ;First day, if out of bounds.
               JMP     SHORT CFUNC_UPDATE

CDOWN:         MOV     CL,7
               MOV     AL,CL
               SUB     AL,BL
FIND_BOTTOM:   ADD     AL,CL                   ;Find last week row.
               CMP     AL,BH
               JBE     FIND_BOTTOM
               SUB     AL,CL                   
               CMP     DL,AL
               JA      CFUNC_END               ;Ignore if on last row.
               ADD     DL,7                    ;Next week.
               CMP     DL,BH
               JBE     CFUNC_UPDATE
               MOV     DL,BH                   ;Last day if out of bounds.
               JMP     SHORT CFUNC_UPDATE

CCTRLHOME:     MOV     DL,1                    ;First day.
               JMP     SHORT CFUNC_UPDATE

CCTRLEND:      MOV     DL,BH                   ;Last day.

CFUNC_UPDATE:  MOV     YEAR_CAL,SI             ;Store new day/year.
               MOV     DAY_MON_CAL,DX
CFUNC_END:     RET

CHOME:         MOV     AL,DL                   ;Adjust for weekday.
               ADD     AL,BL
               DEC     AL
               CBW
               MOV     CL,7
               DIV     CL
               MOV     DL,1                    ;First day if at start of row.
               OR      AH,AH
               JZ      CFUNC_UPDATE
               MUL     CL
               SUB     AL,BL
               INC     AL
               JBE     CFUNC_UPDATE
               MOV     DL,AL                   ;Else go to start of row.
               JMP     CFUNC_UPDATE

CEND:          MOV     AL,DL                   ;Adjust for weekday.
               ADD     AL,BL
               CBW
               MOV     CL,7
               DIV     CL
               MOV     DL,BH                   ;Last day if at end of row.
               OR      AH,AH
               JZ      CFUNC_UPDATE
               MUL     CL
               ADD     AL,CL
               SUB     AL,BL
               MOV     DL,AL                   ;Else, end of row.
               CMP     DL,BH
               JBE     CFUNC_UPDATE
               MOV     DL,BH
               JMP     CFUNC_UPDATE

;----------------------------------------------;
CK_DATE:       MOV     AX,DAY_MON_CAL          ;Retrieve calendar day/year.
               MOV     BX,YEAR_CAL
               CMP     REREAD_FLAG,1           ;Forced reread?
               JZ      GET_CAL
               CMP     AH,MONTH_CUR            ;Has month changed?
               JNZ     GET_CAL
               CMP     BX,YEAR_CUR             ;Has year changed?
               JNZ     GET_CAL
               CMP     STATE,OFFSET APPOINTMENT
               JNZ     CK_DATE_END             ;If appointment screen,
               CMP     AL,DAY_CUR              ; has day changed?
               JZ      CK_DATE_END

GET_CAL:       MOV     REREAD_FLAG,0           ;Reset flag.
               PUSH    AX
               PUSH    BX
               CALL    PERMANENT               ;See if changes have been made
               POP     BX                      ; in appointment screen.
               POP     AX
               MOV     MONTH_CUR,AH            ;Store current day/year.
               MOV     YEAR_CUR,BX
               CALL    FIRSTDAY                ;Find first day of month.
               CALL    READ_DATA               ;Read data off of disk.
               CMP     ALARM_FLAG,1            ;Was this alarm?
               JZ      RESET_ALARM             ;If yes, leave cursor on app.

               MOV     A_LEFT.CURSOR,A_LEFT_CURSOR
               MOV     A_RIGHT.CURSOR,A_RIGHT_CURSOR
               MOV     A_NOTE.CURSOR,A_NOTE_CURSOR
               MOV     APP_INDEX,APP_LEFT
               MOV     APP_CURSOR,A_LEFT_CURSOR

RESET_ALARM:   MOV     ALARM_FLAG,0
CK_DATE_END:   RET

;----------------------------------------------;
FIRSTDAY:      MOV     SI,YEAR_CAL             ;Year.
               MOV     NUMDAYS[1],28           ;Assume this isn't a leap year.
               TEST    SI,3                    ;Year divisible by 4?
               JNZ     GETDAY                  ;Assumed right.
               MOV     NUMDAYS[1],29           ;Leap year = 29 days.
GETDAY:        MOV     BX,6                    ;Saturday,(weekday of 1/1/1583)
               MOV     AX,SI                   ;Calendar year.
               SUB     AX,1583                 ;No. years since our base year.
               ADD     BX,AX                   ;Calendar advances 1 weekday per
                                               ;  year unless leap year.
               ADD     AX,2                    ;Adj diff, so even #, if year
               MOV     CX,4                    ;  was after a leap year.
               CWD
               DIV     CX                      ;No. leap years before this.
               ADD     BX,AX                   ;Add 1 day, for each leap yr.
               CMP     SI,1700                 ;Things work normally 'til 1700.
               JL      CNVRT                   ;Not a leap year.
               MOV     AX,SI
               SUB     AX,1600                 ;Get no. years since 1600.
               MOV     CL,100
               CWD                             ;Convert to number of centuries.
               DIV     CX
               OR      DX,DX                   ;If remain=0 this is centennial.
               JNZ     DEDUCT
               TEST    AX,3                    ;If century divisible by
               JZ      DECAX                   ; 400, it is also leap year.
               MOV     NUMDAYS[1],28           ;Other centennial years have 28.
DECAX:         DEC     AX                      ;If centennial, sub 1 from cent.
DEDUCT:        SUB     BX,AX                   ;Subtract 1 weekday per century.
               CWD
               MOV     CL,4
               DIV     CX                      ;Calculate centuries mod 400.
               ADD     BX,AX                   ;Add back 1 day per 400 years.
CNVRT:         MOV     SI,OFFSET NUMDAYS       ;Add in days per month, this yr.
               XOR     AH,AH
               MOV     CL,MONTH_CAL            ;Calendar month.
               DEC     CL
               MOV     DI,CX
               MOV     DL,NUMDAYS[DI]
               MOV     LAST_DAY,DL
               JCXZ    FINAL                   ;If Jan, ready for final calc.
ADDMNTH:       LODSB
               ADD     BX,AX                   ;Add to days count.
               LOOP    ADDMNTH                 ;Cont. until prior months added.
FINAL:         MOV     AX,BX                   ;Days advanced since 1/1/1583.
               CWD
               MOV     CL,7                    ;Find no. full weeks since 1583.
               DIV     CX                      ;Weekday of 1st of cal's month.
               MOV     WEEKDAY,DL              ;Result is first day of week.

               MOV     AL,DAY_CAL              ;Fix last day.
FIX_CURSOR:    CMP     AL,LAST_DAY
               JBE     STORE_CURSOR2
               DEC     AL
               JMP     FIX_CURSOR
STORE_CURSOR2: MOV     DAY_CAL,AL              ;Store fix.
               MOV     DAY_CUR,AL
               CALL    STORE_DATE
               RET

;----------------------------------------------;
STORE_DATE:    MOV     BP,3                    ;Three places need dates.
               MOV     DI,OFFSET CAL_DATE      ;Calendar screen.
NEXT_DATE:     PUSH    DI
               MOV     AL,MONTH_CAL            ;Month
               DEC     AL
               MOV     CX,3
               MUL     CL
               ADD     AX,OFFSET MONTHS
               MOV     SI,AX
               REP     MOVSB
               INC     DI
               CMP     BP,3
               JZ      STORE_YEAR

               MOV     AL,DAY_CAL              ;Number of day.
               CBW
               CALL    STORE_NUM
               MOV     AL,SPACE
               STOSB
STORE_YEAR:    MOV     AX,YEAR_CAL             ;Year.
               CALL    STORE_NUM
               MOV     AL,SPACE
               STOSB

               POP     DI
               CMP     BP,3
               JZ      LOOP_DATE
               ADD     DI,12
               MOV     AL,WEEKDAY
               ADD     AL,DAY_CAL
               DEC     AL
               CBW
               MOV     CL,7
               DIV     CL
               MOV     AL,AH
               MOV     CL,11
               MUL     CL
               ADD     AX,OFFSET DAYS          ;Name of weekday; ie. Mon etc.
               MOV     SI,AX
               MOV     CX,3
               REP     MOVSB

LOOP_DATE:     DEC     BP
               JZ      STORE_DATE_END
               MOV     DI,OFFSET APP_DATE      ;Appointment screen.
               CMP     BP,2
               JZ      NEXT_DATE
               MOV     DI,OFFSET PUR_DATE      ;Purge window.
               JMP     NEXT_DATE
STORE_DATE_END:RET

;----------------------------------------------;
STORE_NUM:     MOV     BX,10
               XOR     CX,CX                   ;Zero in counter.
NEXT_COUNT:    CWD                             ;Zero in high half.
               DIV     BX                      ;Divide by ten.
               ADD     DL,"0"                  ;Convert to ASCII.
               PUSH    DX                      ;Save results.
               INC     CX                      ;Also increment count.
               OR      AX,AX                   ;Are we done?
               JNZ     NEXT_COUNT              ;Continue until zero.
NEXT_NUMBER:   POP     AX                      ;Retrieve numbers.
               STOSB
               LOOP    NEXT_NUMBER
               RET

;----------------------------------------------;
SCAN_DAY:      CALL    SCAN_APP                ;Find appointments.
               MOV     AL,DAY_CAL              ;Store small box char.
               CALL    STORE_BLOCKS            ; if appointment found.
               MOV     AX,YEAR_CAL
               CMP     AX,YEAR_TODAY
               JNZ     SCAN_DAY_END
               MOV     AX,DAY_MON_CAL
               CMP     AX,DAY_MON_TODAY
               JNZ     SCAN_DAY_END
               CALL    STORE_ALARMS            ;Store alarm if today.
SCAN_DAY_END:  RET

;----------------------------------------------;
SCAN_APP:      PUSH    BP
               MOV     SI,OFFSET WORK_SPACE    ;Spaces in work space.
               MOV     AL,SPACE
               MOV     BP,OFFSET APP_LEFT_START   ;Scan appointments for
               MOV     CX,2                       ; non-space chars.
NEXT_COL:      PUSH    CX
               MOV     DX,APP_HEIGHT
NEXT_DAY:      MOV     DI,BP
               MOV     CX,APP_LEN
               XOR     AH,AH                   ;Mark with zero if no app.
               REP     SCASB
               JZ      TEMP_APP
               INC     AH                      ;Else, mark with one for alarm.
TEMP_APP:      MOV     [SI],AH
               INC     SI
               ADD     BP,APP_WIDTH
               DEC     DX
               JNZ     NEXT_DAY
               MOV     BP,OFFSET APP_RIGHT_START
               POP     CX
               LOOP    NEXT_COL
               POP     BP
               RET

;----------------------------------------------;
; INPUT: AL = Calendar day.
STORE_BLOCKS:  DEC     AL
               CBW
               MOV     CL,3
               SHL     AX,CL
               MOV     DI,AX
               ADD     DI,OFFSET APPOINTMENT_BLOCKS
               MOV     SI,OFFSET WORK_SPACE
               MOV     CX,2
NEXT_SET:      PUSH    CX
               MOV     DX,APP_HEIGHT / 2
NEXT_BLOCK2:   LODSW
               OR      AX,AX
               MOV     AL,SPACE                ;Store a space if no appointment.
               JZ      STORE_BLOCK
               MOV     AL,"þ"                  ;Else, store little block char.
STORE_BLOCK:   STOSB
               DEC     DX
               JNZ     NEXT_BLOCK2
               ADD     DI,(BLOCK_COUNT / 2) - 8
               POP     CX
               LOOP    NEXT_SET
               RET

;----------------------------------------------;
ALARMS         DB      APP_HEIGHT * 2 DUP (0)
WORK_SPACE     DB      APP_HEIGHT * 2 DUP (?)

STORE_ALARMS:  MOV     SI,OFFSET WORK_SPACE    ;Copy results of work space
               MOV     DI,OFFSET ALARMS        ; into alarm array.
               MOV     CX,SIZE ALARMS / 2
               REP     MOVSW
               RET

;----------------------------------------------;
TODAY_FLAG     EQU     001B                    ;Data applies to today.
CAL_FLAG       EQU     010B                    ;Data applies to calendar.
APP_FLAG       EQU     100B                    ;Data applies to appointment.

READ_DATA:     MOV     DI,OFFSET APPOINTMENT_BLOCKS
               MOV     CX,BLOCK_COUNT / 2
               MOV     AX,SPACE SHL 8 + SPACE
               REP     STOSW                   ;Spaces in Appointment blocks.

               MOV     BP,APP_HEIGHT
               MOV     BX,APP_LEN
               MOV     DI,OFFSET APP_LEFT_START
NEXT_BLANK:    MOV     CX,BX
               REP     STOSB
               ADD     DI,APP_COL_SPACE
               MOV     CX,BX
               REP     STOSB                   ;Spaces in Appointment book.
               ADD     DI,APP_ROW_SPACE
               DEC     BP
               JNZ     NEXT_BLANK

               MOV     BP,3
               MOV     BX,NOTE_LEN
               MOV     DI,OFFSET NOTEPAD
NEXT_PAD:      MOV     CX,BX
               REP     STOSB                   ;Spaces in Notepad.
               ADD     DI,APP_NOTE_SPACE
               DEC     BP
               JNZ     NEXT_PAD

               MOV     DI,OFFSET ALARMS
               MOV     CX,SIZE ALARMS / 2
               REP     STOSW                   ;Spaces in Alarms.

               CALL    OPEN_TSR                ;Open data file.
               JBE     READ_DONE               ;If none, then done.

NEXT_READ:     CALL    READ_DATE               ;Read the date.
               JBE     READ_FAILED
               OR      AX,AX
               JZ      READ_END

               XOR     BP,BP                   ;Initialize date flag.
               CMP     CX,YEAR_TODAY           ;Data same day/month/year
               JNZ     CK_YEAR_CAL             ; as today?
               CMP     DX,DAY_MON_TODAY
               JNZ     CK_YEAR_CAL
               OR      BP,TODAY_FLAG           ;If yes, today.

CK_YEAR_CAL:   CMP     CX,YEAR_CAL             ;Same as day/month/year
               JNZ     CK_READ                 ; as calendar?
               CMP     DX,DAY_MON_CAL
               JNZ     CK_MON_CAL
               OR      BP,CAL_FLAG OR APP_FLAG ;If yes, then applies to
               JMP     SHORT DO_READ           ; calendar and appointment.

CK_MON_CAL:    CMP     DH,MONTH_CAL            ;Same as month/year only.
               JNZ     CK_READ
               OR      BP,CAL_FLAG             ;If yes, applies to calendar.

CK_READ:       OR      BP,BP                   ;Does data apply to anything?
               JNZ     DO_READ
               CALL    NEXT_RECORD             ;If no, bump file pointer
               JBE     READ_FAILED             ; to next record.
               JMP     NEXT_READ

DO_READ:       CALL    READ_APP                ;If applies, read in the data.
               JBE     READ_FAILED

               TEST    BP,APP_FLAG             ;Apply to appointments?
               JZ      CK_TODAY_CAL
               CALL    MOVE_APP                ;If yes, move data into app.

CK_TODAY_CAL:  CALL    SCAN_DTA                ;Scan the data for appointments.
               TEST    BP,TODAY_FLAG           ;Does it apply to today.
               JZ      CK_MON_CAL2
               CALL    STORE_ALARMS            ;If yes, store alarms.

CK_MON_CAL2:   TEST    BP,CAL_FLAG             ;Apply to calendar?
               JZ      NEXT_READ
               MOV     AL,BYTE PTR DTA.DATE_BINARY + 2
               CALL    STORE_BLOCKS            ;If yes, store small block chars.
               JMP     NEXT_READ

READ_END:      MOV     AH,3EH                  ;Close data file.
               CALL    INT21_IO
READ_DONE:     RET

READ_FAIL:     CALL    FAILED                  ;If failed, display message.
               RET
READ_FAILED:   CALL    FAILED_CLOSE
               RET

;----------------------------------------------;
SCAN_DTA:      PUSH    BP
               MOV     SI,OFFSET WORK_SPACE
               MOV     AL,SPACE
               MOV     BP,OFFSET DTA.APPOINT_TEXT
               MOV     CX,2
NEXT_DTA:      PUSH    CX
               MOV     DX,APP_HEIGHT           ;Scan data read off disk
NEXT_DTA2:     MOV     DI,BP                   ; and store a 1 in work space
               MOV     CX,APP_LEN              ; if non-space char.
               XOR     AH,AH
               REP     SCASB
               JZ      TEMP_APP2
               INC     AH
TEMP_APP2:     MOV     [SI],AH
               INC     SI
               ADD     BP,APP_LEN * 2
               DEC     DX
               JNZ     NEXT_DTA2
               MOV     BP,OFFSET DTA.APPOINT_TEXT + APP_LEN
               POP     CX
               LOOP    NEXT_DTA
               POP     BP
               RET

;----------------------------------------------;
MOVE_APP:      MOV     SI,OFFSET DTA.APPOINT_TEXT
               MOV     DI,OFFSET APP_LEFT_START
               MOV     DX,APP_HEIGHT
NEXT_MOVE:     MOV     CX,APP_LEN
               REP     MOVSB                   ;Move DTA appointments into
               ADD     DI,APP_COL_SPACE        ; screen app. storage space.
               MOV     CX,APP_LEN
               REP     MOVSB
               ADD     DI,APP_ROW_SPACE
               DEC     DX
               JNZ     NEXT_MOVE

               MOV     SI,OFFSET DTA.NOTEPAD_TEXT
               MOV     DI,OFFSET NOTEPAD       ;Move DTA Notepad into
               MOV     CX,NOTEPAD_LEN          ; screen Notepad space.
               REP     MOVSB
               RET

;***************************************************************************;
APP_BOUNDS     STRUC                           ;Cursor bounds in 3 sections.
COL_HOME       DB      ?
COL_END        DB      ?
ROW_HOME       DB      ?
ROW_END        DB      ?
CURSOR         DW      ?
APP_BOUNDS     ENDS

APP_LEFT       EQU     0
APP_RIGHT      EQU     SIZE APP_BOUNDS
APP_NOTE       EQU     APP_RIGHT + SIZE APP_BOUNDS
APP_INDEX      DW      APP_LEFT                ;Section index.

A_LEFT_CURSOR  EQU     4 SHL 8 + 12            ;Home cursor positions.
A_RIGHT_CURSOR EQU     4 SHL 8 + 48
N_ROW          EQU     4 + APP_HEIGHT + 1
A_NOTE_CURSOR  EQU     N_ROW SHL 8 + 4

BOUNDS         LABEL   BYTE
A_LEFT  APP_BOUNDS < 12, 12 + APP_LEN, 4, 4 + APP_HEIGHT - 1, A_LEFT_CURSOR >
A_RIGHT APP_BOUNDS < 48, 48 + APP_LEN, 4, 4 + APP_HEIGHT - 1, A_RIGHT_CURSOR >
A_NOTE  APP_BOUNDS <4,4+NOTE_LEN,N_ROW,4+APP_HEIGHT+NOTE_HEIGHT,A_NOTE_CURSOR >

APP_CURSOR     LABEL   WORD
APP_CUR_COL    DB      12                      ;Appointment cursor position.
APP_CUR_ROW    DB       4

REPAINT_FLAG   DB      1                       ; 1 = Repaint the screen.


ATABLE         DB  3CH,        3DH,    3EH,    3FH,       40H,       59H
               DB  63H,        4BH,    4DH,    48H,       50H,       47H
               DB  4FH,        49H,    51H,    84H,       76H,       77H
               DB  75H,        73H,    74H,    0FH,       53H
ATABLE_LEN     EQU     $ - ATABLE
               DW  SAVE,       ATODAY, PPURGE, PRINT,     CLEAR,     SHIFT_CLEAR
               DW  CTRL_CLEAR, ALEFT,  ARIGHT, AUP,       ADOWN,     AHOME
               DW  AEND,       APGUP,  APGDN,  ACTRLPGUP, ACTRLPGDN, ACTRLHOME
               DW  ACTRLEND,   AHOME,  AEND,   SHIFT_TAB, ADEL

APPOINTMENT:   CALL    CK_DATE                 ;Check if date changed.
               CMP     REPAINT_FLAG,1
               JNZ     NEXT_APPOINT

               MOV     SI,OFFSET APP_MENU      ;Display appointment screen.
               CALL    POP_MENU
               MOV     REPAINT_FLAG,0

NEXT_APPOINT:  MOV     DX,APP_CURSOR
               CALL    SET_CURSOR
               CALL    GETKEY                  ;Get a keystroke.
               CMP     EXIT_FLAG,1             ;Exit if hotkey.
               JZ      APPOINT_END
               CMP     AL,ESC_SCAN             ;Back to calendar if Esc.
               JNZ     APP_DISPATCH
               MOV     STATE,OFFSET CALENDAR
               JMP     SHORT APPOINT_END
APP_DISPATCH:  CALL    CALC_APP_ADDR           ;Get cursor bounds.
               MOV     BP,APP_INDEX
               MOV     BX,WORD PTR BOUNDS[BP]
               OR      AH,AH                   ;Extended scan code?
               JZ      GOT_DISPATCH
               CALL    CK_ENTRY
               JMP     NEXT_APPOINT

GOT_DISPATCH:  MOV     DI,OFFSET ATABLE
               MOV     CX,ATABLE_LEN
               CALL    DISPATCH
               CMP     EXIT_FLAG,1
               JNZ     APPOINTMENT
APPOINT_END:   MOV     REPAINT_FLAG,1          ;Flag to repaint screen.
               CALL    PERMANENT               ;Check for changes.
               CALL    SCAN_DAY                ;Scan appointments.
               RET

;----------------------------------------------;
; INPUT:  DX = Cursor address; OUTPUT: SI -> Start of row.
CALC_APP_ADDR: PUSH    AX
               MOV     CX,DX
               SUB     CH,4                    ;Appointments start on line 4.
               MOV     AX,APP_WIDTH
               MUL     CH
               MOV     SI,AX
               XOR     CH,CH
               ADD     SI,CX
               ADD     SI,OFFSET APP_START
               POP     AX
               RET

;----------------------------------------------;
; INPUT: SI -> appointments; DX=Cursor; BP=APP_INDEX; BH=COL_END; BL=COL_HOME

CK_ENTRY:      XCHG    AH,AL
               CMP     AL,TAB                  ;Tab?
               JNZ     CK_BACKSPACE
SHIFT_TAB:     MOV     CX,SIZE APP_BOUNDS
               MOV     BX,BP                   ;Save index.
               MOV     AH,2
               INT     16H                     ;Get shift status.
               TEST    AL,11B                  ;Left or Right Shift?
               JZ      DO_TAB
               NEG     CX
DO_TAB:        MOV     AX,APP_NOTE
               ADD     BP,CX
               JNS     CK_HIGH
               MOV     BP,AX
CK_HIGH:       CMP     BP,AX
               JBE     NEW_FIELD
               XOR     BP,BP
NEW_FIELD:     MOV     APP_INDEX,BP            ;New section.
               MOV     BOUNDS.CURSOR[BX],DX    ;Save current cursor position.
               MOV     DX,BOUNDS.CURSOR[BP]    ;Get new cursor position.
               JMP     SHORT ENTRY_UPDATE

CK_BACKSPACE:  CMP     AL,8                    ;Backspace?
               JNZ     CK_PAD_CR
               CMP     DL,BL
               JZ      ENTRY_END
               DEC     SI                      ;Move back one space.
               DEC     DL
               MOV     APP_CURSOR,DX           ;Save cursor position.
               CALL    ADEL                    ;Delete that character.
               JMP     SHORT ENTRY_END

CK_PAD_CR:     CMP     AL,CR                   ;Carriage return?
               JNZ     APP_ASCII
               MOV     DL,BL
               CMP     DH,BOUNDS.ROW_END[BP]   ;Move to start of line.
               JZ      ENTRY_UPDATE
               INC     DH                      ;Next row if not at bottom.
               JMP     SHORT ENTRY_UPDATE

APP_ASCII:     CMP     AL,SPACE                ;Text character?
               JB      ENTRY_END
               INC     DL                      ;Next column.
               SUB     BH,DL
               JC      ENTRY_END               ;If last column, ignore.
               JZ      STORE_APP               ;If next to last column, store.
               MOV     CL,BH
               XOR     CH,CH                   
               ADD     SI,CX
               MOV     DI,SI
               DEC     SI
               STD
               REP     MOVSB                   ;Else, insert mode so shove
               CLD                             ; everything in front up one.
               INC     SI
STORE_APP:     MOV     [SI],AL                 ;Store character.

ENTRY_CHANGE:  MOV     MODIFY_FLAG,1           ;Flag that change made.
ENTRY_UPDATE:  MOV     APP_CURSOR,DX           ;Store new cursor position.

               CALL    DISPLAY_LINE            ;Display just that changed line
                                               ; for better response time.
ENTRY_END:     RET

;----------------------------------------------;
ACTRLHOME:     MOV     DL,BL                   ;Beginning of field.
               JMP     SHORT DO_HOME

AHOME:         CMP     DL,BL                   ;Beginning of field unless
               MOV     DL,BL                   ; already there, then go
               JNZ     ENTRY_UPDATE            ; to section home.
DO_HOME:       MOV     DH,BOUNDS.ROW_HOME[BP]
               JMP     ENTRY_UPDATE

ACTRLEND:      MOV     DL,BH                   ;End of field
               JMP     SHORT DO_END

AEND:          CMP     DL,BH                   ;End of field unless
               MOV     DL,BH                   ; already there, then go
               JNZ     ENTRY_UPDATE            ; to section home.
DO_END:        MOV     DH,BOUNDS.ROW_END[BP]
               JMP     ENTRY_UPDATE

ALEFT:         CMP     DL,BL                   ;Left one space unless
               JNZ     DO_LEFT                 ; already column home.
               CMP     BP,APP_RIGHT
               JNZ     ENTRY_END
               MOV     APP_INDEX,APP_LEFT      ;If in col home of right section,
               SUB     DL,6                    ; go to left section.
DO_LEFT:       DEC     DL
               JMP     ENTRY_UPDATE

ARIGHT:        INC     DL                      ;Right on space unless
               CMP     DL,BH                   ; already column end.
               JBE     ENTRY_UPDATE
               CMP     BP,APP_LEFT
               JNZ     ENTRY_END
               MOV     APP_INDEX,APP_RIGHT     ;If in col end of left section,
               ADD     DL,6                    ; go to right section.
               JMP     ENTRY_UPDATE

ADOWN:         CMP     DH,BOUNDS.ROW_END[BP]   ;If at bottom, go and not
               JNZ     UPDATE_DOWN             ; in Notepad, go to Notepad.
               CMP     BP,APP_NOTE
               JZ      ENTRY_END
               INC     DH                      ;Else, next row.
               MOV     APP_INDEX,APP_NOTE
UPDATE_DOWN:   INC     DH
               JMP     ENTRY_UPDATE

AUP:           CMP     DH,BOUNDS.ROW_HOME[BP]  ;If in top row of NotePad
               JNZ     DO_UP                   ; go to either left or right
               CMP     BP,APP_NOTE             ; section.
               JNZ     ENTRY_END
               DEC     DH
               MOV     BP,APP_RIGHT
               CMP     DL,BOUNDS.COL_HOME[BP]
               JAE     UPDATE_UP
               MOV     BP,APP_LEFT
               MOV     BX,WORD PTR BOUNDS[BP]
               CMP     DL,BL
               JA      CK_UP_END
               MOV     DL,BL
               JMP     SHORT UPDATE_UP
CK_UP_END:     CMP     DL,BH
               JBE     UPDATE_UP
               MOV     DL,BH
UPDATE_UP:     MOV     APP_INDEX,BP
DO_UP:         DEC     DH                      ;Else, just decrement row.
               JMP     ENTRY_UPDATE

ADEL:          SUB     BH,DL                   ;Ignore if in last column.
               MOV     CL,BH
               XOR     CH,CH
               JCXZ    ADEL_END
               MOV     DI,SI
               INC     SI
               REP     MOVSB                   ;Else, move everything to right
               CALL    DISPLAY_LINE            ; of cursor left one space.
               MOV     MODIFY_FLAG,1
ADEL_END:      RET

;----------------------------------------------;
ATODAY:        CALL    PERMANENT
               MOV     SI,YEAR_TODAY
               MOV     YEAR_CAL,SI             ;Go to today.
               MOV     DX,DAY_MON_TODAY
               MOV     DAY_MON_CAL,DX
               JMP     SHORT DAY_UPDATE

APGUP:         CALL    PERMANENT
PPGUP:         MOV     AL,-1                   ;Previous day.
               JMP     SHORT CK_DAY

APGDN:         CALL    PERMANENT
PPGDN:         MOV     AL,1                    ;Next day.
CK_DAY:        MOV     DL,DAY_CAL
               ADD     DL,AL
               JZ      NEW_MONTH
               CMP     DL,LAST_DAY             ;Past month bounds?
               JA      NEW_MONTH
UPDATE_MONTH:  MOV     DAY_CAL,DL              ;If no, OK.
               JMP     SHORT DAY_UPDATE

NEW_MONTH:     MOV     DAY_CAL,1               ;Move to new month.
               JA      PCTRLPGDN
               MOV     DAY_CAL,31
               JMP     SHORT PCTRLPGUP

ACTRLPGUP:     CALL    PERMANENT
PCTRLPGUP:     MOV     AX,-1                   ;Previous month.
               JMP     SHORT CK_MONTH

ACTRLPGDN:     CALL    PERMANENT
PCTRLPGDN:     MOV     AX,1                    ;Next month.
CK_MONTH:      MOV     DH,MONTH_CAL
               ADD     DH,AL
               JNZ     CK_MONTH2
               MOV     DH,12                   ;Past year bounds?
               JMP     SHORT CK_YEAR2
CK_MONTH2:     CMP     DH,12
               JBE     UPDATE_MONTH2
               MOV     DH,1
               JMP     SHORT CK_YEAR2
UPDATE_MONTH2: MOV     MONTH_CAL,DH
               JMP     SHORT DAY_UPDATE

CK_YEAR2:      MOV     MONTH_CAL,DH
               MOV     SI,YEAR_CAL
               ADD     SI,AX                   ;Next/Previous year.
               CMP     SI,10000
               JAE     DAY_END
               CMP     SI,1582                 ;Check bounds.
               JB      DAY_END
               MOV     YEAR_CAL,SI

DAY_UPDATE:    MOV     REPAINT_FLAG,1          ;Repaint the screen.
DAY_END:       RET

;----------------------------------------------;
CLEAR:         MOV     DL,BL
               CALL    CALC_APP_ADDR
               MOV     DI,SI                   ;Appointment address line.
               MOV     CX,BX
               SUB     CH,CL
               MOV     CL,CH
               XOR     CH,CH
               MOV     AL,SPACE                ;Store spaces.
               REP     STOSB
               MOV     APP_CURSOR,DX           ;Home cursor.
               MOV     BOUNDS.CURSOR[BP],DX
               MOV     APP_INDEX,BP
               JMP     SHORT F6_END

SHIFT_CLEAR:   MOV     CX,WORD PTR BOUNDS.ROW_HOME[BP]
               MOV     DH,CH
               SUB     CH,CL
               MOV     CL,CH
               XOR     CH,CH
               INC     CX
NEXT_SHIFT_F6: PUSH    CX                      ;Rows in section.
               CALL    CLEAR                   ;Clear all rows.
               DEC     DH
               POP     CX
               LOOP    NEXT_SHIFT_F6
               JMP     SHORT F6_END

CTRL_CLEAR:    MOV     BP,APP_NOTE             ;Start with Notepad.
               MOV     CX,3                    ;Three sections to clear.
NEXT_CTRL_F6:  PUSH    CX
               MOV     BX,WORD PTR BOUNDS[BP]
               CALL    SHIFT_CLEAR             ;Clear section.
               SUB     BP,SIZE APP_BOUNDS
               POP     CX
               LOOP    NEXT_CTRL_F6

F6_END:        MOV     MODIFY_FLAG,1
               MOV     REPAINT_FLAG,1
               RET

;----------------------------------------------;
; INPUT: DX = Cursor position; SI -> Storage.
DISPLAY_LINE:  MOV     AL,DH
               SUB     AL,4                    ;Calc start of app. line
               MOV     CX,APP_WIDTH            ; to display.
               MUL     CL
               MOV     SI,AX
               ADD     SI,OFFSET APP_START
               MOV     AL,DH
               XOR     AH,AH
               CALL    CALC_ADDR               ;Calc corresponding display
               PUSH    ES                      ; address.
               MOV     DX,STATUS_REG
               MOV     ES,VIDEO_SEG
               MOV     BH,COLOR.B
               DEC     CX
NEXT_LINE:     LODSB                           ;Display line.
               CALL    WRITE_SCREEN
               LOOP    NEXT_LINE
               POP     ES
               RET

;***************************************************************************;
DATEONLY_FLAG  DB      ?                       ; =1 if purge date only.

PTABLE         DB  49H,   51H,   84H,       76H,       3DH
PTABLE_LEN     EQU     $ - PTABLE
               DW  PPGUP, PPGDN, PCTRLPGUP, PCTRLPGDN, CTODAY

PPURGE:        PUSH    YEAR_CAL                ;Preserve calendar variables.
               PUSH    DAY_MON_CAL
               PUSH    YEAR_CUR
               PUSH    DAY_MON_CUR
               CALL    HIDE_CURSOR

NEXT_F4:       CALL    FIRSTDAY                ;Store purge date in window.
               MOV     AX,1
               CALL    CALC_ADDR
               ADD     DI,9 * 2
               MOV     SI,OFFSET PURGE_MSG
               CALL    POP_WINDOW              ;Display purge window.
               CALL    GETKEY                  ;Get a keystroke.
               JC      F4_DONE                 ;If Esc, exit.
               MOV     DATEONLY_FLAG,0         ;Assume F7.
               CMP     AL,F7_SCAN              ;Is it F7?
               JZ      CK_APPEND
               MOV     DATEONLY_FLAG,1         ;Assume F8.
               CMP     AL,F8_SCAN              ;Is it F8.
               JZ      CK_APPEND

               MOV     DI,OFFSET PTABLE
               MOV     CX,PTABLE_LEN
               CALL    DISPATCH
               CMP     EXIT_FLAG,1
               JNZ     NEXT_F4
               JMP     SHORT F4_DONE

F4_END:        MOV     REREAD_FLAG,1

F4_DONE:       POP     DAY_MON_CUR             ;Restore calendar variables.
               POP     YEAR_CUR
               POP     DAY_MON_CAL
               POP     YEAR_CAL
               CALL    FIRSTDAY                ;Fix calendar and appointment
               MOV     REPAINT_FLAG,1          ; ASCII date; repaint screen.
               RET

;----------------------------------------------;
ARCHIVE_FLAG   DB      0                       ; =1 if archive.
READ_HANDLE    DW      -1                      ;File handles.
WRITE_HANDLE   DW      -1
ARCHIVE_HANDLE DW      -1

CK_APPEND:     MOV     ARCHIVE_FLAG,0          ;Reset archive flag.
               MOV     AX,6
               CALL    CALC_ADDR
               ADD     DI,40 * 2
               MOV     SI,OFFSET ARCHIVE_MSG
               CALL    POP_WINDOW              ;Display archive window.

NEXT_APPEND:   CALL    GETKEY                  ;Get a keystroke.
               JC      F4_DONE
               CMP     AL,N_SCAN
               JZ      DO_PURGE
               CMP     AL,Y_SCAN               ;"Y" = archive.
               JNZ     NEXT_APPEND
               MOV     ARCHIVE_FLAG,1

DO_PURGE:      CALL    OPEN_TSR                ;Open data file for reading.
               JBE     F4_DONE                 ;If doesn't exist, nothing to
               MOV     READ_HANDLE,AX          ; purge; else save handle.

               MOV     AX,8
               CALL    CALC_ADDR
               ADD     DI,50 * 2
               MOV     SI,OFFSET PURGING_MSG
               CALL    POP_WINDOW              ;Display purging window.

               CALL    OPEN_TSR                ;Open data file for writing.
               JBE     PURGE_FAIL
               MOV     WRITE_HANDLE,AX
               CMP     ARCHIVE_FLAG,1          ;Archive?
               JNZ     NEXT_PURGE
               MOV     DX,OFFSET ARCHIVE_PATH
               CALL    OPEN_TSR2               ;If yes, open archive file.
               JA      SAVE_HANDLE2
               XOR     CX,CX
               MOV     AH,3CH                  ;If doesn't exist, create one.
               CALL    INT21_IO
               JBE     PURGE_FAIL
SAVE_HANDLE2:  MOV     ARCHIVE_HANDLE,AX
               MOV     BX,AX
               XOR     CX,CX
               XOR     DX,DX
               MOV     AX,4202H                ;Move to end of archive file
               CALL    INT21_IO                ; to append.
               JA      NEXT_PURGE

PURGE_FAIL:    CALL    CLOSE_HANDLES           ;Failure exit.
               CALL    FAILED
               JMP     F4_END


NEXT_PURGE:    MOV     CX,SIZE DATA_RECORD
               MOV     BX,READ_HANDLE
               CALL    READ_RECORD             ;Read a record.
               JBE     PURGE_FAIL
               OR      AX,AX                   ;EOF?
               JZ      PURGE_DONE
               CMP     DATEONLY_FLAG,1         ;Purge everything < purge date.
               JZ      CK_THISDATE
               CMP     CX,YEAR_CAL             ;If no, year < purge date?
               JB      CK_ARCHIVE              ;If yes, purge.
               JA      WRITE_DATA              ;If above write data back.
               CMP     DX,DAY_MON_CAL          ;Else, day/month < purge date?
               JB      CK_ARCHIVE              ;Is yes, purge.
               JMP     SHORT WRITE_DATA        ;Else, write data back.

CK_THISDATE:   CMP     CX,YEAR_CAL             ;Purge date only.
               JNZ     WRITE_DATA
               CMP     DX,DAY_MON_CAL
               JZ      CK_ARCHIVE

WRITE_DATA:    MOV     DX,OFFSET DTA           ;Write data back to data file.
               MOV     CX,SIZE DATA_RECORD
               MOV     BX,WRITE_HANDLE
               CALL    WRITE_RECORD
               JBE     PURGE_FAIL
               JMP     NEXT_PURGE

CK_ARCHIVE:    CMP     ARCHIVE_FLAG,1          ;Archive?
               JNZ     NEXT_PURGE
               CALL    ARCHIVE
               JBE     PURGE_FAIL
               JMP     NEXT_PURGE

PURGE_DONE:    MOV     BX,WRITE_HANDLE
               XOR     CX,CX                   ;Truncate data file to
               CALL    WRITE_RECORD            ; current pointer location.
               JBE     PURGE_FAIL
               CALL    CLOSE_HANDLES           ;Close all file handles.
               JMP     F4_END

;----------------------------------------------;
CLOSE_HANDLES: MOV     BX,READ_HANDLE
               CALL    CLOSE_SAVE
               MOV     BX,WRITE_HANDLE
               CALL    CLOSE_SAVE
               MOV     BX,ARCHIVE_HANDLE
               CALL    CLOSE_SAVE
               RET

;----------------------------------------------;
ARCHIVE:       MOV     BX,ARCHIVE_HANDLE       
               MOV     DX,OFFSET DTA.DATE_ASCII
               MOV     CX,SIZE DATE_ASCII      ;Write date in ASCII.
               CALL    WRITE_RECORD
               JBE     ARCHIVE_END
               MOV     DTA,CR
               MOV     DTA + 1,LF
               CALL    WRITE_CRLF              ;Add Carriage return/linefeed.
               JBE     ARCHIVE_END

               MOV     BP,APP_HEIGHT           
               MOV     SI,OFFSET APP_START + 5
               MOV     DI,OFFSET DTA.APPOINT_TEXT
NEXT_ARCHIVE:  MOV     DX,SI
               MOV     CX,8
               CALL    WRITE_RECORD            ;Write times.
               JBE     ARCHIVE_END
               MOV     DX,DI
               MOV     CX,APP_LEN
               CALL    WRITE_RECORD            ;Write appointments.
               JBE     ARCHIVE_END
               ADD     SI,36
               ADD     DI,APP_LEN
               MOV     DX,SI
               MOV     CX,10
               CALL    WRITE_RECORD
               JBE     ARCHIVE_END
               MOV     DX,DI
               MOV     CX,APP_LEN
               CALL    WRITE_RECORD
               JBE     ARCHIVE_END
               CALL    WRITE_CRLF
               JBE     ARCHIVE_END
               ADD     SI,45
               ADD     DI,APP_LEN
               DEC     BP
               JNZ     NEXT_ARCHIVE

               MOV     BP,3
               MOV     SI,OFFSET DTA.NOTEPAD_TEXT
NEXT_NOTEPAD:  MOV     DX,SI
               MOV     CX,73
               CALL    WRITE_RECORD            ;Write Notepad.
               JBE     ARCHIVE_END
               CALL    WRITE_CRLF
               JBE     ARCHIVE_END
               ADD     SI,APP_WIDTH
               DEC     BP
               JNZ     NEXT_NOTEPAD

               OR      AL,1                    ;Indicate successful.

ARCHIVE_END:   RET

WRITE_CRLF:    MOV     DX,OFFSET DTA
               MOV     CX,2
               CALL    WRITE_RECORD
               RET

;***************************************************************************;
SETUP_CODES    DB      10 DUP (0), 0           ;Printer setup codes.

PRINT:         CALL    HIDE_CURSOR
               MOV     AX,1
               CALL    CALC_ADDR
               ADD     DI,30 * 2
               MOV     SI,OFFSET PRINTER_NO
               CALL    POP_WINDOW              ;Display printer selection window.

NF5_KEY:       CALL    GETKEY                  ;Get a keystroke.
               JC      NF5_END                 ;If Esc, exit.
               XCHG    AH,AL
               SUB     AL,"1"                  ;Adjust to binary.
               JC      NF5_KEY
               CMP     AL,1
               JA      NF5_KEY
               MOV     DL,AL
               XOR     DH,DH

               PUSH    DX
               MOV     SI,OFFSET CAL_MENU
               CMP     STATE,OFFSET CALENDAR
               JZ      FIX_SCREEN
               MOV     SI,OFFSET APP_MENU
FIX_SCREEN:    CALL    POP_MENU                ;Fix screen cause that's where
                                               ; we're getting our data.
               MOV     AX,1
               CALL    CALC_ADDR               ;Printing starts with row 1.
               MOV     BX,DI
               MOV     BP,24
               POP     DX

               MOV     SI,OFFSET SETUP_CODES   ;Send printer setup codes.
NEXT_SETUP:    LODSB
               OR      AL,AL
               JZ      NEXT_PRINT
               CALL    PRINT_IT
               JNC     NEXT_SETUP
               JMP     SHORT NF5_END

NEXT_PRINT:    MOV     SI,BX
               MOV     CX,80                   ;80 characters/line.
NEXT_PRINT2:   CALL    GET_CHAR
               CALL    PRINT_IT
               JC      NF5_END
               LOOP    NEXT_PRINT2
               CALL    PRINT_CRLF
               JC      NF5_END
               ADD     BX,CRT_WIDTH
               DEC     BP
               JNZ     NEXT_PRINT
               MOV     CX,9
NEXT_CRLF:     CALL    PRINT_CRLF
               JC      NF5_END
               LOOP    NEXT_CRLF
NF5_END:       MOV     REPAINT_FLAG,1
               RET

;----------------------------------------------;
IBM_FLAG       DB      0                       ; If 1, use line drawing chars.
IBM_CHARS      DB      "Æµ³ØÂÄÑÍÏþÚ"
EPSON_CHARS    DB      "||||--===*+"           ;Replacements for line chars.
CHARS_LEN      EQU     $ - EPSON_CHARS


PRINT_IT:      PUSH    AX
               MOV     AH,2
               INT     17H
               TEST    AH,00101001B            ;Printer ready?
               JNZ     NOT_READY
               TEST    AH,11111001B
               JNZ     DO_PRINT
NOT_READY:     POP     AX
               CALL    BEEP                    ;If no, beep and exit.
               STC
               JMP     SHORT PRINT_END
DO_PRINT:      POP     AX
               TEST    AL,80H                  ;High bit char?
               JZ      DO_PRINT2               ;If no, print as is.
               CMP     IBM_FLAG,1              ;Else, print line drawing?
               JZ      DO_PRINT2               ;If yes, print as is.
               MOV     DI,OFFSET IBM_CHARS     ;Else, replace with appropriate
               PUSH    CX                      ; text char.
               MOV     CX,CHARS_LEN
               REPNZ   SCASB
               POP     CX
               ADD     DI,CHARS_LEN - 1
               MOV     AL,[DI]

DO_PRINT2:     XOR     AH,AH                   ;Print via BIOS.
               INT     17H
               CLC
PRINT_END:     RET

;----------------------------------------------;
PRINT_CRLF:    MOV     AL,CR
               CALL    PRINT_IT
               JC      CRLF_END
               MOV     AL,LF
               CALL    PRINT_IT
CRLF_END:      RET
;**********************************************;
;       S U B R O U T I N E S                  ;
;**********************************************;
;INPUT: AL = Scan code; DI -> Valid scan codes table; CX = length of table
DISPATCH:      PUSH    AX
               PUSH    DX
               PUSH    BP
               MOV     BP,CX
               MOV     DX,DI
               ADD     DX,CX
               REPNZ   SCASB                   ;Scan for match.
               JZ      DO_DISPATCH
               POP     BP
               POP     DX
               JMP     SHORT NO_DISPATCH
DO_DISPATCH:   SUB     BP,CX
               DEC     BP
               SHL     BP,1
               ADD     BP,DX
               MOV     DI,BP                   ;Calc address of subroutine.
               POP     BP
               POP     DX
               CALL    [DI]                    ;Process the command.
NO_DISPATCH:   CLC
DISPATCH_END:  POP     AX
               RET

;----------------------------------------------;
PERMANENT:     CMP     MODIFY_FLAG,1           ;Any changes been made?
               JNZ     PERMANENT_END           ;If no, done.
               MOV     MODIFY_FLAG,0
               CALL    HIDE_CURSOR
               MOV     AX,10
               CALL    CALC_ADDR
               ADD     DI,25 * 2
               MOV     SI,OFFSET PERMANENT_MSG
               CALL    POP_WINDOW              ;Display "Changes to disk?"

QUERY:         CALL    GETKEY
               JC      PERMANENT_END
               CMP     AL,N_SCAN
               JZ      PERMANENT_END
               CMP     AL,Y_SCAN               ;If "Y", then save.
               JNZ     QUERY
               JMP     SHORT SAVE2

PERMANENT_END: RET

;----------------------------------------------;
SAVE:          CALL    SAVE2                   ;Save appointments.
               JC      F2_END
               MOV     AX,1
               CALL    CALC_ADDR
               ADD     DI,2
               MOV     SI,OFFSET SAVED_MSG
               CALL    POP_WINDOW              ;Display saved message.
               CALL    PAUSE                   ;Pause for keystroke.
F2_END:        RET


SAVE2:         MOV     MODIFY_FLAG,0
               CALL    OPEN_TSR                ;Open data file.
               JNC     SAVE_HANDLE
               XOR     CX,CX                   ;If doesn't exist, create one.
               MOV     AH,3CH
               CALL    INT21_IO
               JBE     FAILED
               MOV     BX,AX

SAVE_HANDLE:   MOV     BP,AX

CK_EOF:        CALL    READ_DATE               ;Read date of data.
               JBE     FAILED_CLOSE
               OR      AX,AX                   ;EOF?
               JNZ     MATCH_DATE              ;If yes, append data.
               CALL    GET_SPACE
               JBE     FAILED_CLOSE
               CMP     BX,2                    ;At least two clusters free?
               MOV     BX,BP                   ;Retrieve file handle.
               JAE     ENOUGH_ROOM
               CALL    CLOSE_SAVE
               MOV     SI,OFFSET DISK_FULL_MSG
               JMP     SHORT FAILED_MSG

ENOUGH_ROOM:   CALL    WRITE_APP               ;Write the data.
               JMP     SHORT CLOSE_SAVE        ;Done.

MATCH_DATE:    CMP     CX,YEAR_CUR             ;Date of data on disk
               JNZ     NO_MATCH                ; match appointment date?
               CMP     DX,DAY_MON_CUR
               JZ      GOT_DATE                ;If yes, write over.
NO_MATCH:      CALL    NEXT_RECORD             ;Else, search next record.
               JBE     FAILED_CLOSE
               JMP     CK_EOF

GOT_DATE:      CALL    WRITE_APP2
               JBE     FAILED_CLOSE

CLOSE_SAVE:    MOV     AH,3EH                  ;Close data file.
               CALL    INT21_IO
               MOV     REPAINT_FLAG,1
SAVE_END:      RET

FAILED_CLOSE:  MOV     AH,3EH
               CALL    INT21_IO
FAILED:        MOV     SI,OFFSET PERMANENT_FAIL
FAILED_MSG:    MOV     AX,10
               CALL    CALC_ADDR
               ADD     DI,25 * 2

               CALL    POP_WINDOW              ;If failed, display failed
               CALL    FLUSH_KEY               ; message.
               XOR     AH,AH                   ;Pause.
               INT     16H
               MOV     REPAINT_FLAG,1
               STC
               RET

;----------------------------------------------;
OPEN_TSR:      MOV     DX,OFFSET DATA_PATH
OPEN_TSR2:     MOV     AX,3D02H                ;Open data file for reading
               CALL    INT21_IO                ; and writing.
               MOV     BX,AX                   ;Filehandle.
               RET

;----------------------------------------------;
; OUTPUT: CX = Year; DH = Month; DL = Day
READ_DATE:     MOV     CX,SIZE DATE_BINARY
READ_RECORD:   MOV     DX,OFFSET DTA
               MOV     AH,3FH                  ;Read first four bytes.
               CALL    INT21_IO
               MOV     CX,WORD PTR DTA.DATE_BINARY      ;Return binary date.
               MOV     DX,WORD PTR DTA.DATE_BINARY + 2
               RET

;----------------------------------------------;
GET_SPACE:     MOV     DL,BYTE PTR DATA_PATH
               SUB     DL,"A" - 1
               MOV     AH,36H                  ;See if room to tack on app.
               CALL    INT21_IO
               RET

;----------------------------------------------;
NEXT_RECORD:   XOR     CX,CX
               MOV     DX,SIZE DATE_ASCII+SIZE APPOINT_TEXT+SIZE NOTEPAD_TEXT
               MOV     AX,4201H
               CALL    INT21_IO                ;Bump pointer to next record.
               RET

;----------------------------------------------;
; OUTPUT: CY = 1 OR ZF = 1 if write failed.
WRITE_APP:     MOV     DX,OFFSET YEAR_CUR      ;Write binary date.
               MOV     CX,SIZE DATE_BINARY
               CALL    WRITE_RECORD
               JBE     WRITE_APP_END

WRITE_APP2:    MOV     DX,OFFSET APP_DATE      ;Write ASCII date.
               MOV     CX,DATE_LEN
               CALL    WRITE_RECORD
               JBE     WRITE_APP_END

               MOV     SI,APP_HEIGHT           ;16 Rows.
               MOV     DI,OFFSET APP_LEFT_START
               MOV     CX,APP_LEN
NEXT_WRITE:    MOV     DX,DI
               CALL    WRITE_RECORD            ;Write appointments.
               JBE     WRITE_APP_END
               ADD     DX,APP_LEN + APP_COL_SPACE
               CALL    WRITE_RECORD
               JBE     WRITE_APP_END
               ADD     DI,APP_WIDTH
               DEC     SI
               JNZ     NEXT_WRITE

WRITE_NOTEPAD: MOV     DX,OFFSET NOTEPAD
               MOV     CX,NOTEPAD_LEN
               CALL    WRITE_RECORD            ;Write Notepad.
               JBE     WRITE_APP_END
               INC     SI                      ;Flag as successful.
               CLC
WRITE_APP_END: RET

WRITE_RECORD:  MOV     AH,40H                  ;DOS write.
               CALL    INT21_IO
               RET

;----------------------------------------------;
READ_APP:      MOV     DX,OFFSET DTA.DATE_ASCII
               MOV     CX,SIZE DATE_ASCII+SIZE APPOINT_TEXT+SIZE NOTEPAD_TEXT
               MOV     AH,3FH
               CALL    INT21_IO                ;Read data record.
               RET

;----------------------------------------------;
BEEP:          PUSH    AX
               PUSH    BX
               PUSH    CX
               PUSH    DX
               CALL    SETUP_BELL

               MOV     CX,1                    ;One timer tick.
               CALL    DELAY                   ;Wait till clock rolls over.
               CALL    TOGGLE_BELL

               MOV     CX,1                    ;Number of seconds.
               CALL    DELAY                   ;Delay seconds.
               CALL    RESET_BELL
               POP     DX
               POP     CX
               POP     BX
               POP     AX
               RET

SETUP_BELL:    MOV     BX,CS:NOTE
               XOR     AX,AX
               MOV     DX,12H                  ;120000h dividend constant.
               DIV     BX                      ; Divide to get
               MOV     BX,AX                   ; 8253 countdown.
               CALL    RESET_BELL
               MOV     AL,0B6H                 ;Channel 2 speaker functions.
               OUT     43H,AL                  ;8253 Mode Control.
               JMP     $+2                     ;IO delay.
               MOV     AX,BX                   ;Retrieve countdown.
               OUT     42H,AL                  ;Channel 2 LSB.
               JMP     $+2
               MOV     AL,AH                   ;Channel 2 MSB.
               OUT     42H,AL
               RET

RESET_BELL:    IN      AL,PORT_B               ;Port B.
               AND     AL,NOT 3                ;Turn off speaker.
               JMP     $+2
               OUT     PORT_B,AL
               RET

FLIP_BELL:     DEC     BELL_CNT
               JNZ     TOGGLE_BELL
               CALL    RESET_BELL
               CMP     PROGRAM_STATUS,0        ;Are we popped up.
               JNZ     FLIP_END                ;If yes, done.
               CMP     NOPOPUP_FLAG,1          ;User requested no-popup?
               JZ      FLIP_END                ;If yes, done.
               MOV     POPUP_FLAG,1            ;Else, try to popup.
FLIP_END:      MOV     BELL_FLAG,0             ;Reset bell flag.
               RET

TOGGLE_BELL:   IN      AL,PORT_B
               XOR     AL,3
               JMP     $+2
               OUT     PORT_B,AL
               RET

;----------------------------------------------;
; INPUT: CX = 1/18 seconds.                    ;
DELAY:         PUSH    DS                      ;Preserve data segment.
               MOV     AX,40H                  ;Point to BIOS data segment.
               MOV     DS,AX
NEXT_TICK:     MOV     AX,DS:[6CH]             ;Retrieve timer low.
NEXT_DELAY:    MOV     DX,DS:[6CH]             ;Retrieve timer low.
               CMP     DX,AX                   ;Have we timed out?
               JZ      NEXT_DELAY              ;If not, wait until timer tick.
               LOOP    NEXT_TICK
               POP     DS                      ;Restore data segment.
               RET

;----------------------------------------------;
SAVE_SCREEN:   XOR     AX,AX                   ;Top left of screen.
               CALL    CALC_ADDR
               MOV     SI,AX
               MOV     DI,OFFSET WIN_SAVE      ;Storage space.
               MOV     CX,25
               MOV     BP,80
               CALL    DO_SAVE                 ;Save entire screen.
               RET

DO_SAVE:       PUSH    DS
               MOV     DX,STATUS_REG
               MOV     DS,VIDEO_SEG

NEXT_SAVE:     PUSH    CX
               PUSH    SI
               MOV     CX,BP
NEXT_SAVE2:    IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it low?
               JC      NEXT_SAVE2              ;If not, wait until it is.
               CLI                             ;No more interrupts.

HWAIT3:        IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it high?
               JNC     HWAIT3                  ;If no, wait until it is.
               MOVSW
               STI
               LOOP    NEXT_SAVE2
               POP     SI
               ADD     SI,CS:CRT_WIDTH         ;Next row.
               POP     CX
               LOOP    NEXT_SAVE
               POP     DS
               RET

;----------------------------------------------;
RESTORE_SCREEN:XOR     AX,AX                   ;Top left of screen.
               CALL    CALC_ADDR
               MOV     SI,OFFSET WIN_SAVE
               MOV     CX,25
               MOV     BP,80
               CALL    DO_RESTORE              ;Restore all 25 rows.
               RET

DO_RESTORE:    PUSH    ES
               MOV     ES,VIDEO_SEG
               MOV     DX,STATUS_REG

NEXT_SCREEN:   PUSH    CX
               PUSH    DI
               MOV     CX,BP
NEXT_SCREEN2:  IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it low?
               JC      NEXT_SCREEN2            ;If not, wait until it is.
               CLI                             ;No more interrupts.

HWAIT2:        IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it high?
               JNC     HWAIT2                  ;If no, wait until it is.
               MOVSW
               STI
               LOOP    NEXT_SCREEN2

               POP     DI
               ADD     DI,CRT_WIDTH            ;Next row.
               POP     CX
               LOOP    NEXT_SCREEN
               POP     ES
               RET

;----------------------------------------------;
POP_MENU:      XOR     AX,AX                   ;Top left of screen.
               CALL    CALC_ADDR

;--------------------------------------------------------;
; INPUT: DI -> Video destination; SI -> Window to popup. ;
POP_WINDOW:    PUSH    ES
               MOV     DX,STATUS_REG
               MOV     ES,VIDEO_SEG

NEXT_POP:      PUSH    DI
NEXT_WIN:      LODSB
               CMP     AL,7                    ;Format code?
               JBE     CK_COMPRESS
               CALL    WRITE_SCREEN            ;If no, write to screen.
               JMP     NEXT_WIN

CK_COMPRESS:   DEC     AL                      ;Character to repeat?
               JNS     CK_DROP
               LODSW                           ;If yes, get count and char.
               MOV     CL,AL
               XOR     CH,CH
               XCHG    AL,AH
               CALL    REPEAT_CHAR             ;Repeat to screen.
               JMP     NEXT_WIN

CK_DROP:       DEC     AL                      ;Transparent black attribute?
               JNS     CK_COLOR
               MOV     CX,2
               CALL    DROP_SHADE              ;If yes, display drop shade.
               JMP     SHORT STRING_END

CK_COLOR:      DEC     AL                      ;New color?
               JNS     CK_STRING
               LODSB
               MOV     BL,AL
               XOR     BH,BH
               MOV     BH,BYTE PTR COLOR[BX]   ;If yes, look it up.
               JMP     NEXT_WIN

CK_STRING:     DEC     AL                      ;End of string?
               JNS     DROP_STRING
STRING_END:    POP     DI
               ADD     DI,CRT_WIDTH            ;If yes, go to next line.
               CMP     BYTE PTR [SI],-1        ;End of window?
               JNZ     NEXT_POP                ;If yes, done.
               JMP     SHORT POP_END

DROP_STRING:   DEC     AL                      ;Bottom drop shade?
               JNS     INSERT_DATE
               LODSB                           ;Get length of window.
               CBW
               MOV     CX,AX
               ADD     DI,4                    ;Shift right 2 columns.
               CALL    DROP_SHADE
               POP     DI
               JMP     SHORT POP_END

INSERT_DATE:   DEC     AL                      ;Special date insert?
               JNS     INSERT_APP
               CALL    DATE_INSERT
               JMP     NEXT_WIN

INSERT_APP:    DEC     AL                      ;Special small char. insert?
               JNS     SKIP
               CALL    APP_INSERT
               JMP     NEXT_WIN

SKIP:          LODSB                           ;Else, skip some characters.
               XOR     AH,AH
               ADD     DI,AX
               JMP     NEXT_WIN

POP_END:       POP     ES
               RET

;----------------------------------------------;
DROP_SHADE:    MOV     BL,8
               INC     DI
ATTR:          IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it low?
               JC      ATTR                    ;If not, wait until it is.
               CLI                             ;No more interrupts.

HWAIT6:        IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it high?
               JNC     HWAIT6                  ;If no, wait until it is.
               MOV     AL,BL
               STOSB
               STI                             ;Interrupts back on.
               INC     DI
               LOOP    ATTR
               RET

;----------------------------------------------;
DATE_INSERT:   PUSH    BX
               MOV     AH,WEEKDAY
               INC     DAY_COUNTER
               MOV     AL,DAY_COUNTER
               CMP     AL,AH                   ;Are we to first day of month?
               JBE     DO_BLANK                ;If no, blanks.
               SUB     AL,AH
               CBW
               CMP     AL,LAST_DAY             ;Are we past last day of month?
               JA      DO_BLANK                ;If yes, blanks.
               CMP     AL,DAY_TODAY
               JNZ     DO_DAY
               MOV     CL,MONTH_TODAY
               CMP     CL,MONTH_CAL
               JNZ     DO_DAY
               MOV     CX,YEAR_TODAY
               CMP     CX,YEAR_CAL
               JNZ     DO_DAY
               MOV     BH,COLOR.Y              ;Use highlight color if today.
DO_DAY:        CALL    DECIMAL_ASCII           ;Write the date.
               JMP     SHORT D_INSERT_END

DO_BLANK:      MOV     AL,SPACE
               CALL    WRITE_SCREEN
               MOV     AL,SPACE
               CALL    WRITE_SCREEN

D_INSERT_END:  POP     BX
               RET

;----------------------------------------------;
APP_INSERT:    PUSH    BX
               MOV     BH,COLOR.A              ;Assume small block color.
               MOV     AH,WEEKDAY
               MOV     BP,BLOCK_ROW            ;Block row index (0 or 1).
               INC     BLOCK_COUNTER[BP]       ;Block day counter.
               MOV     AL,BLOCK_COUNTER[BP]
               CMP     AL,AH                   ;If before first day, blanks.
               JBE     DO_BLANK2

               PUSH    AX                      ;Save block counter.
               CBW
               MOV     CL,7
               DIV     CL
               OR      AH,AH
               JNZ     CALC_BLOCK
               XOR     BLOCK_ROW,1             ;Once a week flip block row index

CALC_BLOCK:    POP     AX
               SUB     AL,AH
               CMP     AL,LAST_DAY             ;Past last day?
               JA      DO_BLANK2               ;If so, blanks.
               CBW
               CMP     AL,DAY_CAL
               JNZ     CK_ROW
               MOV     BH,COLOR.C              ;If today, user cursor color.

CK_ROW:        MOV     CL,3
               DEC     AX
               SHL     AX,CL                   ;Index * 8 into array.
               TEST    BP,1
               JZ      GOT_INDEX
               ADD     AX,BLOCK_COUNT / 2      ; + Array size / 2 for odd rows.

GOT_INDEX:     MOV     BP,AX
               MOV     CX,8
NEXT_BLOCK:    MOV     AL,APPOINTMENT_BLOCKS[BP]
               CALL    WRITE_SCREEN            ;Write the 8 block chars.
               INC     BP
               LOOP    NEXT_BLOCK
               JMP     SHORT A_INSERT_END

DO_BLANK2:     MOV     AL,SPACE
               MOV     CX,8
               CALL    REPEAT_CHAR

A_INSERT_END:  POP     BX
               RET

;----------------------------------------------;
CALC_ADDR:     MUL     CRT_WIDTH               ;Address = row * screen width
               ADD     AX,CRT_START            ; + start of screen buffer.
               MOV     DI,AX
               RET

;----------------------------------------------;
;INPUT: CX=char count; AX=character
REPEAT_CHAR:   PUSH    AX
               CALL    WRITE_SCREEN
               POP     AX
               LOOP    REPEAT_CHAR
               RET

;----------------------------------------------;
WRITE_SCREEN:  MOV     BL,AL                   ;Store character in BL.
HORZ_RET:      IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it low?
               JC      HORZ_RET                ;If not, wait until it is.
               CLI                             ;No more interrupts.

HWAIT:         IN      AL,DX                   ;Get status.
               RCR     AL,1                    ;Is it high?
               JNC     HWAIT                   ;If no, wait until it is.

               MOV     AX,BX                   ;Retrieve character; now it's OK
               STOSW                           ; to write to screen buffer.
               STI                             ;Interrupts back on.
               RET                             ;Return

;----------------------------------------------;
GET_CHAR:      PUSH    DS
               PUSH    DX
               MOV     DX,STATUS_REG
               MOV     DS,VIDEO_SEG

HORZ_RET2:     IN      AL,DX                  ;Get status.
               RCR     AL,1                   ;Is it low?
               JC      HORZ_RET2              ;If not, wait until it is.
               CLI                            ;No more interrupts.

HWAIT4:        IN      AL,DX                  ;Get status.
               RCR     AL,1                   ;Is it high?
               JNC     HWAIT4                 ;If no, wait until it is.
               LODSW
               STI
               POP     DX
               POP     DS
               RET

;----------------------------------------------;
DECIMAL_ASCII: MOV     CL,10
               DIV     CL
               ADD     AX,"0" SHL 8 + "0"
               PUSH    AX
               CMP     AL,"0"
               JNZ     DO_DECIMAL
               MOV     AL,SPACE
DO_DECIMAL:    CALL    WRITE_SCREEN
               POP     AX
               MOV     AL,AH
               CALL    WRITE_SCREEN
               RET

;------------------------------------------------------------------------------
; OUTPUT: AL=scan code; AH=char; CY=1 if hotkey or Esc; EXIT_FLAG=1 if hotkey
GETKEY:        MOV     AH,1                    ;Keystroke waiting?
               INT     16H
               JNZ     GETKEY1
               INT     28H                     ;DOS idle interrupt.
               JMP     GETKEY
GETKEY1:       XOR     AH,AH                   ;Get keystroke.
               INT     16H
               XCHG    AL,AH
               CMP     AL,ESC_SCAN             ;Esc key?
               JZ      EXIT_KEY

CK_HOT:        CMP     AL,HOT_KEY_SCAN         ;Hotkey?
               JNZ     GETKEY_END
               PUSH    AX
               MOV     AH,2
               INT     16H
               TEST    AL,HOT_SHIFT_KEY
               POP     AX
               JZ      GETKEY_END
               MOV     EXIT_FLAG,1

EXIT_KEY:      STC
               RET

GETKEY_END:    CLC
               RET

;----------------------------------------------;
FLUSH_IT:      XOR     AH,AH                   ;Flush keyboard buffer.
               INT     16H
FLUSH_KEY:     MOV     AH,1
               INT     16H
               JNZ     FLUSH_IT
               RET

;----------------------------------------------;
PAUSE:         MOV     AH,1                    ;Wait till keystroke in buffer.
               INT     16H
               JZ      PAUSE
               RET

;---------------------------------------------;
; Move BIOS video data into our data segment. ;
;---------------------------------------------;
GET_BIOS_DATA: PUSH    DS
               PUSH    ES

               MOV     AX,40H                  ;BIOS data area.
               MOV     DS,AX
               PUSH    CS
               POP     ES

               MOV     SI,BIOS_ACTIVE_PAGE     ;Start with active page.
               MOV     DI,OFFSET ACTIVE_PAGE
               MOVSB                           ;Retrieve active page
               MOVSW                           ; and address of 6845 port.
               MOV     SI,BIOS_CRT_MODE        ;Retrieve CRT mode, CRT columns,
               MOV     CX,CRT_DATA_LENGTH      ; CRT length, CRT start.
               REP     MOVSB

               XOR     BH,BH
               MOV     DL,24                   ;Assume 24 logical rows.
               MOV     AX,1130H                ;Get Information via BIOS.
               INT     10H
STORE_ROWS:    POP     ES
               POP     DS
               MOV     CRT_ROWS,DL             ;Store rows.

               MOV     BH,ACTIVE_PAGE
               MOV     AH,3
               INT     10H                     ;Get Cursor mode.
               MOV     CURSOR_MODE,CX
               MOV     CURSOR_POS,DX
               MOV     DX,ADDR_6845
               ADD     DX,6
               MOV     STATUS_REG,DX
               MOV     AX,0B000H
               CMP     DX,3BAH
               JZ      STORE_VIDEO
               ADD     AX,800H
STORE_VIDEO:   MOV     VIDEO_SEG,AX            ;Video address.
               MOV     AX,CRT_COLS
               SHL     AX,1
               MOV     CRT_WIDTH,AX            ;CRT width = columns * 2.
               RET

;------------------------------------------------------------------------------;
; Read port directly for programs like 123 that do not use BIOS to set address.;
;------------------------------------------------------------------------------;
GET_CUR_ADDR:  MOV     DX,ADDR_6845
               MOV     AL,14
               OUT     DX,AL
               INC     DX
               IN      AL,DX
               MOV     AH,AL
               DEC     DX
               MOV     AL,15
               OUT     DX,AL
               INC     DX
               IN      AL,DX
               MOV     CURSOR_ADDR,AX
               RET

;----------------------------------------------;
HIDE_CURSOR:   MOV     DH,CRT_ROWS
               INC     DH
               XOR     DL,DL

SET_CURSOR:    PUSH    AX
               MOV     BH,ACTIVE_PAGE
               MOV     AH,2
               INT     10H
               POP     AX
               RET

;------------------------------------------------------------------------------
;IOSET vectors interrupts 1Bh, 23h and 24h to internal handlers.  IORESET
;restores the original vector values.
;------------------------------------------------------------------------------
IOSET          PROC    NEAR
               PUSH    ES
               MOV     AX,351BH                ;BIOS Ctrl break interrupt.
               INT     21H
               MOV     OLD1B[0],BX
               MOV     OLD1B[2],ES
               MOV     DX,OFFSET IOEXIT        ;Ignore.
               MOV     AX,251BH
               INT     21H

               MOV     AX,3523H                ;DOS Ctrl break interrupt.
               INT     21H
               MOV     OLD23[0],BX
               MOV     OLD23[2],ES
               MOV     DX,OFFSET IOEXIT        ;Ignore.
               MOV     AX,2523H
               INT     21H

               MOV     AX,3524H                ;Critical error interrupt.
               INT     21H
               MOV     OLD24[0],BX
               MOV     OLD24[2],ES
               MOV     DX,OFFSET IOERR         ;Install ours.
               MOV     AX,2524H
               INT     21H
               POP     ES
               RET
IOSET          ENDP

;-------------------------------------------;
IORESET        PROC    NEAR
               PUSH    DS
               MOV     DX,OLD24[0]
               MOV     DS,OLD24[2]
               MOV     AX,2524H                ;Restore Critical.
               INT     21H

               MOV     DX,CS:OLD23[0]
               MOV     DS,CS:OLD23[2]
               MOV     AX,2523H                ;Restore DOS Ctrl break.
               INT     21H

               MOV     DX,CS:OLD1B[0]
               MOV     DS,CS:OLD1B[2]
               MOV     AX,251BH                ;Restore BIOS Ctrl break.
               INT     21H
               POP     DS
               RET
IORESET        ENDP

;------------------------------------------------------------------------------
;INITIALIZE prepares the program for residency.
;------------------------------------------------------------------------------
PATH_LEN       EQU     100
DATA_PATH      DB      PATH_LEN DUP (0)
ARCHIVE_PATH   DB      PATH_LEN DUP (0)
               DB      (256 / 8) DUP ("STACK   ")
OUR_STACK      =       $
ERR_SAVE       DB      (((ERR_WIDTH + 2) * 2) * (ERR_HEIGHT + 1)) DUP (?)
WIN_SAVE       DB      ((80 * 2) * 25) DUP (?)
RESIDENT_END   =       $

SYNTAX DB  "Syntax: Schedule [/I] [/U] [/Hn] [/Pn...n] [/G] [/A] [/C] [/M] [/B]",CR,LF
       DB  "/I = Install Memory-resident",CR,LF
       DB  "/U = Uninstall",CR,LF
       DB  "/H = Hotkey assignment; Ctrl or Alt plus new hotkey",CR,LF
       DB  "     eg. /H Ctrl Y or /H Alt 1;  default is Alt C",CR,LF
       DB  "/P = Printer setup string in decimal; 10 maximum.",CR,LF
       DB  "     eg. /P 27 40 115 66 is boldface on a LaserJet",CR,LF
       DB  "/G = Graphical characters in printing; Use appropriate /P",CR,LF
       DB  "     setup for your printer to switch to IBM character set",CR,LF
       DB  "/A = Appointment pop up OFF",CR,LF
       DB  "/C = Chime OFF",CR,LF
       DB  "/M = Midnight update pop up OFF",CR,LF
       DB  "/B = Black and white attributes"
       DB  CR,LF,LF,"$"

CANT_FIND      DB      "Can't find Schedule.com",CR,LF
               DB      "Change to Schedule's directory before running.",CR,LF,"$"
NOT_INSTALLED  DB      "Schedule not installed",CR,LF,"$"
ALREADY_MSG    DB      "Schedule already installed",CR,LF,"$"
UNLOAD_MSG     DB      "Schedule can't be uninstalled",CR,LF
               DB      "Uninstall resident programs in reverse order",CR,LF,"$"
NOT_ENOUGH     DB      "Not enough memory to install Schedule",CR,LF,"$"
ALLOCATE_MSG   DB      "Memory allocation error",CR,LF,BELL,"$"
INSTALL_MSG    DB      "Installed",CR,LF,"$"
UNINSTALL_MSG  DB      "Uninstalled",CR,LF,"$"
TSR            DB      "SCHEDULE.COM",0
TSR_LEN        EQU     $ - TSR
ARCHIVE_FILE   DB      "SCHEDULE.ASC",0
ARC_LEN        EQU     $ - ARCHIVE_FILE
DATA_FILE      DB      "SCHEDULE.DAT",0
DAT_LEN        EQU     $ - DATA_FILE

CRITICAL_MSG   DB      "DOS Critical Error Flag not found",CR,LF
               DB      "Can't install",CR,LF,"$"

HOTKEY_MSG     DB      "Press $"
HOTKEY_MSG2    DB      " to pop-up Schedule",CR,LF,"$"

SIDEKICK_MSG   DB      "SideKick must be loaded last.",CR,LF
               DB      "Uninstall SideKick and then install Schedule",CR,LF,"$"

WRONG_VERSION  DB      "Needs DOS 2.0 or later$"

SCAN_CODES LABEL BYTE

DB "1",2,"2",3,"3",4,"4",5,"5",6,"6",7,"7",8,"8",9,"9",0AH,"0",0BH,"-",0CH
DB "=",0DH,"Q",10H,"W",11H,"E",12H,"R",13H,"T",14H,"Y",15H,"U",16H,"I",17H
DB "O",18H,"P",19H,"[",1AH,"]",1BH,"A",1EH,"S",1FH,"D",20H,"F",21H,"G",22H
DB "H",23H,"J",24H,"K",25H,"L",26H,";",27H,39,28H,96,29H,"\",2BH,"Z",2CH
DB "X",2DH,"C",2EH,"V",2FH,"B",30H,"N",31H,"M",32H,",",33H,".",34H,"/",35H

SCAN_COUNT     EQU     ($ - SCAN_CODES) / 2

INSTALL_FLAG   DB      0                       ; =1 if /I found.
UNINSTALL_FLAG DB      0                       ; =1 if /U found.
ALT            DB      " Alt $"
CTRL           DB      "Ctrl $"
ALT_CAPS       DB      "ALT"
CTRL_CAPS      DB      "CTRL"

;----------------------------------------------;
INITIALIZE     PROC    NEAR
               CLD                             ;All string operations forward.
               MOV     BX,OFFSET SIGNATURE     ;Point to start of code.
               NOT     BYTE PTR [BX]           ;Change a byte so no false match.
               XOR     DX,DX                   ;Start at segment zero.
               MOV     AX,CS                   ;Store our segment in AX.
NEXT_PARAG:    INC     DX                      ;Next paragraph.
               MOV     ES,DX
               CMP     DX,AX                   ;Is it our segment?
               JZ      ANNOUNCE                ;If yes, search is done.
               MOV     SI,BX                   ;Else, point to our signature.
               MOV     DI,BX                   ; and offset of possible match.
               MOV     CX,16                   ;Check 16 bytes for match.
               REP     CMPSB
               JNZ     NEXT_PARAG              ;If no match, keep looking.

;----------------------------------------------;
ANNOUNCE:      MOV     TSR_SEGMENT,ES
               MOV     DX,OFFSET SIGNATURE + 1 ;Display our signature.
               CALL    PRINT_STRING
               MOV     DX,OFFSET SYNTAX        ;And syntax.
               CALL    PRINT_STRING

               MOV     SI,81H                  ;Point to command line.
NEXT_CAP:      LODSB                           ;Capitalize parameters.
               CMP     AL,CR
               JZ      PARSE
               CMP     AL,"a"
               JB      NEXT_CAP
               CMP     AL,"z"
               JA      NEXT_CAP
               AND     BYTE PTR [SI - 1],5FH
               JMP     SHORT NEXT_CAP

;----------------------------------------------;
PARSE:         MOV     SI,81H                  ;Point to command line again.
NEXT_SWITCH:   LODSB
               CMP     AL,CR                   ;Is it carriage return?
               JNZ     CK_SPACE
               JMP     INSTALL                 ;If yes, done here.
CK_SPACE:      CMP     AL,SPACE                ;If space or below, ignore.
               JBE     NEXT_SWITCH
               CMP     AL,"/"                  ;Is it a switch character?
               JZ      GET_SWITCH              ;If yes, continue.
               JMP     TERMINATE               ;Else, error; exit.

GET_SWITCH:    LODSB
               CMP     AL,CR
               JZ      INSTALL
               CMP     AL,"U"                  ;Is it "U" ?
               JNZ     CK_I
               MOV     UNINSTALL_FLAG,1
               CALL    CK_INSTALLED            ;Else, see if installed.
               JZ      NO_TSR
               JMP     UNINSTALL               ;Else, uninstall.
NO_TSR:        MOV     DX,OFFSET NOT_INSTALLED ;If no, exit with error message.
               JMP     MSG_EXIT

CK_I:          CMP     AL,"I"                  ;Is it (I)nstall?
               JNZ     CK_G
               MOV     INSTALL_FLAG,1
               JMP     NEXT_SWITCH

CK_G:          CMP     AL,"G"                  ;Is it (G)raphic chars?
               JNZ     CK_P
               MOV     ES:IBM_FLAG,1
               JMP     NEXT_SWITCH

CK_P:          CMP     AL,"P"                  ;Is it (P)rinter setup codes?
               JNZ     CK_H
               CALL    GET_SETUP
               JMP     NEXT_SWITCH

CK_H:          CMP     AL,"H"                  ;Is it (H)otkey?
               JNZ     CK_C
               CALL    CHANGE_HOT
               JMP     NEXT_SWITCH

CK_C:          CMP     AL,"C"                  ;Is it (C)hime OFF?
               JNZ     CK_A
               MOV     ES:NOBELL_FLAG,1
               JMP     NEXT_SWITCH

CK_A:          CMP     AL,"A"                  ;Is it (A)ppointment popup OFF?
               JNZ     CK_M
               MOV     ES:NOPOPUP_FLAG,1
               JMP     NEXT_SWITCH

CK_M:          CMP     AL,"M"                  ;Is it (M)idnight rollover OFF?
               JNZ     CK_B
               MOV     ES:NOMIDNIGHT_FLAG,1
               JMP     NEXT_SWITCH

CK_B:          CMP     AL,"B"                  ;Is it (B)lack and white switch
               JZ      GOT_B
               JMP     TERMINATE
GOT_B:         MOV     ES:MONO_FLAG,1
               JMP     NEXT_SWITCH

;----------------------------------------------;
INSTALL:       CALL    CK_INSTALLED            ;Check if already installed.
               JZ      CK_AVAILABLE            ;If no, see if enough memory.
               MOV     DX,OFFSET ALREADY_MSG   ;If yes, display "Already
               CALL    PRINT_STRING            ; installed" and
               CALL    DISP_HOTKEY             ; current hotkey.
               XOR     AL,AL                   ; EL = 0
               JMP     TERMINATE

;----------------------------------------------;
CK_AVAILABLE:  MOV     BX,OFFSET RESIDENT_END
               ADD     BX,15                   ;Round up.
               MOV     CL,4
               SHR     BX,CL                   ;Convert to paragraphs.
               MOV     AH,4AH
               INT     21H                     ;Allocate memory.
               JNC     GET_VERSION
               MOV     DX,OFFSET NOT_ENOUGH    ;Exit if not enough
               JMP     MSG_EXIT                ; with message.

GET_VERSION:   MOV     AH,30H                  ;Get DOS version
               INT     21H
               XCHG    AL,AH
               MOV     DOS_VERSION,AX          ; and save.
               CMP     AH,2
               JAE     GET_PATH
               MOV     DX,OFFSET WRONG_VERSION ;If not DOS 2 or above, exit.
               JMP     MSG_EXIT

GET_PATH:      MOV     DX,OFFSET TSR           ;Open .COM file.
               MOV     AX,3D00H
               INT     21H
               JNC     FOUND_PATH
NOT_FOUND:     MOV     DX,OFFSET CANT_FIND     ;If can't find, exit
               JMP     MSG_EXIT                ; with message.

FOUND_PATH:    MOV     BX,AX                   ;Close .COM file.
               MOV     AH,3EH
               INT     21H
               MOV     AH,19H                  ;Get default drive.
               INT     21H
               ADD     AL,"A"
               MOV     DI,OFFSET DATA_PATH
               STOSB
               MOV     AL,":"                  ;Add delimiters.
               STOSB
               MOV     AL,"\"
               STOSB
               MOV     SI,DI
               XOR     DL,DL
               MOV     AH,47H                  ;Get default directory.
               INT     21H
               JC      NOT_FOUND

FIND_END:      LODSB                           ;Find end of path.
               OR      AL,AL
               JNZ     FIND_END
               MOV     DI,SI
               DEC     DI
               MOV     AL,"\"                  ;Add on delimiter if root.
               CMP     [DI-1],AL
               JZ      ADD_TSR
               STOSB
ADD_TSR:       PUSH    DI
               MOV     SI,OFFSET DATA_FILE
               MOV     CX,TSR_LEN
               REP     MOVSB                   ;Tack on data filename.
               MOV     SI,OFFSET DATA_PATH
               MOV     DI,OFFSET ARCHIVE_PATH
               MOV     CX,PATH_LEN
               REP     MOVSB                   ;Make a copy.
               POP     DI
               SUB     DI,OFFSET DATA_PATH
               ADD     DI,OFFSET ARCHIVE_PATH
               MOV     SI,OFFSET ARCHIVE_FILE
               MOV     CX,ARC_LEN
               REP     MOVSB                   ;Make this one archive filename

GET_INDOS:     MOV     AH,34H                  ;Undocumented INDOS call.
               INT     21H
               MOV     DOS_SEGMENT,ES
               MOV     INDOS_OFFSET,BX

               MOV     AX,3E80H                ;CMP opcode
               MOV     CX,2000H                ;Max search length
               MOV     DI,BX                   ;Look for Critical error address.
INIT4:         REPNZ   SCASW
               JCXZ    INIT5
               CMP     BYTE PTR ES:[DI+5],0BCH
               JZ      FOUND
               JMP     INIT4
INIT5:         MOV     CX,2000H
               INC     BX                      ;Odd addresses.
               MOV     DI,BX
INIT6:         REPNZ   SCASW
               JCXZ    NOTFOUND
               CMP     BYTE PTR ES:[DI+5],0BCH
               JZ      FOUND
               JMP     INIT6

NOTFOUND:      MOV     DX,OFFSET CRITICAL_MSG
               JMP     MSG_EXIT

FOUND:         MOV     AX,ES:[DI]
               MOV     ERRFLAG_OFFSET,AX

               CMP     INSTALL_FLAG,1          ;Install request?
               JZ      CK_SIDEKICK
               MOV     AH,0FH                  ;If no, change to text
               INT     10H                     ; video mode if necessary.
               CMP     AL,2
               JZ      GET_VECTORS
               CMP     AL,7
               JZ      GET_VECTORS
               CMP     AL,3
               JZ      GET_VECTORS
               MOV     AX,3
               INT     10H
               JMP     SHORT GET_VECTORS

CK_SIDEKICK:   XOR     AX,AX                   ;Check if SideKick installed.
               MOV     ES,AX
               MOV     ES,ES:[(9 * 4) + 2]     ;INT 9 segment
               CMP     WORD PTR ES:[16CH],"KS" ;SideKick signature.
               JNZ     GET_VECTORS
               MOV     DX,OFFSET SIDEKICK_MSG
               JMP     MSG_EXIT

GET_VECTORS:   PUSH    CS
               POP     ES
               CALL    UPDATE_DATE             ;Update calendar variables.
               MOV     YEAR_CAL,CX
               MOV     DAY_MON_CAL,DX
               CALL    GET_BIOS_DATA           ;Get BIOS variables.
               CALL    CK_DATE

               MOV     AX,3508H                ;INT 8
               INT     21H
               MOV     OLD8[0],BX
               MOV     OLD8[2],ES
               MOV     DX,OFFSET TIMER         ;Install new interrupt.
               MOV     AX,2508H
               INT     21H

               MOV     AX,3510H                ;Get INT 10 interrupt.
               INT     21H
               MOV     OLD10[0],BX             ;Save old interrupt.
               MOV     OLD10[2],ES
               MOV     DX,OFFSET VIDEO         ;Install new interrupt.
               MOV     AX,2510H
               INT     21H

               MOV     AX,3513H                ;Get INT 13 vector.
               INT     21H
               MOV     OLD13[0],BX             ;Save old interrupt.
               MOV     OLD13[2],ES
               MOV     DX,OFFSET BDISK         ;Install new interrupt.
               MOV     AX,2513H
               INT     21H

               MOV     AX,3528H                ;Get INT 28 interrupt.
               INT     21H
               MOV     OLD28[0],BX             ;Save old interrupt.
               MOV     OLD28[2],ES
               MOV     DX,OFFSET BACKPROC      ;Install new interrupt.
               MOV     AX,2528H
               INT     21H

               CMP     INSTALL_FLAG,1          ;Install request?
               JZ      INSTALL_INT9            ;If no, skip INT 9
               CALL    MAIN                    ; and just popup now.
               JMP     SHORT UNINSTALL         ;Uninstall vectors on return.

STAY_RESIDENT: CALL    PRINT_STRING
INSTALL_INT9:  MOV     AX,3509H                ;Get INT 9 vector.
               INT     21H
               MOV     OLD9[0],BX              ;Save old interrupt.
               MOV     OLD9[2],ES
               MOV     DX,OFFSET KEYBOARD      ;Install new interrupt.
               MOV     AX,2509H
               INT     21H

               MOV     AX,DS:[2CH]             ;Get environment segment.
               MOV     ES,AX
               MOV     AH,49H                  ;Free up environment.
               INT     21H

               MOV     DX,OFFSET INSTALL_MSG   ;Display install message.
               CALL    PRINT_STRING
               CALL    DISP_HOTKEY

               MOV     DX,OFFSET RESIDENT_END
               ADD     DX,15                   ;Round up.
               MOV     CL,4
               SHR     DX,CL                   ;Convert to paragraphs.
               MOV     AX,3100H                ;Return error code of zero.
               INT     21H                     ;Terminate but stay resident.

;-------------------------------------------------------------------;
; Exit.  Return ERRORLEVEL code 0 if successful, 1 if unsuccessful. ;
;-------------------------------------------------------------------;
MSG_EXIT:      CALL    PRINT_STRING
ERROR_EXIT:    MOV     AL,1                    ;ERRORLEVEL = 1.
TERMINATE:     MOV     AH,4CH                  ;Terminate.
               INT     21H

;---------------------------------------------------;
; This subroutine uninstalls the resident TSR.      ;
;---------------------------------------------------;
UNINSTALL:     MOV     CX,ES                   ;Save segment in CX.
DO_VECTORS:    MOV     AX,3508H                ;Get interrupt 8h.
               INT     21H
               CMP     BX,OFFSET TIMER         ;Has it been hooked by another?
               JNZ     UNINSTALL_ERR           ;If yes, exit with error message.
               MOV     BX,ES
               CMP     BX,CX                   ;Is the segment vector same?
               JNZ     UNINSTALL_ERR           ;If no, exit with error message.

               MOV     AX,3510H                ;Get interrupt 10h.
               INT     21H
               CMP     BX,OFFSET VIDEO         ;Has it been hooked by another?
               JNZ     UNINSTALL_ERR           ;If yes, exit with error message.
               MOV     BX,ES
               CMP     BX,CX                   ;Is the segment vector same?
               JNZ     UNINSTALL_ERR           ;If no, exit with error message.

               MOV     AX,3513H                ;Get interrupt 13h.
               INT     21H
               CMP     BX,OFFSET BDISK         ;Has it been hooked by another?
               JNZ     UNINSTALL_ERR           ;If yes, exit with error message.
               MOV     BX,ES
               CMP     BX,CX                   ;Is the segment vector same?
               JNZ     UNINSTALL_ERR           ;If no, exit with error message.

               MOV     AX,3528H                ;Get interrupt 28h.
               INT     21H
               CMP     BX,OFFSET BACKPROC      ;Has it been hooked by another?
               JNZ     UNINSTALL_ERR           ;If yes, exit with error message.
               MOV     BX,ES
               CMP     BX,CX                   ;Is the segment vector same?
               JNZ     UNINSTALL_ERR

               CMP     UNINSTALL_FLAG,1        ;Uninstall request?
               JNZ     DO_UNINSTALL            ;If no, skip INT 9.

               MOV     AX,3509H                ;Get interrupt 9h.
               INT     21H
               CMP     BX,OFFSET KEYBOARD      ;Has it been hooked by another?
               JNZ     UNINSTALL_ERR           ;If yes, exit with error message.
               MOV     BX,ES
               CMP     BX,CX                   ;Is the segment vector same?
               JZ      DEALLOCATE              ;If no, exit with error message.

UNINSTALL_ERR: MOV     DX,OFFSET UNLOAD_MSG    ;And exit with error message.
               CMP     UNINSTALL_FLAG,1        ;Uninstall request?
               JZ      LILLY_ERR               ;If yes, just exit.
               JMP     STAY_RESIDENT           ;Else, go resident.
LILLY_ERR:     JMP     MSG_EXIT

DEALLOCATE:    MOV     AH,49H                  ;Return memory to system pool.
               INT     21H
               MOV     DX,OFFSET ALLOCATE_MSG
               JC      LILLY_ERR               ;Display message if problem.

               MOV     DX,ES:OLD9[0]           ;Restore old INT 9.
               MOV     DS,ES:OLD9[2]
               MOV     AX,2509H
               INT     21H

DO_UNINSTALL:  MOV     DX,ES:OLD8[0]           ;Restore old INT 8.
               MOV     DS,ES:OLD8[2]
               MOV     AX,2508H
               INT     21H

               MOV     DX,ES:OLD10[0]          ;Restore old INT 10.
               MOV     DS,ES:OLD10[2]
               MOV     AX,2510H
               INT     21H

               MOV     DX,ES:OLD13[0]           ;Restore old INT 13.
               MOV     DS,ES:OLD13[2]
               MOV     AX,2513H
               INT     21H

               MOV     DX,ES:OLD28[0]          ;Restore old INT 28.
               MOV     DS,ES:OLD28[2]
               MOV     AX,2528H
               INT     21H

               PUSH    CS
               POP     DS                      ;Point to our data.
               CMP     UNINSTALL_FLAG,1
               JNZ     UNINSTALL_END
               MOV     DX,OFFSET UNINSTALL_MSG ;Display uninstall message.
               CALL    PRINT_STRING
UNINSTALL_END: XOR     AL,AL                   ;Exit with ERRORLEVEL = 0.
               JMP     TERMINATE

INITIALIZE     ENDP

;-------------------------------------------------------;
; OUTPUT: ZR = 1 if not installed; ZR = 0 if installed. ;
;-------------------------------------------------------;
CK_INSTALLED:  MOV     AX,ES
               MOV     BX,CS
               CMP     AX,BX                   ;Compare segments.
               RET

;----------------------------------------------;
CHANGE_HOT:    MOV     BP,ES                   ;Save segment.
               MOV     DX,CS
               CALL    FIND_START              ;Find start of parameter.
               CMP     AL,CR
               JZ      HOT_END
               MOV     BX,SI
               MOV     ES,DX
               MOV     DI,OFFSET ALT_CAPS      ;Is it "ALT"?
               MOV     CX,3
               REP     CMPSB
               JNZ     CK_CTRL

               MOV     ES,BP
               MOV     ES:HOT_SHIFT_KEY,ALT_STATE
               JMP     SHORT GET_HOT

CK_CTRL:       MOV     SI,BX
               MOV     DI,OFFSET CTRL_CAPS     ;Is it "CTRL"?
               MOV     CX,4
               REP     CMPSB
               JZ      GOT_CTRL
               MOV     SI,BX
               JMP     SHORT GET_HOT
GOT_CTRL:      MOV     ES,BP
               MOV     ES:HOT_SHIFT_KEY,CTRL_STATE

GET_HOT:       CALL    FIND_START              ;Find parameter start.
               CMP     AL,CR
               JZ      HOT_END
               LODSB
               MOV     CX,SCAN_COUNT           
               MOV     ES,DX
               MOV     DI,OFFSET SCAN_CODES
NEXT_HOT:      SCASB                           ;Look up hotkey.
               JZ      FOUND_HOT
               INC     DI
               LOOP    NEXT_HOT
               JMP     SHORT HOT_END

FOUND_HOT:     MOV     AH,[DI]
               MOV     ES,BP
               MOV     ES:COMBO,AL             ;Store hotkey ASCII
               MOV     ES:HOT_KEY_SCAN,AH      ; and scan code.
HOT_END:       MOV     ES,BP
               RET

;----------------------------------------------;
FIND_START:    LODSB
               CMP     AL,CR
               JZ      START_END
               CMP     AL,SPACE
               JBE     FIND_START
START_END:     DEC     SI
               RET

;----------------------------------------------;
GET_SETUP:     MOV     DI,OFFSET SETUP_CODES   ;Printer setup code storage.
               MOV     BP,10                   ;Ten codes maximum.
NEXT_SETUP2:   CALL    FIND_START              ;Find parameter.
               CMP     AL,CR
               JZ      SETUP_END
               CMP     AL,"/"
               JZ      SETUP_END
               XOR     BL,BL
               MOV     CL,10
NEXT_DECIMAL:  LODSB                           ;Get a character.
               CMP     AL,"/"
               JZ      STORE_SETUP
               CMP     AL,SPACE
               JBE     STORE_SETUP
               SUB     AL,"0"                  ;ASCII to binary.
               JC      SETUP_END               ;If not between 0 and 9, skip.
               CMP     AL,9
               JA      SETUP_END
               XCHG    AL,BL                   ;Swap old and new number.
               MUL     CL                      ; last entry by ten.
               ADD     BL,AL                   ;Add new number and store in BX.
               JMP     NEXT_DECIMAL

STORE_SETUP:   MOV     AL,BL
               STOSB                           ;Store printer code.
               DEC     SI
               DEC     BP
               JNZ     NEXT_SETUP2

SETUP_END:     XOR     AL,AL                   ;NULL terminate.
               STOSB
               RET

;----------------------------------------------;
DISP_HOTKEY:   PUSH    DS
               MOV     DX,OFFSET HOTKEY_MSG    ;Display hotkey message.
               CALL    PRINT_STRING
               MOV     DS,TSR_SEGMENT
               MOV     BL,COMBO
               MOV     DX,OFFSET ALT
               CMP     HOT_SHIFT_KEY,ALT_STATE
               JZ      DISP_STATE
               MOV     DX,OFFSET CTRL
DISP_STATE:    POP     DS
               CALL    PRINT_STRING            ;And current hotkey.
               MOV     DL,BL
               MOV     AH,2
               INT     21H
               MOV     DX,OFFSET HOTKEY_MSG2
               CALL    PRINT_STRING
               RET

;----------------------------------------------;
PRINT_CHAR:    MOV     DL,AL
               MOV     AH,2                    ;Print character via DOS.
               JMP     SHORT DOS_INT

PRINT_STRING:  MOV     AH,9                    ;Print string via DOS.
DOS_INT:       INT     21H
               RET

_TEXT          ENDS
               END     START
