?? RIGHT := 110 ??
?? NEWTITLE := 'CREATE_ORDER_DEFINITION Subutility: DETERMINE_TAPE_NUMBER Subcommand.' ??
MODULE ram$determine_number_of_tapes;

{ PURPOSE:
{   This module contains the procedures that determine the number of tapes that
{   will be required to write the given pacs catalog.
{
{ DESIGN:
{   The algorithm is the same as that used in WRITE_DEFINITION for determining
{   the number of tapes.
{

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc rac$not_assigned
*copyc rac$order_data_file_name
*copyc rac$packing_list_name
*copyc rac$sif_file_name
*copyc rac$tape_types
*copyc rae$package_software_cc
*copyc amt$file_byte_address
*copyc ost$string
*copyc rat$string
*copyc rat$tape
?? POP ??
*copyc i#current_sequence_position
*copyc amp$get_segment_pointer
*copyc amp$put_next
*copyc amp$set_segment_eoi
*copyc clp$change_variable
*copyc clp$evaluate_parameters
*copyc clp$trimmed_string_size
*copyc fsp$close_file
*copyc osp$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc osp$set_status_abnormal
*copyc pfp$define_catalog
*copyc pmp$get_date
*copyc pmp$get_unique_name
*copyc rap$add_name_to_path_ref
*copyc rap$get_file_path_and_ref
*copyc smp$begin_sort_specification
*copyc smp$end_specification
*copyc smp$from_memory_area
*copyc smp$key
*copyc smp$to_memory_area
*copyc rap$open_file
*copyc rav$creod_scratch_segment
*copyc rav$order_contents_count
*copyc rav$order_contents_list_p
*copyc rav$packing_list_header_p
*copyc rav$packing_list_seq_p
*copyc rav$subproduct_type
*copyc rav$tape_information

?? TITLE := 'Global Declarations Declared by This Module', EJECT ??

  CONST
    bytes_per_foot_mt9$1600 = 14975,
    bytes_per_foot_mt9$6250 = 49527,
    bytes_per_foot_mt18$38000 = 412962,
    bytes_per_tape_gap_mt9$1600 = (75 * bytes_per_foot_mt9$1600) DIV 100, {9 inch gap}
    bytes_per_tape_gap_mt9$6260 = (75 * bytes_per_foot_mt9$6250) DIV 100,

{9 inch gap

    bytes_per_tape_gap_mt18$38000 = (bytes_per_foot_mt18$38000) DIV 12;

{1 inch gap


?? TITLE := '[XDCL] rap$determine_number_of_tapes', EJECT ??

{ PURPOSE:
{   This is a hidden interface to determine the number of tapes that will be
{   required for the given subproduct list.
{
{ DESIGN:
{   This is the main driver.
{
{   It follows the same process as used by WRITE DEFINITION, except it stops
{   after it has determined the number of tapes.
{
{ NOTES:
{

  PROCEDURE [XDCL] rap$determine_number_of_tapes
    (    parameter_list: clt$parameter_list;
     VAR status: ost$status);


{  procedure wrid_pdt (
{    number_of_tapes, not: (VAR) integer = $required
{    status: status = $optional
{    )

?? PUSH (LISTEXT := ON) ??
?? FMT (FORMAT := OFF) ??

  VAR
    pdt: [STATIC, READ, cls$declaration_section] record
      header: clt$pdt_header,
      names: array [1 .. 3] of clt$pdt_parameter_name,
      parameters: array [1 .. 2] of clt$pdt_parameter,
      type1: record
        header: clt$type_specification_header,
        qualifier: clt$integer_type_qualifier,
      recend,
      type2: record
        header: clt$type_specification_header,
      recend,
    recend := [
    [1,
    [91, 4, 19, 16, 35, 40, 485],
    clc$command, 3, 2, 1, 0, 0, 1, 0, ''], [
    ['NOT                            ',clc$abbreviation_entry, 1],
    ['NUMBER_OF_TAPES                ',clc$nominal_entry, 1],
    ['STATUS                         ',clc$nominal_entry, 2]],
    [
{ PARAMETER 1
    [2, clc$normal_usage_entry, clc$non_secure_parameter,
    $clt$parameter_spec_methods[clc$specify_by_name, clc$specify_positionally],
    clc$pass_by_reference, clc$immediate_evaluation, clc$standard_parameter_checking, 20,
  clc$required_parameter, 0, 0],
{ PARAMETER 2
    [3, clc$normal_usage_entry, clc$non_secure_parameter,
    $clt$parameter_spec_methods[clc$specify_by_name, clc$specify_positionally],
    clc$pass_by_value, clc$immediate_evaluation, clc$standard_parameter_checking, 3, clc$optional_parameter, 0
  , 0]],
{ PARAMETER 1
    [[1, 0, clc$integer_type], [clc$min_integer, clc$max_integer, 10]],
{ PARAMETER 2
    [[1, 0, clc$status_type]]];

?? FMT (FORMAT := ON) ??
?? POP ??

    CONST
      p$number_of_tapes = 1,
      p$status = 2;

    VAR
      pvt: array [1 .. 2] of clt$parameter_value;

    VAR
      disk_subproduct_indexer_p: ^rat$disk_subproduct_indexer,
      order_catalog_p: ^pft$path,
      order_catalog_ref_p: ^fst$file_reference,
      tape_list_p: ^rat$primary_tape,
      number_of_tapes: clt$variable_reference,
      temp_order_contents_list_p: ^rat$order_contents_list,
      value: clt$data_value;

    status.normal := TRUE;

    clp$evaluate_parameters (parameter_list, #SEQ (pdt), NIL, ^pvt, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF rav$order_contents_count < rac$first_subproduct_entry THEN
      osp$set_status_abnormal ('RA', rae$no_subproducts_ordered, '', status);
      RETURN;
    IFEND;

    IF (rav$packing_list_header_p^.order_medium = rac$tape) AND
          (rav$tape_information.tape_type = 'UNKNOWN') THEN
      osp$set_status_abnormal ('RA', rae$tape_attributes_not_defined, '', status);
      RETURN;
    IFEND;

    RESET rav$creod_scratch_segment.sequence_p TO rav$creod_scratch_segment.reset_p;

    estimate_tape_packing_list_size (rav$packing_list_seq_p, rav$tape_information, rav$order_contents_count,
          rav$order_contents_list_p);

    temp_order_contents_list_p := rav$order_contents_list_p;

    sort_order_contents ('PRIORITY_AND_SIZE', rav$order_contents_count, temp_order_contents_list_p, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    assign_order_contents_to_tape (rav$tape_information, temp_order_contents_list_p, tape_list_p, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    value.kind := clc$integer;
    value.integer_value.value := rav$tape_information.number_of_tapes;

    clp$change_variable (pvt [p$number_of_tapes].variable^, ^value, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    RESET rav$creod_scratch_segment.sequence_p;

  PROCEND rap$determine_number_of_tapes;


?? TITLE := 'assign_order_contents_to_tape', EJECT ??

{ PURPOSE:
{   This procedure assigns the ordered subproducts and packing list to the
{   required tapes.  The order contents list is returned with the position
{   assigned for each subproduct set.  Also a list of tapes is returned.
{
{ DESIGN:
{   The subproduct tape assignment algorithm uses a best fit approach.
{   The order contents list has previously been sorted by priority and
{   size in decending order.  Looping through the contents list the
{   contents items are added to a tape until it is full or all contents
{   items have been assigned.  Additional tapes are added as required.
{
{     Assumptions and Requirements
{
{     1.  Minimize the number of tapes needed.  The ideal or theoretical
{         number of tapes is the number of tapes required when the
{         subproducts are backed up together as one multi-volume set.
{
{     2.  In general, tapes will be independent backups that can be
{         accessed asynchronously.  The only exception is when a subproduct
{         is too large to fit on a single tape.  Then the tape will become
{         multi-volume, with the additional tape volumes containing only
{         the subproduct in question.
{
{     3.  A subproduct will only be allowed to span across tapes when the
{         subproduct is larger than one tape.
{
{     4.  Files larger than one tape are not an issue since subproducts
{         will be allowed to be larger than one tape.
{
{     5.  When dealing with a subproduct larger than one tape the following
{         rules apply:
{
{           A tape can only be assigned one subproduct belonging to the
{           "too large" category.  A tape assigned a subproduct of this type
{           then is designated as the 1st tape of a multi-volume set.
{           (This means a separate multi-volume set will be defined for
{           each subproduct of type "too large".)
{
{           The subproduct is assigned to as many additional tape volumes
{           as the subproduct can completely fill.  Once the amount left
{           to be assigned is less than a single tape the remainder is
{           assigned to the 1st tape of the multi-volume set.
{
{           The subproduct causing the need for multi-volumes is the only
{           subproduct that can be assigned to the additional tapes of the
{           multi-volume set.
{
{           Additional subproducts are assigned to the 1st tape until the
{           tape is "completed".
{
{           The subproduct of type "too large" must be the last subproduct
{           backed up to the 1st tape of the multi-volume set.  This is
{           accomplished by setting the assigned field (of the "too large"
{           subproduct's contents record) to -1.  Once all the other
{           subproducts have had a chance to be assigned to the tape, the
{           assigned field will be set to the next available value.
{
{     6.  The largest tape size defined will be used in the tape
{         assignment algorithm.  Adjustments to smaller tape sizes (if
{         allowed) will be made after the assignments have been made.  When
{         adjusting a multi-volume tape place the smallest tape as the last
{         tape in the volume.
{
{ NOTES:
{   The tape sizes are assumed to be sorted from largest to smallest.
{   The first tape size is used during the assignment.
{

  PROCEDURE assign_order_contents_to_tape
    (VAR tape_info: rat$tape_information;
     VAR contents_list_p: ^rat$order_contents_list;
     VAR tape_list_p: ^rat$primary_tape;
     VAR status: ost$status);


    CONST
      assigned_last_to_tape = -1;

    VAR
      additional_volume_p: ^rat$additional_volume,
      contents_assigned: integer,
      contents_index: integer,
      bytes_per_tape_gap: integer,
      contents_item_tape_size: integer,
      free_bytes: integer,
      i: integer,
      j: integer,
      max_bytes_per_tape: integer,
      multi_volume: boolean,
      tape_p: ^rat$primary_tape,
      tape_vsn: string (6);


    status.normal := TRUE;

    initialize_tape_assignment (contents_list_p, tape_info, tape_list_p, tape_vsn, max_bytes_per_tape,
          bytes_per_tape_gap);

    contents_assigned := 0;

    WHILE contents_assigned < UPPERBOUND (contents_list_p^) DO

{ One interation will complete the assignment to one tape.

      tape_info.number_of_tapes := tape_info.number_of_tapes + 1;

      free_bytes := max_bytes_per_tape;
      multi_volume := FALSE;

{
{   Search the contents list to find subproducts that have not been
{   assigned to a tape.
{

      FOR i := 1 TO UPPERBOUND (contents_list_p^) DO

        IF contents_list_p^ [i].position_assigned = rac$not_assigned THEN

          contents_item_tape_size := contents_list_p^ [i].size + bytes_per_tape_gap;

{
{   If the size of the subproduct is smaller than the number of bytes
{   left on the tape,
{   assign the subproduct to the tape.
{

          IF contents_item_tape_size <= free_bytes THEN

            contents_assigned := contents_assigned + 1;

{
{   Indicate the position that the subproduct will have on the tape
{   by setting
{   the position assigned to the number of contents that have been
{   assigned.
{

            contents_list_p^ [i].position_assigned := contents_assigned;
            free_bytes := free_bytes - contents_item_tape_size;

          ELSEIF (multi_volume = FALSE) AND (contents_item_tape_size > max_bytes_per_tape) THEN

{
{   Determine if part of the multi volume subproduct can be assigned
{   to the present tape vsn.
{

            IF (contents_item_tape_size MOD max_bytes_per_tape) <= free_bytes THEN

              multi_volume := TRUE;
              contents_list_p^ [i].position_assigned := assigned_last_to_tape;
              free_bytes := free_bytes - (contents_item_tape_size MOD max_bytes_per_tape);
              contents_index := i;

              FOR j := 1 TO (contents_item_tape_size DIV max_bytes_per_tape) DO
                tape_info.number_of_tapes := tape_info.number_of_tapes + 1;
                IF NOT status.normal THEN
                  RETURN;
                IFEND;
              FOREND;

            IFEND;

          IFEND;

        IFEND;

      FOREND;

      IF multi_volume = TRUE THEN
        contents_assigned := contents_assigned + 1;
        contents_list_p^ [contents_index].position_assigned := contents_assigned;
      IFEND;

    WHILEND;

  PROCEND assign_order_contents_to_tape;

?? TITLE := 'estimate_tape_packing_list_size', EJECT ??

{ PURPOSE:
{   This procedure estimates the size required for the packing list when the
{   order medium is defined as tape.
{
{ DESIGN:
{   The packing list currently contains the sequence_descriptor,
{   packing_list_header, and the SIF's for all the subproducts ordered.  The
{   sizes for the tape_subproduct_indexer and tape_vsns are estimated and
{   added to the current packing list size.  The estimation for the
{   tape_subproduct_indexer is based on the number of subproducts ordered *
{   the size of the index record.  The estimation for the tape_vsns is based
{   on the size of the tape vsn record * a general guess factor for the
{   number of tapes at the specified tape density.  (The general guess is
{   assuming an order of 400 mega bytes and 2400 foot tapes for 9 track and
{   540 foot tapes for 18 track.)
{
{ NOTES:
{   Contents count includes the packing list.  This is not adjusted when
{   computing the packing_list size.  It is felt that this provides a small
{   cushion for error in the size estimation.
{

  PROCEDURE estimate_tape_packing_list_size
    (    packing_list_seq_p: ^rat$packing_list_sequence;
         tape_info: rat$tape_information;
         contents_count: rat$subproduct_count;
     VAR contents_list_p: ^rat$order_contents_list);


    CONST
      tape_factor_mt9$1600 = 12,
      tape_factor_mt9$6250 = 4,
      tape_factor_mt18$38000 = 2;

    VAR
      subproduct_indexer_size: integer;


    IF tape_info.tape_type = rac$mt9$6250 THEN
      subproduct_indexer_size := (#SIZE (rat$tape_subproduct_index) * contents_count) +
            (#SIZE (rat$tape_vsn) * tape_factor_mt9$6250);
    ELSEIF tape_info.tape_type = rac$mt9$1600 THEN
      subproduct_indexer_size := (#SIZE (rat$tape_subproduct_index) * contents_count) +
            (#SIZE (rat$tape_vsn) * tape_factor_mt9$1600);
    ELSE {tape_type = rac$mt18$38000}
      subproduct_indexer_size := (#SIZE (rat$tape_subproduct_index) * contents_count) +
            (#SIZE (rat$tape_vsn) * tape_factor_mt18$38000);
    IFEND;

    contents_list_p^ [rac$packing_list_entry].size := i#current_sequence_position (packing_list_seq_p) +
          subproduct_indexer_size;

  PROCEND estimate_tape_packing_list_size;

?? TITLE := 'initialize_tape_assignment', EJECT ??

{ PURPOSE:
{   This procedure initializes the variables required by the tape
{   assignment algorithm.
{
{ DESIGN:
{   The usable length in bytes for each tape size is calculated.
{   This is determined by the percent usable value and the tape
{   density.  The usable bytes for the largest tape size becomes the
{   maximum bytes allowed.
{
{   The theoretical number of tapes is calculated for statistical
{   comparision (by others) with the actual tapes required.
{   This value is the number of tapes required when the
{   subproducts are backed up together as one multi-volume set.
{   This is calculated by adding up all the sizes of the order contents
{   and adding the size of a tape file gap for each content item (since
{   each content item will be a discrete backup file on the tape).
{   This is then divided by the maximum bytes for the largest tape.
{   One tape is added to this value to account for a reminder lost
{   using integer division.  Example:
{                            Total bytes in all subproducts = 9,000,000
{                            Total bytes per tape = 2,000,000
{                            9,000,000 DIV 2,000,000 = 4
{                            But 4 tapes will only hold 8,000,000 bytes
{                            So the number of tapes must be 4 + 1 = 5.
{ NOTES:
{   The first tape size on list is the largest.
{
{

  PROCEDURE initialize_tape_assignment
    (    contents_list_p: ^rat$order_contents_list;
     VAR tape_info: rat$tape_information;
     VAR tape_list_p: ^rat$primary_tape;
     VAR tape_vsn: string (6);
     VAR max_bytes_per_tape: integer;
     VAR bytes_per_tape_gap: integer);


    VAR
      i: integer,
      number_of_tapes_theoretical: integer,
      total_bytes: integer,
      usable_feet: integer;


    FOR i := 1 TO UPPERBOUND (tape_info.sizes) DO

      IF tape_info.sizes [i].feet <> 0 THEN
        usable_feet := (tape_info.sizes [i].feet * tape_info.percent_usable) DIV 100;

        IF tape_info.tape_type = rac$mt9$6250 THEN
          tape_info.sizes [i].usable_bytes := usable_feet * bytes_per_foot_mt9$6250;
        ELSEIF tape_info.tape_type = rac$mt9$1600 THEN
          tape_info.sizes [i].usable_bytes := usable_feet * bytes_per_foot_mt9$1600;
        ELSE { tape type = rac$mt18$38000 }
          tape_info.sizes [i].usable_bytes := usable_feet * bytes_per_foot_mt18$38000;
        IFEND;

      IFEND;

    FOREND;

    max_bytes_per_tape := tape_info.sizes [1].usable_bytes;

    IF tape_info.tape_type = rac$mt9$6250 THEN
      bytes_per_tape_gap := bytes_per_tape_gap_mt9$6260;
    ELSEIF tape_info.tape_type = rac$mt9$1600 THEN
      bytes_per_tape_gap := bytes_per_tape_gap_mt9$1600;
    ELSE { tape type = rac$mt18$38000 }
      bytes_per_tape_gap := bytes_per_tape_gap_mt18$38000;
    IFEND;

    total_bytes := 0;
    FOR i := 1 TO UPPERBOUND (contents_list_p^) DO
      total_bytes := total_bytes + contents_list_p^ [i].size + bytes_per_tape_gap;
    FOREND;

    number_of_tapes_theoretical := total_bytes DIV max_bytes_per_tape + 1;

    tape_info.number_of_tapes := 0;
    tape_info.number_of_primary_tapes := 0;
    tape_info.number_of_multi_vol_sets := 0;
    tape_info.number_of_tapes_theoretical := number_of_tapes_theoretical;

    tape_list_p := NIL;
    tape_vsn := '';

  PROCEND initialize_tape_assignment;

?? TITLE := 'sort_order_contents', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to sort the order contents list by
{   the specified keys (fields).
{
{ DESIGN:
{   The sorting is performed by the standard NOS/VE Sort/Merge interfaces.
{   The sort_key parameter defines the type of sort that is performed.
{   The supported sorts are:
{
{     1. Sort by priority and size fields.  This sort is done prior to
{        the assignment of the contents list to tape.  The tape assignment
{        algorithm requires the contents list to be sorted in this way.
{
{     2. Sort by position assigned.  Once the contents has been assigned
{        this sort puts the contents list into the correct assignment order
{        for writing the order data file and packing list's
{        tape subproduct_indexer.
{
{   The contents list is rewritten to a new location within the scratch
{   segment.  The pointer to the contents list is reset to point to the
{   new (sorted) contents list.
{
{ NOTES:
{   The result_array is used by the sorting interfaces to return status
{   information about the sort.  At this time, this information is being
{   ignored.
{

  PROCEDURE sort_order_contents
    (    sort_key: string ( * <= osc$max_name_size);
         contents_count: rat$subproduct_count;
     VAR contents_list_p: ^rat$order_contents_list;
     VAR status: ost$status);


    VAR
      new_contents_list_p: ^rat$order_contents_list,
      order_catalog_p: ^pft$path,
      result_array: smt$info_array;


    status.normal := TRUE;
    result_array [1] := 0; {Number of result elements returned in this array.}

    NEXT new_contents_list_p: [1 .. contents_count] IN rav$creod_scratch_segment.sequence_p;
    IF new_contents_list_p = NIL THEN
      osp$set_status_abnormal ('RA', rae$accessed_beyond_memory_seg, '', status);
      RETURN;
    IFEND;

    smp$begin_sort_specification (result_array, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    smp$from_memory_area (#LOC (contents_list_p^), 'FIXED', #SIZE (rat$order_contents), contents_count,
          status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    smp$to_memory_area (#LOC (new_contents_list_p^), 'FIXED', #SIZE (rat$order_contents), status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF sort_key = 'PRIORITY_AND_SIZE' THEN

      smp$key (1, #SIZE (rat$subproduct_priority), 'INTEGER', 'D', status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;

      smp$key ((1 + #SIZE (rat$subproduct_priority)), #SIZE (rat$subproduct_size), 'INTEGER', 'D', status);

    ELSE { sort_key = 'POSITION_ASSIGNED' }

      smp$key ((1 + #SIZE (rat$subproduct_priority) + #SIZE (rat$subproduct_size)),
            #SIZE (rat$position_assigned), 'INTEGER', 'A', status);

    IFEND;

    IF NOT status.normal THEN
      RETURN;
    IFEND;

    smp$end_specification (status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    contents_list_p := new_contents_list_p;

  PROCEND sort_order_contents;

MODEND ram$determine_number_of_tapes;
