/*

Copyright (C) 2015-2018 Night Dive Studios, LLC.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/
/*
 * $Source: r:/prj/cit/src/RCS/status.c $
 * $Revision: 1.77 $
 * $Author: kevin $
 * $Date: 1994/11/29 11:50:25 $
 */

// Source code for handling the status indicators for the
// Citadel game screen
//
// Source is divided into two segments; first comes all the
// biorhythm stuff (upper left hand corner of the game screen)
// and second, the upper right hand healthy/suit-energy measuring
// bar graphs

#include <math.h>

#include "status.h"
#include "player.h"
#include "tools.h"
#include "gamescr.h"
#include "faketime.h"
#include "fullscrn.h"
#include "gamesys.h"
#include "physics.h"
#include "objsim.h"
#include "biotrax.h"
#include "otrip.h"

#include "curdat.h"

#include "gr2ss.h"

// Defines
#define NUM_BIO_TRACKS 8

#define GAMESCR_BIO        0
#define GAMESCR_BIO_X      5
#define GAMESCR_BIO_Y      1
//#define GAMESCR_BIO_WIDTH  149
#define GAMESCR_BIO_WIDTH  131
#define GAMESCR_BIO_HEIGHT 17

#define DIFF_BIO        1
#define DIFF_BIO_X      6
#define DIFF_BIO_Y      181
#define DIFF_BIO_WIDTH  307
#define DIFF_BIO_HEIGHT 17

#define STATUS_CHI_AMP  8

#define STATUS_BIO_X      curr_bio_x
#define STATUS_BIO_Y      curr_bio_y
#define STATUS_BIO_WIDTH  curr_bio_w
#define STATUS_BIO_HEIGHT curr_bio_h

#define STATUS_START_OFFSET 0
#define STATUS_BIO_Y_DELTA  1
#define MAX_BIO_LENGTH      307
#define STATUS_BIO_LENGTH   (STATUS_BIO_WIDTH - STATUS_START_OFFSET)
#define STATUS_BIO_TAIL     30
#define STATUS_BIO_PEAK     (STATUS_BIO_HEIGHT - 3) // 3 because of zany art size
#define STATUS_BIO_X_BASE   (STATUS_BIO_X + STATUS_START_OFFSET)
#define STATUS_BIO_Y_BASE   (STATUS_BIO_Y + STATUS_BIO_HEIGHT - STATUS_BIO_Y_DELTA - 2)

#define SPIKE_THRESHOLD 4
#define COLOR_CHANGES   6
#define COLOR_LENGTH    (STATUS_BIO_TAIL / COLOR_CHANGES)

#define MAX_TAIL_LENGTH 5
#define NO_HEIGHT       0x1f
#define INVALID_HEIGHT  0xE0

#define COLOR_BIO_MASK  0xE0 // Top Three bits signify depth of color
#define HEIGHT_BIO_MASK 0x1f // Bottom Five bits signify height
#define BIT6            0x20 // First bit of the color field.

#define COLOR_BIT_SHIFT(x) ((x) << 5)

//#define FIND_OVERLAP(x,y) (((x) - y + STATUS_BIO_LENGTH) % STATUS_BIO_LENGTH)

#define STATUS_VITALS_X      184
#define STATUS_VITALS_Y      0
#define STATUS_VITALS_WIDTH  130
#define STATUS_VITALS_HEIGHT 17

#define STATUS_VITALS_X_BASE   (STATUS_VITALS_X + 4)
#define STATUS_VITALS_Y_TOP    (STATUS_VITALS_Y + 1)
#define STATUS_VITALS_Y_BOTTOM (STATUS_VITALS_Y + 11)
#define STATUS_VITALS_H        8
#define STATUS_VITALS_W        (STATUS_VITALS_WIDTH - 9)

#define STATUS_X      4
#define STATUS_Y      1
#define STATUS_HEIGHT 20
#define STATUS_WIDTH  312

#define GAMESCR_BIO_REF      REF_IMG_bmBiorhythm
#define DIFF_BIO_REF         REF_IMG_bmDiffBio
#define STATUS_RESID         curr_bio_ref
#define STATUS_RES_VITALSID  REF_IMG_bmVitals
#define STATUS_RES_HEALTH_ID REF_IMG_bmVitalInnardsTop
#define STATUS_RES_ENERGY_ID REF_IMG_bmVitalInnardsBottom

