/*
 * bc_init.c
 *
 * Borland C interface, initialisation
 *
 */

#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bc_frotz.h"

#define INFORMATION "\
\n\
FROTZ V2.22 - an interpreter for all Infocom games. Complies with standard\n\
0.2 of Graham Nelson's specification. Written by Stefan Jokisch in 1995-97\n\
\n\
[patch level 1, 20th Feb 1997]\n\
\n\
Syntax: frotz [options] story-file\n\
\n\
  -a   watch attribute setting  \t -l # left margin\n\
  -A   watch attribute testing  \t -o   watch object movement\n\
  -b # background colour        \t -O   watch object locating\n\
  -B # reverse background colour\t -r # right margin\n\
  -c # context lines            \t -s # random number seed value\n\
  -d # display mode (see below) \t -S # transscript width\n\
  -e # emphasis colour [mode 1] \t -t   set Tandy bit\n\
  -f # foreground colour        \t -T   bold typing [modes 2+4+5]\n\
  -F # reverse foreground colour\t -u # slots for multiple undo\n\
  -h # screen height            \t -w # screen width\n\
  -i   ignore runtime errors    \t -x   expand abbreviations g/x/z\n\
\n\
List of display modes: 0 mono, 1 text, 2 cga, 3 mcga, 4 ega, 5 amiga."

extern unsigned cdecl _stklen = 0x600;

#if defined(__SMALL__) || defined (__TINY__)
extern unsigned cdecl _heaplen = 4 * BUFSIZ + 64;
#endif

extern const char *optarg;
extern int optind;

int cdecl getopt (int, char *[], const char *);

extern char script_name[];
extern char command_name[];
extern char save_name[];
extern char auxilary_name[];

int display = -1;

int user_background = -1;
int user_foreground = -1;
int user_emphasis = -1;
int user_bold_typing = -1;
int user_reverse_bg = -1;
int user_reverse_fg = -1;
int user_screen_height = -1;
int user_screen_width = -1;
int user_tandy_bit = -1;
int user_random_seed = -1;

char stripped_story_name[9];

static byte old_video_mode = -1;
static byte new_video_mode = -1;

static void interrupt (*vect) (void) = NULL;

/*
 * dectoi
 *
 * Convert a string containing a decimal number to integer. The string may
 * be NULL, but it must not be empty.
 *
 */

int dectoi (const char *s)
{
    int n = 0;

    if (s != NULL)

	do {

	    n = 10 * n + (*s & 15);

	    s++;

	} while (*s > ' ');

    return n;

}/* dectoi */

/*
 * hextoi
 *
 * Convert a string containing a hex number to integer. The string may be
 * NULL, but it must not be empty.
 *
 */

int hextoi (const char *s)
{
    int n = 0;

    if (s != NULL)

	do {

	    n = 16 * n + (*s & 15);

	    if (*s > '9')
		n += 9;

	    s++;

	} while (*s > ' ');

    return n;

}/* hextoi */

/*
 * cleanup
 *
 * Shut down the IO interface: free memory, close files, restore
 * interrupt pointers and return to the previous video mode.
 *
 */

static void cleanup (void)
{

    reset_sound ();
    reset_pictures ();

    asm mov ah,0
    asm mov al,old_video_mode
    asm int 0x10

    setvect (0x1b, vect);

}/* cleanup */

/*
 * fast_exit
 *
 * Interrupt handler to be called when the crtl-break key is pressed.
 * ...I still haven't found out why this won't work under Win95.
 *
 */

static void interrupt fast_exit (void)
{

    cleanup ();

    exit (EXIT_FAILURE);

}/* fast_exit */

/*
 * os_fatal
 *
 * Display error message and exit program.
 *
 */

void os_fatal (const char *s)
{

    /* Reset screen */

    if (h_interpreter_number)
	os_reset_screen ();

    /* Display error message */

    fputs ("\nFatal error: ", stderr);
    fputs (s, stderr);
    fputs ("\n", stderr);

    /* Abort program */

    exit (EXIT_FAILURE);

}/* os_fatal */

