/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*           XXXX    XXX   X      X      XXXXX  XXX  X      XXXXX            */
/*           X   X  X   X  X      X      X       X   X      X                */
/*           X   X  X   X  X      X      X       X   X      X                */
/*           XXXX   X   X  X      X      XXX     X   X      XXX              */
/*           X X    X   X  X      X      X       X   X      X                */
/*           X  X   X   X  X      X      X       X   X      X                */
/*           X   X   XXX   XXXX   XXXXX  X      XXX  XXXXX  XXXXX            */
/*                                                                           */
/*                                                                           */
/*                         Roll File Manager, V10.00                         */
/*                    Copyright (c) 1991 by Wayne Stahnke                    */
/*                            All Rights Reserved                            */
/*                                                                           */
/*****************************************************************************/

/* A roll file consists of a byte stream of arbitrary length in the following
    format:

            Byte Stream Entry   Description of Entry                           
            ------------------  --------------------------------------------
            [<header string>]   (optional ascii string of arbitrary length),
            <header delimiter>  (ascii "null" character to delimit string),
            <version number>    (two-byte version number in low/high order),
            <event 1>           (first event),
            <event 2>           (second event),
            .                   .
            .                   .
            .                   .
            <event n>           (final event).

    Every event has two elements: differential displacement and port number.
    Five event types (COPY, MARK, LYRIC, CUE, and TEXT) have an additional
    third element: a text string.  We consider these three elements in turn.

        (1) The differential displacement consists of one or more bytes that
            denote the number of steps (samples) since the prior event.  The
            most significant bit is "1" for all bytes preceding the last one
            if more than one byte is required, "0" for the last or only byte.
            The value of the remaining seven bits of each byte are multiplied
            by a weighting factor determined by the position of the byte in
            the differential displacement field, and the products are summed
            to form the composite differential displacement.  The weighting
            factors are as follows:

                    Byte Position        | Weighting Factor          
                    ---------------------+---------------------------
                    first (or only) byte | 2**0  = 128**0 = 1
                    second byte          | 2**7  = 128**1 = 128
                    third byte           | 2**14 = 128**2 = 16384
                    fourth byte          | 2**21 = 128**3 = 2097152
                    fifth byte           | 2**28 = 128**4 = 268435456

            and so forth.  Thus, if the differential displacement is in the
            range 0-127, it is represented by only one byte.  Two bytes are
            used to represent a differential displacement that falls in the
            range 128-16383, and three bytes represent 16384-2097151.

            This scheme is similar to the one used in MIDI files.  However,
            there are two important differences: maximum value and order of
            presentation.  For MIDI files, the maximum value is (2**28)-1,
            whereas there is no upper limit to differential displacement in
            roll files.  In addition, for MIDI files the weighting factors
            decrease from the first byte to the last, whereas the weighting
            factors increase in roll files.  These differences can best be
            illustrated by the following examples:

                    Differential  | Representation | Representation
                    Displacement  | in roll files  | in MIDI files
                    --------------+----------------+---------------
                    0             | 00             | 00
                    1             | 01             | 01
                    127           | 7f             | 7f
                    128           | 80,01          | 81,00
                    129           | 81,01          | 81,01
                    130           | 82,01          | 81,02
                    16384         | 80,80,01       | 81,80,00
                    (2**28)-1     | ff,ff,ff,7f    | ff,ff,ff,7f
                    (2**28)       | 80,80,80,80,01 | (impossible)
                    (2**32)-1     | ff,ff,ff,ff,0f | (impossible)
                    (2**32)       | 80,80,80,80,10 | (impossible)

        (2) Ports are divided into two types: "working" ports and "dedicated"
            ports.  Working ports have port numbers in the range 0-239, and
            correspond to the physical ports in a tracker bar.  All working
            port events are of the OPEN or CLOSE type.  The first event for
            a given working port (if at least one event exists) must be of
            the OPEN type.  The type implicitly alternates between OPEN and
            CLOSE with each subsequent event.

            Dedicated ports have port numbers in the range 240-255, and are
            used to designate singular events that can occur at any point in
            the file.  The event type corresponding to a dedicated port is
            not OPEN or CLOSE.

        (3) For every event of the COPY, MARK, LYRIC, CUE, or TEXT type, the
            dedicated port number is followed by an ascii string of arbitrary
            length, including a length of zero.  This string is followed by
            an ascii "null" character to delimit the string.  The delimiter
            must be present, even if the string is omitted.  To provide the
            maximum copyright protection, the COPY event should be the first
            event in the file.

    If a roll has a rewind perforation, the roll file must contain an "end of
    performance" event before the first rewind indication.  In addition, every
    roll file must end with a "final" event. */