// Special Status Biorhythm variables

#define NO_SPIKE    0x01
#define SPIKE_NOISE 0x02

// Look ma, a new biorhythm option.
#define BOTTOMLESS 0x04 // things that fall off the bottom don't get drawn.

// For now, turn these on so it won't try to draw any biorhythm stuff
#define TIMING_PROCEDURES_OFF
//#define TIMING_CALLBACK_OFF

// Struct to contain information about tail

typedef struct {
    uchar free;
    int *data;
    uchar height[MAX_BIO_LENGTH];
    int head;
    uchar tail;
    uchar tail_length;
    uchar color_length;
    int update_time;
    int counter;
    int max_value;
    uchar special;
    uchar active;
} bio_data_block;

uchar gBioInited = FALSE;

uchar status_background[(DIFF_BIO_WIDTH + 4) * (DIFF_BIO_HEIGHT + 2)];
// uchar status_background[(266+4)*(44+2)];
uchar bio_data_buffer[NUM_BIO_TRACKS * sizeof(bio_data_block)];

#define FRAME_RATE_SCALE 20
#define LOOPLINE_SCALE 0x2F

bio_data_block *bio_data;

int bio_time_id;

int curr_bio_x, curr_bio_y;
int curr_bio_w, curr_bio_h;
short curr_bio_mode;
Ref curr_bio_ref;

static uchar track_colors[NUM_BIO_TRACKS] = {
    0x66, // turquoise
    0x57, // yellow green?
    0x73, // blue
    0x40, // orange
    0x4A, // yellow
    0x28, // gold
    0x33, // red
    0x20, // purple
};

/* bio keeps private canvas so we don't have to save/restore stuff from
   the real screen canvas. */
static grs_canvas bio_canvas;

#ifdef SYNCH_BIORHYTHMS
grs_bitmap bio_bitmap;

#define BIO_BITMAP_SIZE ((GAMESCR_BIO_X + GAMESCR_BIO_WIDTH) * (GAMESCR_BIO_Y + GAMESCR_BIO_HEIGHT))
uchar bio_bitmap_bits[BIO_BITMAP_SIZE];
#endif

extern LGPoint LastCursorPos;

// Internal Prototype

uchar under_bio(int x);
void ss_save_under_set_pixel(int color, short i, short j);
void bio_set_pixel(int color, short x, short y);
void bio_restore_pixel(grs_bitmap *bmp, short x, short y);
void bio_vline(int color, int x, int y, int y1);
uchar status_track_free(int track);
uchar status_track_active(int track);
void status_track_activate(int track, uchar active);
void draw_lower_tracks(int track_number, int location);
void draw_one_location_tracks(int location);
void draw_bio_height(int track_number, int draw_location);
errtype clear_bio_tracks(void);
void clear_tail(int track_number, int delete_location);
int FIND_OVERLAP(int x, int y);

void change_bio_vars(void);

void (*bio_funcs[])(void) = {gamescr_bio_func, diff_bio_func};

uchar under_bio(int x) {
    if (((x >= 16) && (x <= 22)) || ((x >= 69) && (x <= 74)) || ((x >= 114) && (x <= 208)) ||
        ((x >= 252) && (x <= 258)) || ((x >= 298) && (x <= 303)))
        return (TRUE);
    return (FALSE);
}

// ---------------------------------------------------------
// bio_set_pixel(int color, int x, int y)
//
// calls gr_set_pixel as normal, unless the biorhythms is
// going "under" something

void ss_save_under_set_pixel(int color, short i, short j) {
    extern LGPoint LastCursorPos;

    if (LastCursor != NULL) {
        if ((LastCursorPos.x + SaveUnder.bm.w > i) && (LastCursorPos.x <= i)) {
            if ((LastCursorPos.y + SaveUnder.bm.h > j) && (LastCursorPos.y <= j)) {
                grs_bitmap *bm = (grs_bitmap *)(LastCursor->state);
                int k = (i - LastCursorPos.x) + SaveUnder.bm.row * (j - LastCursorPos.y);
                SaveUnder.bm.bits[k] = color;
                k = (i - LastCursorPos.x) + bm->row * (j - LastCursorPos.y);
                if (bm->bits[k]) {
                    return;
                }
            }
        }
    }
    gr_set_pixel(color, i, j);
    //   mprintf("x:%i, y:%i, w:%i, h:%i\n",LastCursorPos.x,LastCursorPos.y,SaveUnder.bm.w,SaveUnder.bm.h);
}