/*
 * os_process_arguments
 *
 * Handle command line switches. Some variables may be set to activate
 * special features of Frotz:
 *
 *     option_attribute_assignment
 *     option_attribute_testing
 *     option_context_lines
 *     option_object_locating
 *     option_object_movement
 *     option_left_margin
 *     option_right_margin
 *     option_ignore_errors
 *     option_piracy
 *     option_undo_slots
 *     option_expand_abbreviations
 *     option_script_cols
 *
 * The global pointer "story_name" is set to the story file name.
 *
 */

void os_process_arguments (int argc, char *argv[])
{
    char *p;
    int i;
    int c;

    /* Parse the options */

    do {

	int num = 0;

	c = getopt (argc, argv, "aAb:B:c:d:e:f:F:h:il:oOpr:s:S:tTu:w:x");

	if (optarg != NULL)
	    num = dectoi (optarg);

	if (c == 'a')
	    option_attribute_assignment = 1;
	if (c == 'A')
	    option_attribute_testing = 1;
	if (c == 'b')
	    user_background = num;
	if (c == 'B')
	    user_reverse_bg = num;
	if (c == 'c')
	    option_context_lines = num;
	if (c == 'd')
	    display = num;
	if (c == 'e')
	    user_emphasis = num;
	if (c == 'T')
	    user_bold_typing = 1;
	if (c == 'f')
	    user_foreground = num;
	if (c == 'F')
	    user_reverse_fg = num;
	if (c == 'h')
	    user_screen_height = num;
	if (c == 'i')
	    option_ignore_errors = 1;
	if (c == 'l')
	    option_left_margin = num;
	if (c == 'o')
	    option_object_movement = 1;
	if (c == 'O')
	    option_object_locating = 1;
	if (c == 'p')
	    option_piracy = 1;
	if (c == 'r')
	    option_right_margin = num;
	if (c == 's')
	    user_random_seed = num;
	if (c == 'S')
	    option_script_cols = num;
	if (c == 't')
	    user_tandy_bit = 1;
	if (c == 'u')
	    option_undo_slots = num;
	if (c == 'w')
	    user_screen_width = num;
	if (c == 'x')
	    option_expand_abbreviations = 1;

    } while (c != EOF);

    /* Display usage and exit */

    if (optind != argc - 1) {
	puts (INFORMATION);
	exit (EXIT_FAILURE);
    }

    /* Set the story file name */

    story_name = argv[optind];

    /* Strip path and extension off the story file name */

    p = story_name;

    for (i = 0; story_name[i]; i++)
	if (story_name[i] == '\\' || story_name[i] == ':')
	    p = story_name + i + 1;

    for (i = 0; p[i] != 0 && p[i] != '.' && i < 8; i++)
	stripped_story_name[i] = p[i];

    stripped_story_name[i] = 0;

    /* Create nice default file names */

    strcpy (script_name, stripped_story_name);
    strcpy (command_name, stripped_story_name);
    strcpy (save_name, stripped_story_name);
    strcpy (auxilary_name, stripped_story_name);

    strcat (script_name, ".scr");
    strcat (command_name, ".rec");
    strcat (save_name, ".sav");
    strcat (auxilary_name, ".aux");

}/* os_process_arguments */

/*
 * os_init_screen
 *
 * Initialise the IO interface. Prepare the screen and other devices
 * (mouse, sound board). Set various OS depending story file header
 * entries:
 *
 *     h_config (aka flags 1)
 *     h_flags (aka flags 2)
 *     h_screen_cols (aka screen width in characters)
 *     h_screen_rows (aka screen height in lines)
 *     h_screen_width
 *     h_screen_height
 *     h_font_height (defaults to 1)
 *     h_font_width (defaults to 1)
 *     h_default_foreground
 *     h_default_background
 *     h_interpreter_number
 *     h_interpreter_version
 *     h_user_name (optional; not used by any game)
 *
 * Finally, set reserve_mem to the amount of memory (in bytes) that
 * should not be used for multiple undo and reserved for later use.
 *
 */