/*****************************************************************************/

/* Roll file port definitions */

/* The number of working ports is defined here.  The working ports correspond
    to physical ports in a tracker bar.  Whenever a working port event occurs,
    there is an implicit toggle of the event type from CLOSE to OPEN or vice
    versa. */

#define WORKING_PORTS 240 /* total working ports */

/* All dedicated ports are defined here.  Dedicated ports have port numbers
    in the range WORKING_PORTS through 255. */

#define COPY_PORT       240 /* copyright text event */
#define MARK_PORT       241 /* marker text event */
#define LYRIC_PORT      242 /* lyric text event */
#define CUE_PORT        243 /* cue text event */
#define TEXT_PORT       244 /* general text event */

#define RESERVED_1      245 /* not assigned (reserved) */
#define RESERVED_2      246 /* not assigned (reserved) */
#define RESERVED_3      247 /* not assigned (reserved) */
#define RESERVED_4      248 /* not assigned (reserved) */
#define RESERVED_5      249 /* not assigned (reserved) */
#define RESERVED_6      250 /* not assigned (reserved) */
#define RESERVED_7      251 /* not assigned (reserved) */

#define SPROCKET_PORT   252 /* sprocket mark */
#define EOS_PORT        253 /* end of selection */
#define EOP_PORT        254 /* end of performance */
#define FINAL_PORT      255 /* final event */

/*****************************************************************************/

/* Conditional compilation switches */

/* The conditional compilation switches are provided for debugging of this
    module and higher-level programs only.  The normal switch settings are
    given in the definitions. */

/*===========================================================================*/

/* The following switch manages overlapping and underlapping perforations by
    discarding redundant OPEN and CLOSE events */

#define LAP TRUE /* manage overlapping and underlapping (TRUE/FALSE) */

/*===========================================================================*/

/* Not more than one of the following three switches should be TRUE, and the
    rest should be FALSE */

#define SORT_BY_STEPS FALSE /* sort by displacement */
#define SORT_BY_TYPE FALSE /* sort by displacement, then by type */
#define SORT_BY_PORT TRUE /* sort by displacement, then type, then port */

/*****************************************************************************/

/* Include files */

#include <fcntl.h>
    #include <sys\types.h>
    #include <sys\stat.h>
#include <stdlib.h>

#include "error.h"
#include "logical.h"
#include "global.h"
#include "rollfile.h"
#include "single.i"

/*****************************************************************************/

/* Static Functions */

static void open_xroll_file(char *); /* open source file */
static char *get_xroll_header(void); /* get header string */
static void close_xroll_file(void); /* close source file */

static void make_xroll_file(char *); /* create target file */
static void put_xroll_header(char *); /* put header string */
static void alter_xroll_file(void); /* close and alter target file */
static void erase_xroll_file(void); /* close and erase target file */

static int get_xroll_event(roll_event *); /* get roll event */
static void put_xroll_event(roll_event *); /* put roll event */

/*===========================================================================*/

/* Static function variables */

static char source_header[(STRING_LENGTH + 1)] = ""; /* source header */
static char source_text[(STRING_LENGTH + 1)]; /* source text */

static int source_version; /* source file version number */
#define TARGET_VERSION 1 /* target file version number */