void bio_set_pixel(int color, short x, short y) {
    short x0, x1, y0, y1, i, j;

    if ((curr_bio_mode == DIFF_BIO) && (under_bio(x)))
        return;
    if (convert_use_mode) {
        x0 = SCONV_X(x);
        y0 = SCONV_Y(y);
        x1 = SCONV_X(x + 1);
        y1 = SCONV_Y(y + 1);
        for (i = x0; i < x1; i++)
            for (j = y0; j < y1; j++)
                ss_save_under_set_pixel(color, i, j);
    } else
        ss_save_under_set_pixel(color, x, y);
}

// ---------------------------------------------------------
//  Restore the pixels from the offscreen background bitmap.
// ---------------------------------------------------------
void bio_restore_pixel(grs_bitmap *bmp, short x, short y) {
    if ((curr_bio_mode == DIFF_BIO) && (under_bio(x)))
        return;

    short a, b, c, d;
    STORE_CLIP(a, b, c, d);
    ss_cset_cliprect(&bio_canvas, x, y, x + 1, y + 1);
    ss_bitmap(bmp, STATUS_BIO_X, STATUS_BIO_Y);
    RESTORE_CLIP(a, b, c, d);
}

// ---------------------------------------------------------
// bio_vline(int x, int y, int y1)
//
// calls bio_vline as normal, unless the biorhythms is
// going "under" something

void bio_vline(int color, int x, int y, int y1) {
    if ((curr_bio_mode == DIFF_BIO) && (under_bio(x)))
        return;
#ifdef SVGA_SUPPORT
    if (convert_use_mode != 0) {
        short x0, x1, i, j;
        x0 = SCONV_X(x);
        x1 = SCONV_X(x + 1);
        y = SCONV_Y(y);
        y1 = SCONV_Y(y1);
        if (y > y1) {
            int foo = y;
            y = y1;
            y1 = foo;
        }
        for (i = x0; i < x1; i++)
            for (j = y; j <= y1; j++)
                ss_save_under_set_pixel(color, i, j);
    } else
#endif
    {
        if (y > y1) {
            int foo = y;
            y = y1;
            y1 = foo;
        }
        for (; y <= y1; y++)
            ss_save_under_set_pixel(color, x, y);
    }
}

// ---------------------------------------------------------
// status_bio_set(short bio_mode)
//
// Tell the biorhythm code which screen we are on

short bios_x[2] = {GAMESCR_BIO_X, DIFF_BIO_X};
short bios_y[2] = {GAMESCR_BIO_Y, DIFF_BIO_Y};
short bios_w[2] = {GAMESCR_BIO_WIDTH, DIFF_BIO_WIDTH};
short bios_h[2] = {GAMESCR_BIO_HEIGHT, DIFF_BIO_HEIGHT};
Ref bio_refs[2] = {GAMESCR_BIO_REF, DIFF_BIO_REF};

grs_bitmap bio_background_bitmap;

void status_bio_set(short bio_mode) {
    FrameDesc *f;
    int i;
#ifdef SVGA_SUPPORT
    uchar old_over = gr2ss_override;
#endif

    curr_bio_mode = bio_mode;

    // Assuming there are only 8 bio tracks!!!!!
    /* set biorhythm positions & clipping rectangle. */
    curr_bio_x = bios_x[bio_mode];
    curr_bio_y = bios_y[bio_mode];
    curr_bio_w = bios_w[bio_mode];
    curr_bio_h = bios_h[bio_mode];
#ifdef SVGA_SUPPORT
    gr2ss_override = OVERRIDE_ALL;
#endif
    ss_cset_cliprect(&bio_canvas, STATUS_BIO_X, STATUS_BIO_Y, STATUS_BIO_X + STATUS_BIO_WIDTH,
                     STATUS_BIO_Y + STATUS_BIO_HEIGHT);
#ifdef SVGA_SUPPORT
    gr2ss_override = old_over;
#endif

    curr_bio_ref = bio_refs[bio_mode];
    f = RefLock(STATUS_RESID);
    bio_background_bitmap = f->bm;

    // let's try to do the right thing!
    bio_background_bitmap.bits = status_background;

    LG_memcpy(bio_background_bitmap.bits, (char *)(f + 1), sizeof(char) * f->bm.w * f->bm.h);

    bio_data = (bio_data_block *)bio_data_buffer;
    for (i = 0; i < NUM_BIO_TRACKS; i++)
        bio_data[i].free = TRUE;

    RefUnlock(STATUS_RESID);
    bio_funcs[curr_bio_mode]();
}