void os_init_screen (void)
{
    static byte video_mode[] = {
	0x07, 0x03, 0x06, 0x13, 0x0e, 0x12
    };

    static byte zcolour[] = {
	BLACK_COLOUR,
	BLUE_COLOUR,
	GREEN_COLOUR,
	CYAN_COLOUR,
	RED_COLOUR,
	MAGENTA_COLOUR,
	BROWN + 16,
	LIGHTGRAY + 16,
	GREY_COLOUR,
	LIGHTBLUE + 16,
	LIGHTGREEN + 16,
	LIGHTCYAN + 16,
	LIGHTRED + 16,
	LIGHTMAGENTA + 16,
	YELLOW_COLOUR,
	WHITE_COLOUR
    };

    /* Get the current video mode. This video mode will be selected
       when the program terminates. It's also useful to auto-detect
       monochrome boards. */

    asm mov ah,15
    asm int 0x10
    asm mov old_video_mode,al

    /* If the display mode has not already been set by the user then see
       if this is a monochrome board. If so, set the display mode to 0.
       Otherwise check the graphics flag of the story. Select a graphic
       mode if it is set or if this is a V6 game. Select text mode if it
       is not. */

    if (display == -1)

	if (old_video_mode == 7)
	    display = _MONO_;
	else if (h_version == V6 || (h_flags & GRAPHICS_FLAG))
	    display = _AMIGA_;
	else
	    display = _TEXT_;

    /* Activate and prepare the desired display mode */

    new_video_mode = video_mode[display];

    asm mov ah,0
    asm mov al,new_video_mode
    asm int 0x10

    if (display <= _TEXT_) {

	/* Enable bright background colours */

	asm mov ax,0x1003
	asm mov bl,0
	asm int 0x10

	/* Turn off hardware cursor */

	asm mov ah,1
	asm mov cx,0xffff
	asm int 0x10

    } else if (display == _AMIGA_) {

	scaler = 2;

	/* Prepare palette registers. (Note: VGA colours are actually
	   indices into a table of palette registers. However, these
	   palette registers are once again just indices into another
	   table which finally holds R-G-B values. We set the palette
	   register #i to the value i such that VGA colours directly
	   relate to the table of DAC registers.) */

	if (h_version == V6) {

	    asm mov bl,15
	loop:
	    asm mov ax,0x1000
	    asm mov bh,bl
	    asm int 0x10
	    asm dec bl
	    asm ja loop

	}

	/* Use resolution 640 x 400 instead of 640 x 480. BIOS doesn't
	   help us here since this is not a standard resolution. */

	outportb (0x03c2, 0x63);

	outport (0x03d4, 0x0e11);
	outport (0x03d4, 0xbf06);
	outport (0x03d4, 0x1f07);
	outport (0x03d4, 0x9c10);
	outport (0x03d4, 0x8f12);
	outport (0x03d4, 0x9615);
	outport (0x03d4, 0xb916);

    }

    /* Set the amount of memory to reserve for later use. It takes
       some memory to open command, script and game files. If Frotz
       is compiled in a small memory model then memory for opening
       files is allocated on the "near heap" while other allocations
       are made on the "far heap", i.e. we need not reserve memory
       in this case. More memory might be reserved by init_sound or
       init_pictures. */

#if !defined(__SMALL__) && !defined (__TINY__)
    reserve_mem = 3 * BUFSIZ + 48;
#endif

    /* Set various bits in the configuration byte. These bits tell
       the game which features are supported by the interpreter. */

    if (h_version == V3 && user_tandy_bit != -1)
	h_config |= CONFIG_TANDY;

    if (h_version == V3)
	h_config |= CONFIG_SPLITSCREEN;

    if (h_version == V3 && display == _MCGA_)
	h_config |= CONFIG_PROPORTIONAL;

    if (h_version >= V4 && display != _MCGA_ && (user_bold_typing != -1 || display <= _TEXT_))
	h_config |= CONFIG_BOLDFACE;

    if (h_version >= V4)
	h_config |= CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMEDINPUT;

    if (h_version >= V5 && display != _MONO_ && display != _CGA_)
	h_config |= CONFIG_COLOUR;

    if (h_version == V6 && display >= _CGA_ && init_pictures ())
	h_config |= CONFIG_PICTURES;

    /* Handle various game flags. These flags are set if the game wants
       to use certain features. The flags must be cleared if the feature
       is not available. */

    if (h_flags & GRAPHICS_FLAG)
	if (display <= _TEXT_)
	    h_flags &= ~GRAPHICS_FLAG;

    if (h_version == V3 && (h_flags & OLD_SOUND_FLAG))
	if (!init_sound ())
	    h_flags &= ~OLD_SOUND_FLAG;

    if (h_flags & SOUND_FLAG)
	if (!init_sound ())
	    h_flags &= ~SOUND_FLAG;

    if (h_version >= V5 && (h_flags & UNDO_FLAG))
	if (!option_undo_slots)
	    h_flags &= ~UNDO_FLAG;

    if (h_flags & MOUSE_FLAG)
	if (!detect_mouse ())
	    h_flags &= ~MOUSE_FLAG;

    if (h_flags & COLOUR_FLAG)
	if (display == _MONO_ || display == _CGA_)
	    h_flags &= ~COLOUR_FLAG;

    h_flags &= ~MENU_FLAG;

    /* Set the screen dimensions and the font size */

    if (display <= _TEXT_) {
	h_screen_width = 80;
	h_screen_height = 25;
	h_font_height = 1;
	h_font_width = 1;
    } else if (display == _MCGA_) {
	h_screen_width = 320;
	h_screen_height = 200;
	h_font_height = 8;
	h_font_width = 5;
    } else if (display <= _EGA_) {
	h_screen_width = 640;
	h_screen_height = 200;
	h_font_height = 8;
	h_font_width = 8;
    } else { /* Amiga mode */
	h_screen_width = 640;
	h_screen_height = 400;
	h_font_height = 16;
	h_font_width = 8;
    }

    if (user_screen_width != -1)
	h_screen_width = user_screen_width;
    if (user_screen_height != -1)
	h_screen_height = user_screen_height;

    h_screen_rows = h_screen_height / h_font_height;
    h_screen_cols = h_screen_width / h_font_width;

    /* Set the default text colours */

    h_default_foreground = WHITE_COLOUR;
    h_default_background = BLACK_COLOUR;

    if (display == _TEXT_)
	h_default_foreground = LIGHTGRAY + 16;
    if (display == _TEXT_ || display == _EGA_)
	h_default_background = BLUE_COLOUR;
    if (display == _MCGA_)
	h_default_background = GREY_COLOUR;

    if (display == _AMIGA_ && h_version == V6) {

	user_reverse_fg = -1;
	user_reverse_bg = -1;

	zcolour[LIGHTGRAY] = LIGHTGREY_COLOUR;
	zcolour[DARKGRAY] = DARKGREY_COLOUR;

    }

    if (user_foreground != -1)
	h_default_foreground = zcolour[user_foreground];
    if (user_background != -1)
	h_default_background = zcolour[user_background];

    /* Set the interpreter number (a constant telling the game which
       operating system it runs on) and the interpreter version. The
       interpreter number has effect on V6 games and "Beyond Zork". */

    h_interpreter_number = INTERP_MSDOS;
    h_interpreter_version = 'E';

    if (display == _AMIGA_)
	h_interpreter_number = INTERP_AMIGA;

    /* Frotz doesn't like the way DOS handles the crtl-break key,
       therefore we install our own routine. BIOS calls interrupt
       0x1b whenever the ctrl-break key is pressed. */

    vect = getvect (0x1b);

    setvect (0x1b, fast_exit);

}/* os_init_screen */

/*
 * os_reset_screen
 *
 * Reset the screen before the program stops.
 *
 */

void os_reset_screen (void)
{

    os_set_font (TEXT_FONT);
    os_set_text_style (0);
    os_display_string ("[Hit any key to exit.]");
    os_read_key (0);

    cleanup ();

}/* os_reset_screen */

/*
 * os_random_seed
 *
 * Return an appropriate random seed value in the range from 0 to
 * 32767, possibly by using the current system time.
 *
 */

int os_random_seed (void)
{

    if (user_random_seed == -1) {

	/* Use the time of day as seed value */

	asm mov ah,0
	asm int 0x1a

	return _DX & 0x7fff;

    } else return user_random_seed;

}/* os_random_seed */