#define NO_STATUS 0 /* status does not exist */
#define STOP 1 /* end of performance */
#define RUN 2 /* not start, not end */
#define START 3 /* start of performance */

static int source_file_status = NO_STATUS; /* source file status */
static int target_file_status = NO_STATUS; /* target file status */

/*===========================================================================*/

static void open_xroll_file(char *source_path) { /* open source file */
    int i; /* working index */

    if (source_file_status != NO_STATUS)
        critical_error("ROLLFILE.C: Source file status error\n");

    open1(source_path); /* open source file */
    for (i = 0; (source_header[i] = get_source()) != '\0'; i++)
        if (i >= STRING_LENGTH)
            critical_error("ROLLFILE.C: Source header too long\n");

    source_version = get_source(); /* low byte */
    source_version += (256 * get_source()); /* high byte */
    if (source_version > TARGET_VERSION)
        critical_error("ROLLFILE.C: Unsupported source file version\n");

    source_file_status = START; /* source file status */
}

/*===========================================================================*/

static char *get_xroll_header(void) { /* get header string */
    if (source_file_status == NO_STATUS)
        critical_error("ROLLFILE.C: Source file status error\n");

    return(source_header);
}

/*===========================================================================*/

static void close_xroll_file(void) { /* close source file */
    if (source_file_status != STOP)
        critical_error("ROLLFILE.C: Source file status error\n");

    close1(); /* close source file */
    source_file_status = NO_STATUS; /* source file status */
}

/*===========================================================================*/

static void make_xroll_file(char *target_path) { /* create target file */
    if (target_file_status != NO_STATUS)
        critical_error("ROLLFILE.C: Target file status error\n");

    make1(target_path); /* create target file */
    target_file_status = START; /* target file status */
}

/*===========================================================================*/

static void put_xroll_header(char *header_string) { /* put header string */
    if (target_file_status != START) /* target file not just created */
        critical_error("ROLLFILE.C: Target file status error\n");

    while (*header_string != '\0')
        put_target(*header_string++);
}

/*===========================================================================*/

static void alter_xroll_file(void) { /* close and alter target file */
    if (target_file_status != STOP)
        critical_error("ROLLFILE.C: Target file status error\n");

    alter1(); /* close and alter target file */
    target_file_status = NO_STATUS; /* target file status */
}

/*===========================================================================*/

static void erase_xroll_file(void) { /* close and erase target file */
    erase1(); /* close and erase target file */
    target_file_status = NO_STATUS; /* target file status */
}

/*===========================================================================*/