// ---------------------------------------------------------
// status_bio_init()
//
// Do the init stuff for the biorhythm. 2d should be initialized and
// screen canvas should be unchanged when this is called.

void status_bio_update_screenmode() { bio_canvas = *grd_screen_canvas; /* make copy for int routine */ }

void status_bio_init(void) {
    status_bio_update_screenmode();

#ifndef TIMING_PROCEDURES_OFF
    bio_time_id = tm_add_process((void (*)())status_bio_update, 0, TMD_FREQ / 140);
#endif
}

void status_bio_start(void) {
    if (!full_game_3d)
        gBioInited = TRUE;

#ifndef TIMING_PROCEDURES_OFF
    tm_activate_process(bio_time_id);
#endif
}

void status_bio_end(void) {
    gBioInited = FALSE;
//   Free(bio_background_bitmap.bits);
#ifndef TIMING_PROCEDURES_OFF
    tm_deactivate_process(bio_time_id);
#endif
}

// -----------------------------------------------------------
// status_bio_draw()
//
// Draw the biorhythm background

void status_bio_draw(void) {
    int i;

    // Draw the background map
    ss_bitmap(&bio_background_bitmap, STATUS_BIO_X, STATUS_BIO_Y);
    // gr_bitmap(&bio_background_bitmap, SCONV_X(STATUS_BIO_X), SCONV_Y(STATUS_BIO_Y));

    // Go from left to right and draw all the tracks
    for (i = 0; i < STATUS_BIO_LENGTH; i++)
        draw_one_location_tracks(i);
}

// -----------------------------------------------
// Accessors for the "active" field.

uchar status_track_free(int track) { return bio_data[track].free; }

uchar status_track_active(int track) { return bio_data[track].active; }

void status_track_activate(int track, uchar active) { bio_data[track].active = active; }

//
// ----------------------------------------------------------------------
// status_bio_add()
//
// *var           - address of variable to be tracked
// max_value      - scale factor for value of variable
// update_time    - "bio time blocks" between update (can be any value)
// track_number   - track which this biorhythm should take (0 to NUM_BIO_TRACKS-1)
// tail_length    - length of tail (value * STATUS_BIO_TAIL)
// special        - special characteristics of this biorhythm
//
// Add a variable to be tracked by the biorhythm monitor.
// Track the NULL pointer to clear out a track slot.
//

errtype status_bio_add(int *var, int max_value, int update_time, int track_number, int tail_length, uchar special) {
    bio_data_block *new_block;
    uchar value;
    int var_value;

    if (var == NULL) // are we trying to clear out a track slot??
    {
        if (bio_data[track_number].free == TRUE)
            return (ERR_NOEFFECT);
        else {
            bio_data[track_number].free = TRUE;
            bio_data[track_number].active = FALSE;
            return (OK);
        }
    }

    // Make sure we have a valid entry
    if ((bio_data[track_number].free == FALSE) || // unused track?
        (track_number >= NUM_BIO_TRACKS) ||       // correct track number?
        (tail_length < 1) ||                      // non-negative tail length?
        (tail_length > MAX_TAIL_LENGTH))          // too long of a tail?
        return (ERR_NOEFFECT);
    else {
        // Initialize the new biorhythm

        new_block = bio_data + track_number;
        new_block->free = FALSE;
        new_block->data = var;
        LG_memset(&(new_block->height), INVALID_HEIGHT, sizeof(uchar) * MAX_BIO_LENGTH);

        // We must first check if the variable is greater than max value, if so make it max_value
        var_value = (*var > max_value) ? max_value : *var;

        // Then check that it's not below 0
        if (var_value < 0)
            var_value = 0;

        value = (var_value * STATUS_BIO_PEAK) / max_value;

        // Set the end of the bio line to be the same as the initial
        // This way - we don't have to check if we've started a tail
        // when we erase the first value.
        new_block->height[STATUS_BIO_LENGTH - 1] = value | INVALID_HEIGHT;

        // Initialize the struct
        new_block->head = 0; // head represents the first "new" x location
        new_block->tail = FALSE;
        new_block->tail_length = tail_length * STATUS_BIO_TAIL;
        new_block->color_length = tail_length * COLOR_LENGTH;
        new_block->update_time = update_time;
        new_block->counter = 0;
        new_block->max_value = max_value;
        new_block->special = special;
        new_block->active = TRUE;

        return (OK);
    }
}

errtype clear_bio_tracks() {
    int i;
    for (i = 0; i < NUM_BIO_TRACKS; i++)
        status_bio_add(NULL, 0, 0, i, 0, 0);
    return (OK);
}


//#define FIND_OVERLAP(x,y) (((x) - y + STATUS_BIO_LENGTH) % STATUS_BIO_LENGTH)
int FIND_OVERLAP(int x, int y) { return (((x)-y + STATUS_BIO_LENGTH) % STATUS_BIO_LENGTH); }

#ifdef SYNCH_BIORHYTHMS
#ifdef SVGA_SUPPORT
// ------------------------------------------------------------------------
// status_bio_synchronous()
//
// Does the synchronous blitting of biorhythm canvas
void status_bio_synchronous() {
    if (!convert_use_mode)
        return;
    gr_push_canvas(grd_screen_canvas);
    ss_scale_bitmap(&bio_bitmap, 0, 0, GAMESCR_BIO_WIDTH, GAMESCR_BIO_HEIGHT);
    gr_pop_canvas();
}
#endif
#endif

// ------------------------------------------------------------------------
// status_bio_update()
//
// Draw in the biorhythm stuff
// Does clever incremental redraw (but not yet)

void status_bio_update(void) {
#ifndef TIMING_CALLBACK_OFF
    uchar color;
    int i;
    int j;
    bio_data_block *curr_blk;
    int the_head;
    long color_base;
    int draw_location;
    int var_value;
    static grs_canvas *old_canvas;

    if (!gBioInited)
        return;

    MouseLock++;
    if (MouseLock > 1) {
        MouseLock--;
        return;
    }
    change_bio_vars();
    old_canvas = grd_canvas;
    gr_set_canvas(&bio_canvas);
    curr_blk = bio_data;

#ifdef SVGA_SUPPORT
    gr_push_state();
#endif
    for (i = 0; i < NUM_BIO_TRACKS; i++, curr_blk++) {
        if (curr_blk->free == FALSE) {
            // We must check to see if this track should be drawn now,
            // or must it wait until it's time
            if (curr_blk->counter < curr_blk->update_time) {
                curr_blk->counter++;
            } else {
                curr_blk->counter = 0;
                color_base = track_colors[i]; // Prevent need to use index every time
                the_head = curr_blk->head;    // Prevent need to look inside data structure every time

                // We must first check if the variable is greater than max value, if so make it max_value
                var_value = (*(curr_blk->data) > curr_blk->max_value) ? curr_blk->max_value : (*(curr_blk->data));

                // Then check that it's not below 0
                curr_blk->height[the_head] = (var_value <= 0) ? 0 : (var_value * STATUS_BIO_PEAK) / curr_blk->max_value;

                if (curr_blk->special & BOTTOMLESS && curr_blk->height[the_head] == 0)
                    curr_blk->height[the_head] = INVALID_HEIGHT;

                // draw the head
                draw_lower_tracks(i, the_head);

                // Draw the first trailer pixel - We know that this pixel must exist because we drew the first pixel
                // when we started the biorhythm

                draw_location = FIND_OVERLAP(the_head, 1);
                curr_blk->height[draw_location] |= COLOR_BIT_SHIFT(1); // shift color
                draw_lower_tracks(i, draw_location);

                if (curr_blk->tail == FALSE) {
                    // Since we don't have a tail -
                    // we don't know how long the biorhythm is
                    // Let's find out what we have to dim!!!!

                    for (j = 0, draw_location = the_head - curr_blk->color_length;
                         j < the_head / curr_blk->color_length; j++, draw_location -= curr_blk->color_length) {
                        color = curr_blk->height[draw_location] & COLOR_BIO_MASK;
                        if ((color != COLOR_BIT_SHIFT(j + 1)) || (rand() > (RAND_MAX / 2))) {
                            curr_blk->height[draw_location] &= HEIGHT_BIO_MASK;
                            curr_blk->height[draw_location] |= COLOR_BIT_SHIFT(j + 2);
                            draw_lower_tracks(i, draw_location);
                        }
                    }

                    // Have we gotten to the point where we can see the end of the tail????

                    if (the_head == curr_blk->tail_length) {
                        curr_blk->tail = TRUE; // start the tail
                        clear_tail(i, 0);      // clear the first spot
                    }
                } else {
                    // Since we have a tail - we know that we will have three spots to dim and one
                    // to delete from the biorhythm - let's dim the three spots first.

                    for (j = 0, draw_location = FIND_OVERLAP(the_head, curr_blk->color_length); j < (COLOR_CHANGES - 1);
                         j++, draw_location = FIND_OVERLAP(draw_location, curr_blk->color_length)) {
                        color = curr_blk->height[draw_location] & COLOR_BIO_MASK;
                        if ((color != COLOR_BIT_SHIFT(j + 1)) || (rand() > (RAND_MAX / 2))) {
                            curr_blk->height[draw_location] &= HEIGHT_BIO_MASK;
                            curr_blk->height[draw_location] |= COLOR_BIT_SHIFT(j + 2);
                            draw_lower_tracks(i, draw_location);
                        }
                    }

                    // Let's delete the end of the tail
                    clear_tail(i, draw_location);
                }

                // Advance the head
                curr_blk->head = (curr_blk->head + 1) % STATUS_BIO_LENGTH;
            }
        }
    }

#ifdef SVGA_SUPPORT
    gr_pop_state();
#endif
    gr_set_canvas(old_canvas);
    MouseLock--;
#endif
}