static int get_xroll_event(roll_event *source_event) { /* get roll event */
    static long steps; /* previous displacement (steps) */
    static int next_type[WORKING_PORTS]; /* next working port type */
    long weight; /* weighting factor (1,128,16384,2097152,...) */
    int i; /* working index */

    switch(source_file_status) {
        default: /* illegal status */
        case NO_STATUS:
            critical_error("ROLLFILE.C: Source file status error\n");
            break;

        case STOP: /* end of performance */
            return(FALSE); /* event not available */
            break;

        case RUN: /* not start, not end */
            break;

        case START: /* start of performance */
            steps = 0L; /* initialize displacement */
            for (i = 0; i < WORKING_PORTS; i++)
                next_type[i] = OPEN;
            source_file_status = RUN;
            break;
    }

    for (weight = 1L; peek_source() >= 128; weight *= 128L)
        steps += weight * (get_source() % 128); /* not last byte (msb = 1) */
    steps += weight * get_source(); /* last byte (msb = 0) */

    source_event->steps = steps;
    source_event->port = get_source();

    if (source_event->port < WORKING_PORTS) { /* working port */
        if (next_type[source_event->port] == OPEN) {
            source_event->type = OPEN;
            next_type[source_event->port] = CLOSE;
        }
        else if (next_type[source_event->port] == CLOSE) {
            source_event->type = CLOSE;
            next_type[source_event->port] = OPEN;
        }
        return(TRUE);
    }

    if (source_event->port >= WORKING_PORTS) { /* dedicated port */
        switch(source_event->port) { /* dedicated port number */
            default: /* illegal dedicated port */
                critical_error(
                  "ROLLFILE.C: Illegal source file dedicated port\n"
                );
                break;

            case COPY_PORT: /* copyright text event */
                for (i = 0; (source_text[i] = get_source()) != '\0'; i++)
                    if (i >= STRING_LENGTH)
                        critical_error(
                          "ROLLFILE.C: Source copyright text too long\n"
                        );
                source_event->type = COPY;
                source_event->port = null_event.port;
                source_event->text = source_text;
                break;

            case MARK_PORT: /* marker text event */
                for (i = 0; (source_text[i] = get_source()) != '\0'; i++)
                    if (i >= STRING_LENGTH)
                        critical_error(
                          "ROLLFILE.C: Source marker text too long\n"
                        );
                source_event->type = MARK;
                source_event->port = null_event.port;
                source_event->text = source_text;
                break;

            case LYRIC_PORT: /* lyric text event */
                for (i = 0; (source_text[i] = get_source()) != '\0'; i++)
                    if (i >= STRING_LENGTH)
                        critical_error(
                          "ROLLFILE.C: Source marker text too long\n"
                        );
                source_event->type = LYRIC;
                source_event->port = null_event.port;
                source_event->text = source_text;
                break;

            case CUE_PORT: /* cue text event */
                for (i = 0; (source_text[i] = get_source()) != '\0'; i++)
                    if (i >= STRING_LENGTH)
                        critical_error(
                          "ROLLFILE.C: Source cue text too long\n"
                        );
                source_event->type = CUE;
                source_event->port = null_event.port;
                source_event->text = source_text;
                break;

            case TEXT_PORT: /* general text event */
                for (i = 0; (source_text[i] = get_source()) != '\0'; i++)
                    if (i >= STRING_LENGTH)
                        critical_error("ROLLFILE.C: Source text too long\n");
                source_event->type = TEXT;
                source_event->port = null_event.port;
                source_event->text = source_text;
                break;

            case SPROCKET_PORT: /* sprocket mark */
                source_event->type = SPROCKET;
                source_event->port = null_event.port;
                break;

            case EOS_PORT: /* end of selection */
                source_event->type = EOS;
                source_event->port = null_event.port;
                break;

            case EOP_PORT: /* end of performance */
                source_event->type = EOP;
                source_event->port = null_event.port;
                break;

            case FINAL_PORT: /* final event */
                source_event->type = FINAL;
                source_event->port = null_event.port;
                source_file_status = STOP;
                break;
        }
        return(TRUE);
    }
}

/*===========================================================================*/