// ---------------------------------------------------------------------------------------
// draw_bio_height()
//
// Note: requires bio_mouse_rect's x coordinates have already
// been set. We do this since we only draw_one_location_tracks
// calls this function.

void draw_bio_height(int track_number, int draw_location) {
    bio_data_block *curr_blk;
    long color;
    int x;
    int y;
    int y1;
    uchar height;
    int prevHeight;
    int prevSpike;

    curr_blk = bio_data + track_number;

    // Extract the raw "height" information
    height = curr_blk->height[draw_location];

    if ((height & HEIGHT_BIO_MASK) == 0 && (curr_blk->special & BOTTOMLESS))
        return;

    // If we have an invalid height, continue no further
    if ((height & INVALID_HEIGHT) == INVALID_HEIGHT)
        return;

    // Extract the color offset
    color = track_colors[track_number] + ((height & COLOR_BIO_MASK) >> 5);

    // Extract the actual height
    height &= HEIGHT_BIO_MASK;

    // Get the previous height - to check for a spike
    prevHeight = prevSpike = curr_blk->height[FIND_OVERLAP(draw_location, 1)];
    prevHeight &= HEIGHT_BIO_MASK;

    x = STATUS_BIO_X_BASE + draw_location;
    y = STATUS_BIO_Y_BASE - height;

    if ((abs(height - prevHeight) < SPIKE_THRESHOLD) || (curr_blk->special & NO_SPIKE) ||
        ((prevSpike & INVALID_HEIGHT) == INVALID_HEIGHT)) {
        bio_set_pixel(color, x, y);
    } else {
        y1 = STATUS_BIO_Y_BASE - prevHeight;

        bio_vline(color, x, y, y1);
    }
}