static void put_xroll_event(roll_event *target_event) { /* put roll event */
    static long steps; /* previous displacement (steps) */
    static int overlap[WORKING_PORTS]; /* degree of overlap (or underlap) */
    long delta; /* differential displacement (steps) */
    int i; /* working index */

    switch(target_file_status) {
        default: /* illegal status */
        case NO_STATUS:
            critical_error("ROLLFILE.C: Target file status error\n");
            break;

        case STOP: /* end of performance */
            critical_error("ROLLFILE.C: Illegal target file status\n");
            break;

        case RUN: /* not start, not end */
            break;

        case START: /* start of performance */
            put_target('\0'); /* end of header */
            put_target(TARGET_VERSION % 256); /* low byte */
            put_target(TARGET_VERSION / 256); /* high byte */

            steps = 0L; /* initialize displacement */
            for (i = 0; i < WORKING_PORTS; i++)
                overlap[i] = 0;
            target_file_status = RUN;
            break;
    }

    if (target_event->steps < steps) {
        printf("Target steps = %ld, type = %d, port = %d\n",
          target_event->steps,
          target_event->type,
          target_event->port
        );
        critical_error("ROLLFILE.C: Illegal target displacement\n");
    }

    if ((target_event->type == OPEN) || (target_event->type == CLOSE))
        if ((target_event->port < 0) || (target_event->port >= WORKING_PORTS))
            critical_error("ROLLFILE.C: Illegal target port number\n");

/* At this point, the port number associated with each OPEN or CLOSE event
    satisfies 0 <= (target_event->port) < WORKING_PORTS, and can therefore
    be used as an array index */

#if LAP
    if (target_event->type == OPEN) {
        if (overlap[target_event->port]++ != 0)
            return; /* discard redundant OPEN event */
    }
    else if (target_event->type == CLOSE) {
        if (overlap[target_event->port]-- != 1)
            return; /* discard redundant CLOSE event */
    }
#else
    if (target_event->type == OPEN) {
        if (overlap[target_event->port]++ != 0)
            critical_error("ROLLFILE.C: Open event follows open event\n");
    }
    else if (target_event->type == CLOSE) {
        if (overlap[target_event->port]-- != 1)
            critical_error("ROLLFILE.C: Close event follows close event\n");
    }
#endif

    for (delta = (target_event->steps - steps); delta >= 128L; delta /= 128L)
        put_target((int)((delta % 128L) + 128L)); /* not last byte (msb = 1) */
    put_target((int)delta); /* last byte (msb = 0) */
    steps = target_event->steps;

    switch(target_event->type) { /* event type */
        default: /* illegal type */
            critical_error("ROLLFILE.C: Illegal target event type\n");
            break;

        case COPY: /* copyright text event */
            put_target(COPY_PORT);
            for (i = 0; put_target(*(target_event->text + i)) != '\0'; i++)
                ;
            break;

        case MARK: /* marker text event */
            put_target(MARK_PORT);
            for (i = 0; put_target(*(target_event->text + i)) != '\0'; i++)
                ;
            break;

        case LYRIC: /* lyric text event */
            put_target(LYRIC_PORT);
            for (i = 0; put_target(*(target_event->text + i)) != '\0'; i++)
                ;
            break;

        case CUE: /* cue text event */
            put_target(CUE_PORT);
            for (i = 0; put_target(*(target_event->text + i)) != '\0'; i++)
                ;
            break;

        case TEXT: /* general text event */
            put_target(TEXT_PORT);
            for (i = 0; put_target(*(target_event->text + i)) != '\0'; i++)
                ;
            break;

        case OPEN: /* open event */
        case CLOSE: /* close event */
            put_target(target_event->port);
            break;
     
        case SPROCKET: /* sprocket mark */
            put_target(SPROCKET_PORT);
            break;

        case EOS: /* end of selection */
            put_target(EOS_PORT);
            break;

        case EOP: /* end of performance */
            put_target(EOP_PORT);
            break;

        case FINAL: /* final event */
            put_target(FINAL_PORT);
            target_file_status = STOP;
            break;
    }
}

/*****************************************************************************/

/* External functions */

/*===========================================================================*/

/* External function variables */

/* When events are to be installed in the target file, they are first placed
    in a linked list.  The linked list is initially empty, and a new node is
    placed by inserting it appropriately according to its displacement, its
    type, and its port.  This sorts the list by insertion.

    Similarly, when events are retrieved from the source file they are sorted
    so that they are always provided to the calling functions in order.  This
    allows the calling routine to process the events in order, regardless of
    whether they are ordered in the source file.

    The linked list capacity may be in the range 1 through 32767 (not 32768)
    under the Microsoft C Compiler V5.00.  The only other constraint on its
    value is the available environment space.  We make the value a power of
    two for convenience only, not out of necessity.  The value given here is
    about twice as large as required for any programs written to date. */

#define LIST_CAPACITY 4096 /* linked list capacity (nodes) */

/* The nodes are numbered 0 through (LIST_CAPACITY - 1).  In addition to
    to the working nodes there is a special node, called the "list head,"
    that conceptually always remains in the linked list.  Thus, even when
    the linked list is empty, it still contains the list head.  The list
    head does not contain event data; only its link fields are used.  The
    number of the list head is one greater than the number of the highest
    working node, as defined here. */