// ----------------------------------------------------------
//  clear_tail()
//
//  KLC - greatly simplified this for Mac version, where the offscreen background
//  bitmap is the same size as onscreen.  When clearing, we simply copy the back-
//  ground bitmap directly back onto the screen.  bio_restore_pixel() handles
//  figuring out where to restore from.
// ----------------------------------------------------------
void clear_tail(int track_number, int delete_location) {
    bio_data_block *curr_blk;
    int prevHeight;
    int prevLocation;
    int height;
    int x, y, y1;
    int i, delta, base;

    curr_blk = bio_data + track_number;
    prevLocation = FIND_OVERLAP(delete_location, 1);

    prevHeight = curr_blk->height[prevLocation] & HEIGHT_BIO_MASK;
    height = curr_blk->height[delete_location] & HEIGHT_BIO_MASK;

    x = STATUS_BIO_X_BASE + delete_location;
    y = STATUS_BIO_Y_BASE - height;

    // First, check to see if we're restoring for a v-line spike.
    delta = abs(height - prevHeight);
    if ((delta >= SPIKE_THRESHOLD) && !((delete_location == 0) && (curr_blk->tail == FALSE))) {
        y1 = STATUS_BIO_Y_BASE - prevHeight;
        if (y > y1)
            base = y1;
        else
            base = y;
        for (i = 0; i < delta + 1; i++) {
            bio_restore_pixel(&bio_background_bitmap, x, base + i);
        }
    } else {
        bio_restore_pixel(&bio_background_bitmap, x, y);
    }

    // Invalidate the height at the delete location
    curr_blk->height[delete_location] |= INVALID_HEIGHT;

    draw_one_location_tracks(delete_location);
}

// ------------------------------------------------------
// draw_one_location_tracks()
//
// procedure draws all tracks at a location along
// the biorhythm
// use this to draw to preserve ordering of tracks (overlapping)

void draw_one_location_tracks(int location) {
    int i;

    for (i = (NUM_BIO_TRACKS - 1); i >= 0; i--) {
        if (!bio_data[i].free && bio_data[i].active)
            draw_bio_height(i, location);
    }
}

// --------------------------------------------
// draw_lower_tracks()
//
// procedure differs from draw_one_location_tracks in that it only
// draws tracks that are under the track number, including the track
// number given. Saves a little work.
//

void draw_lower_tracks(int track_number, int location) {
    int i;

    for (i = track_number; i >= 0; i--) {
        if (!bio_data[i].free && bio_data[i].active)
            draw_bio_height(i, location);
    }
}

    // RANDOM SPEW STUFF
    // moved here by doug to make things looking pretty

#define SIN_MAG 20

// Temporary biorhythm variables
ulong time1 = 0;
ulong time2 = 0;
ulong time3 = 0;
ulong time4 = 0;
ulong time5 = 0;
ulong time6 = 0;
ulong time7 = 0;
int bio_delta = 1;
extern int diff_sum;
extern int curr_ll;

// this simulates the heart beat!
int test_bio_var = 10;
int test_bio_var2 = 2;

// this simulates the sine wave
int test_bio_var3 = 19;

int bio_energy_var;
fix sinX = 0;
fix sinChi = 0;

void gamescr_bio_func(void) {
    extern int rad_absorb, bio_absorb;
    int i;

    clear_bio_tracks();

    // KLC - the "update_time" parameter is halved for Mac version, because we're only
    // getting called 70 times/sec rather than 140.

    status_bio_add(&bio_energy_var, MAX_ENERGY, 2, ENERGY_TRACK, 2, 0);
    {
        short ver = player_struct.hardwarez[CPTRIP(ENV_HARD_TRIPLE)];
        if (ver >= 1)
            status_bio_add(&bio_absorb, 24, 2, BIOHAZARD_TRACK, 3, BOTTOMLESS);
        if (ver >= 2)
            status_bio_add(&rad_absorb, 24, 2, RADIATION_TRACK, 3, BOTTOMLESS);
    }
    status_bio_add(&test_bio_var, 20, 1, HEART_TRACK, 3, 0);
    status_bio_add(&test_bio_var3, SIN_MAG, 4, SINE_TRACK, 2, 0);

    for (i = 0; i < NUM_BIO_TRACKS; i++)
        status_track_activate(i, (player_struct.active_bio_tracks & (1 << i)) != 0);
}

void diff_bio_func(void) {
    clear_bio_tracks();
    //                            max_value, update_time, track_number, tail_length, special
    status_bio_add(&diff_sum, 12, 6, 0, 2, 0);
    status_bio_add(&test_bio_var, 20, 2, 1, 3, 0);
    status_bio_add(&test_bio_var3, 20, 8, 2, 2, 0);
    status_bio_add(&test_bio_var2, SIN_MAG, 4, 4, 3, 0);
}