#define HEAD (LIST_CAPACITY) /* list head node number */

/* The linked list node structure is similar to the roll event structure,
    except that it includes the left and right link fields.  The Microsoft
    C Compiler V5.00 allows huge arrays only if the number of bytes in an
    array element is a power of two.  The node structure defined here has
    been designed with this in mind and occupies 16 bytes, satisfying the
    requirement. */

typedef struct { /* structure to contain left and right links */
    int left; /* left node number (0 to LIST_CAP) */
    int right; /* right node number (0 to LIST_CAP) */
} couple;

typedef struct {
    roll_event data; /* event data */
    couple link; /* left and right links */
} node; /* linked list node */

/* The number of nodes allocated to an array is one larger than the linked
    list capacity, to include the list head.  All unused nodes are linked
    into a stack of available nodes, which requires a variable to point to
    the node on the top of the stack. */

static node huge source[(LIST_CAPACITY + 1)]; /* source nodes */
static int source_avail; /* source next available (unused) source node */

static node huge target[(LIST_CAPACITY + 1)]; /* target nodes */
static int target_avail; /* target next available (unused) target node */

/*===========================================================================*/

extern void open_roll_file(char *path) { /* open source file */
    int i; /* working node number */

    for (i = 0; i < LIST_CAPACITY; i++) /* all working nodes */
        source[i].link.right = (i + 1);
    source_avail = 0; /* first available node number */

    source[HEAD].link.left = HEAD;
    source[HEAD].link.right = HEAD; /* source linked list empty */

    open_xroll_file(path); /* open source file */
}

/*===========================================================================*/

extern unsigned get_roll_date(void) { /* retrieve source file date */
    return(date1());
}

/*===========================================================================*/

extern unsigned get_roll_time(void) { /* retrieve source file time */
    return(time1());
}

/*===========================================================================*/

extern char *get_roll_header(void) { /* get header string */
    return(get_xroll_header());
}

/*===========================================================================*/

extern void close_roll_file(void) { /* close source file */
    close_xroll_file(); /* close source file */
}

/*===========================================================================*/

extern void make_roll_file(char *path) { /* create target file */
    int i; /* working node number */

    for (i = 0; i < LIST_CAPACITY; i++) /* all working nodes */
        target[i].link.right = (i + 1);
    target_avail = 0; /* first available node number */

    target[HEAD].link.left = HEAD;
    target[HEAD].link.right = HEAD; /* target linked list empty */

    make_xroll_file(path); /* create target file */
}

/*===========================================================================*/

extern void stamp_roll_file(unsigned date,unsigned time) { /* stamp target */
    stamp1(date,time);
}

/*===========================================================================*/

extern void put_roll_header(char *header_string) { /* put header string */
    put_xroll_header(header_string);
}

/*===========================================================================*/

extern void alter_roll_file(void) { /* close and alter target file */
    int i; /* working node number */

    while (target[HEAD].link.right != HEAD) { /* not empty */
        i = target[HEAD].link.right; /* number of leftmost node */
        put_xroll_event(&target[i].data); /* dispose of node */

        target[HEAD].link.right = target[i].link.right;
        target[target[HEAD].link.right].link.left = HEAD; /* uncouple */
        target[i].link.right = target_avail;
        target_avail = i; /* place on stack */
    }

    alter_xroll_file(); /* close and alter target file */
}

/*===========================================================================*/

extern void erase_roll_file(void) { /* close and erase target file */
    erase_xroll_file(); /* close and erase target file */
}

/*===========================================================================*/