uchar heart_beat = 0;
ulong heart_time = 0;
ulong heart_delay = 5;
uchar flatline_heart;

uchar chi_amp = STATUS_CHI_AMP;

extern ubyte fatigue_threshold;

#define FATIGUE_RANGE (CONTROL_MAX_VAL - SPRINT_CONTROL_THRESHOLD)
#define ENERGY_ZERO 50

void change_bio_vars(void) {
    // Demo for biorhythm
    int fatigue_ratio;
    int fatigue_amp;
    int chi_per;

    if ((*tmd_ticks - heart_time) > heart_delay) {
        switch (heart_beat) {
        case 0:

        {
            int f = lg_max(0, (player_struct.fatigue - (CIT_CYCLE * fatigue_threshold)) / CIT_CYCLE);
            int heart_ratio = 3400 / (f + 7);
            if (heart_ratio > 500)
                heart_ratio = 500;
            else if (heart_ratio < 90)
                heart_ratio = 90;
            if (!flatline_heart && (*tmd_ticks - time1) > heart_ratio) {
                test_bio_var = 19;
                heart_delay = 5;
                time1 = heart_time = *tmd_ticks;
                heart_beat = 1;
            }
        } break;

        case 1:
            heart_beat = 2;
            test_bio_var = 20;
            heart_delay = 5;
            break;
        case 2:
            heart_beat = 3;
            test_bio_var = 10;
            heart_delay = 7;
            break;
        case 3:
            heart_beat = 4;
            test_bio_var = 2;
            heart_delay = 5;
            break;
        case 4:
            heart_beat = 5;
            test_bio_var = 1;
            heart_delay = 5;
            break;
        case 5:
            heart_beat = 0;
            test_bio_var = 10;
            heart_delay = 5;
            break;
        }
        heart_time = *tmd_ticks;
    }
    // bio_energy_var
    {
        static ubyte energy_spike = 0;
        static int last_val = 0;
        if (bio_energy_var != last_val)
            energy_spike = 3;
        if (energy_spike == 0) {
            bio_energy_var = ENERGY_ZERO + player_struct.energy_spend - player_struct.energy_regen;
        } else {
            energy_spike--;
        }
        last_val = bio_energy_var;
    }
    // MR. SINUSOID biorhythm
    if (player_struct.fatigue)
        fatigue_ratio = fatigue_amp = (150000 / player_struct.fatigue);
    else {
        fatigue_ratio = 40;
        fatigue_amp = SIN_MAG;
    }

    if (fatigue_ratio < 5)
        fatigue_ratio = 5;
    else if (fatigue_ratio > 40)
        fatigue_ratio = 40;

    fatigue_amp -= 5;
    if (fatigue_amp < 0)
        fatigue_amp = 0;
    else if (fatigue_amp > SIN_MAG)
        fatigue_amp = SIN_MAG;

    if ((*tmd_ticks - time2) > fatigue_ratio) {
        sinX += FIX_UNIT / 10;
        test_bio_var2 = (((FIX_UNIT - fix_fastsin(sinX)) / 2) * fatigue_amp) + 1 + ((SIN_MAG - fatigue_amp) / 2);
        time2 = *tmd_ticks;
    }

    chi_per = chi_amp;
    if (player_struct.drug_status[CPTRIP(LSD_DRUG_TRIPLE)])
        chi_per <<= 1;
    if (player_struct.drug_status[CPTRIP(GENIUS_DRUG_TRIPLE)])
        chi_per >>= 2;
    if (chi_per < 2) {
        test_bio_var3 = 10;
        time3 = *tmd_ticks;
        sinChi = 0;
    } else if ((*tmd_ticks - time3) > chi_per) {
        fix s;
        sinChi += FIX_UNIT / 20;
        if (sinChi > 6 * FIX_UNIT)
            sinChi -= 6 * FIX_UNIT;
        if (sinChi < fix_make(3, 0))
            s = -fix_mul(sinChi, sinChi - 3 * FIX_UNIT) * 4 / 9;
        else
            s = fix_mul(sinChi - 3 * FIX_UNIT, sinChi - 6 * FIX_UNIT) * 4 / 9;
        test_bio_var3 = 10 + fix_int(chi_amp * s);
        time3 = *tmd_ticks;
    }
}