extern int peek_roll_event(roll_event *source_event) { /* peek roll event */
    int i; /* working node number */
    int j; /* new node number */

    while (source_avail != HEAD) { /* source linked list not full */
        j = source_avail; /* new node number */
        if (get_xroll_event(&source[j].data) == FALSE)
            break; /* event not available */
        source_avail = source[j].link.right; /* remove from stack */
        i = source[HEAD].link.left; /* number of rightmost node */

#if (SORT_BY_STEPS | SORT_BY_TYPE | SORT_BY_PORT) /* sort by displacement */
        while ((i != HEAD) && /* not end of list */
          (source[j].data.steps < source[i].data.steps))
            i = source[i].link.left; /* move to next node on left */

#if (SORT_BY_TYPE | SORT_BY_PORT) /* sort by type */
        while ((i != HEAD) && /* not end of list */
          (source[j].data.steps == source[i].data.steps) &&
          (source[j].data.type < source[i].data.type))
            i = source[i].link.left; /* move to next node on left */

#if (SORT_BY_PORT)  /* sort by port */
        while ((i != HEAD) && /* not end of list */
          (source[j].data.steps == source[i].data.steps) &&
          (source[j].data.type == source[i].data.type) &&
          (source[j].data.port < source[i].data.port))
            i = source[i].link.left; /* move to next node on left */
#endif
#endif
#endif
        source[j].link.right = source[i].link.right;
        source[source[j].link.right].link.left = j;
        source[i].link.right = j;
        source[source[i].link.right].link.left = i; /* insert new node */
    }

/* At this point the source linked list is as full as possible */
    if (source[HEAD].link.right == HEAD) /* source linked list empty */
        return(FALSE); /* event not available */

    i = source[HEAD].link.right; /* number of leftmost node */
    *source_event = source[i].data; /* copy from leftmost node */
    return(TRUE);
}

/*===========================================================================*/

extern int get_roll_event(roll_event *source_event) { /* get roll event */
    int i; /* working node number */

    if (peek_roll_event(source_event) == FALSE)
        return(FALSE); /* event not available */

    i = source[HEAD].link.right; /* number of leftmost node */
    *source_event = source[i].data; /* copy from leftmost node */

    source[HEAD].link.right = source[i].link.right;
    source[source[HEAD].link.right].link.left = HEAD; /* uncouple */
    source[i].link.right = source_avail;
    source_avail = i; /* place on stack */
    return(TRUE);
}

/*===========================================================================*/

extern void put_roll_event(roll_event *target_event) { /* put roll event */
    int i; /* working node number */
    int j; /* new node number */

    if (target_avail == HEAD) { /* target linked list full */
        i = target[HEAD].link.right; /* number of leftmost node */
        put_xroll_event(&target[i].data); /* dispose of node */

        target[HEAD].link.right = target[i].link.right;
        target[target[HEAD].link.right].link.left = HEAD; /* uncouple */
        target[i].link.right = target_avail;
        target_avail = i; /* place on stack */
    }

/* At this point the target linked list is not full */
    j = target_avail; /* new node number */
    target_avail = target[j].link.right; /* remove from stack */
    target[j].data = *target_event; /* copy into new node */
    i = target[HEAD].link.left; /* number of rightmost node */

#if (SORT_BY_STEPS | SORT_BY_TYPE | SORT_BY_PORT) /* sort by displacement */
    while ((i != HEAD) && /* not end of list */
      (target[j].data.steps < target[i].data.steps))
        i = target[i].link.left; /* move to next node on left */

#if (SORT_BY_TYPE | SORT_BY_PORT) /* sort by type */
    while ((i != HEAD) && /* not end of list */
      (target[j].data.steps == target[i].data.steps) &&
      (target[j].data.type < target[i].data.type))
        i = target[i].link.left; /* move to next node on left */

#if (SORT_BY_PORT)  /* sort by port */
    while ((i != HEAD) && /* not end of list */
      (target[j].data.steps == target[i].data.steps) &&
      (target[j].data.type == target[i].data.type) &&
      (target[j].data.port < target[i].data.port))
        i = target[i].link.left; /* move to next node on left */
#endif
#endif
#endif
    target[j].link.right = target[i].link.right;
    target[target[j].link.right].link.left = j;
    target[i].link.right = j;
    target[target[i].link.right].link.left = i; /* insert new node */
}

/*****************************************************************************/

/* Change history */

/*  5-15-91: Initial release (V10.00) */

/*****************************************************************************/