/*

   FILE:  range_io.c

   Range I/O and checking routines

*/

#include "dbinc.h"
#include "use_db.h" /* RANGE_TYPE, TIME_RANGE, etc. */

/*

    FUNCTION:  get_range

       Reads keyword and corresponding range from input text file

    PARAMETERS:

       fp = pointer to input text file
       range = pointer to RANGE_TYPE structure

    RETURNS:

       EOF on end-of-file occurring when reading keyword
       0   on success
       1   on error or end-of-file occurring after reading keyword

*/
#define SUCCESS 0
#define FAILURE 1

#if PROTOTYPE_ALLOWED
int get_range(FILE *fp, RANGE_TYPE *range)
#else
int get_range(fp, range)
FILE *fp;
RANGE_TYPE *range;
#endif
{
   int errcode;
   long save_ofs;
   char keyword[25];  /* TIME_RANGE: or DAY_RANGE: or BLOCK_RANGE: or BLKPRF_RANGE: */
   char nextword[25]; /* "all" or start of range */
   YMDHMS_TIME_TYPE ymd_time;
   BLKPRF_INDEX_TYPE last_blkprf;

   if ( (errcode = fscanf(fp, "%19s", keyword)) != 1 ) return(errcode); /* EOF? */
   save_ofs = ftell(fp);
   if (fscanf(fp, "%19s", nextword) != 1) return(FAILURE);
   if (strcmp(keyword, "TIME_RANGE:") == 0)
   {
      range->type = TIME_RANGE;
      if (stricmp(nextword, "all") == 0)
      {
         UPCKTIM(&(range->ru.time.start), &(db->block_dir_hdr.time1));
         UPCKTIM(&(range->ru.time.end)  , &(db->block_dir_hdr.time2));
         return(SUCCESS);
      } /* else must be numerically specified: */
      fseek(fp, save_ofs, 0L);
      return(get_time_range(fp, &(range->ru.time.start), &(range->ru.time.end)) == 1 ?
         SUCCESS : FAILURE);
   }
   if (strcmp(keyword, "DAY_RANGE:") == 0)
   {
      range->type = DAY_RANGE;
      if (stricmp(nextword, "all") == 0)
      {
         UPCKTIM(&ymd_time, &(db->block_dir_hdr.time1));
         range->ru.day.start = year_day(&ymd_time, ymd_time.year);
         UPCKTIM(&ymd_time, &(db->block_dir_hdr.time2));
         range->ru.day.end   = year_day(&ymd_time, ymd_time.year);
         return(SUCCESS);
      } /* else must be numerically specified: */
      fseek(fp, save_ofs, 0L);
      return(fscanf
         (fp, " %lf to %lf", &(range->ru.day.start), &(range->ru.day.end)) == 2 ?
         SUCCESS : FAILURE);
   }
   if (strcmp(keyword, "BLOCK_RANGE:") == 0)
   {
      range->type = BLOCK_RANGE;
      if (stricmp(nextword, "all") == 0)
      {
         range->ru.block.start = 0;
         range->ru.block.end = db->block_dir_hdr.dir_nentries - 1;
         return(SUCCESS);
      } /* else must be numerically specified: */
      fseek(fp, save_ofs, 0L);
      return(fscanf
         (fp, " %d to %d", &(range->ru.block.start), &(range->ru.block.end)) == 2 ?
         SUCCESS : FAILURE);
   }
   if (strcmp(keyword, "BLKPRF_RANGE:") == 0)
   {
      range->type = BLKPRF_RANGE;
      if (stricmp(nextword, "all") == 0)
      {
         range->ru.blkprf.start.block = range->ru.blkprf.start.profile = 0;
         last_blkprf.block = db->block_dir_hdr.dir_nentries - 1;
         last_blkprf.profile = 0;
         if (dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &last_blkprf))
            return(FAILURE);
         range->ru.blkprf.end.block = last_blkprf.block;
         range->ru.blkprf.end.profile = db->block_hdr.dir_nentries - 1;
         return(SUCCESS);
      } /* else must be numerically specified: */
      fseek(fp, save_ofs, 0L);
      return(fscanf
         (fp, " %d to %d", &(range->ru.block.start), &(range->ru.block.end)) == 2 ?
         SUCCESS : FAILURE);
   }
   return(FAILURE);  /* unrecognized range option */
}

/*

    FUNCTION: in_range

    It retrieves the current database position into <current>
    and returns TRUE if it falls within the given <range>,
    0 otherwise (out-of-range or error).

*/

#if PROTOTYPE_ALLOWED
int in_range(char *current, RANGE_TYPE *range)
#else
int in_range(current, range)
char *current;
RANGE_TYPE *range;
#endif
{
   unsigned int n = sizeof(YMDHMS_TIME_TYPE);
   YMDHMS_TIME_TYPE now, base;

   switch(range->type)
   {
      case TIME_RANGE:
         return( check_time( (YMDHMS_TIME_TYPE *) current,
            &(range->ru.time.start), &(range->ru.time.end) ) );
      case DAY_RANGE:
         if (dbget_cnf(TIME, (char *) &now, &n, "getting current time")) return(0);
         UPCKTIM(&base, &(db->block_dir_hdr.time1));
         *((double *) current) = year_day(&now, base.year);
         return( *((double *) current) >= range->ru.day.start &&
                 *((double *) current) <= range->ru.day.end );
      case BLOCK_RANGE:
         if (dbget_cnf(BLOCK_PROFILE_INDEX, current, &n,
            "getting block-profile index")) return(0);
         return(
            ((BLKPRF_INDEX_TYPE *) current)->block >= range->ru.block.start &&
            ((BLKPRF_INDEX_TYPE *) current)->block <= range->ru.block.end);
      case BLKPRF_RANGE:
         if (dbget_cnf(BLOCK_PROFILE_INDEX, current, &n,
            "getting block-profile index")) return(0);
         return(
            BPCMP( (BLKPRF_INDEX_TYPE *) current,
                   (BLKPRF_INDEX_TYPE *) &(range->ru.blkprf.start) ) >= 0 &&
            BPCMP( (BLKPRF_INDEX_TYPE *) current,
                   (BLKPRF_INDEX_TYPE *) &(range->ru.blkprf.end) ) <= 0);
      default:
         return(0);  /* out-of-range to quit */
   }
}
/*

    FUNCTION:  goto_start_of_range

       It positions the database pointer at earliest profile within given range.

*/

#if PROTOTYPE_ALLOWED
int goto_start_of_range(RANGE_TYPE *range)
#else
int goto_start_of_range(range)
RANGE_TYPE *range;
#endif
{
   YMDHMS_TIME_TYPE base, time_start;
   int ierr;
   BLKPRF_INDEX_TYPE bp;

   switch(range->type)
   {
      case TIME_RANGE:
         ierr = dbsrch_cnf(TIME_SEARCH, (char *) &(range->ru.time.start));
         break;
      case DAY_RANGE:
         UPCKTIM(&base, &(db->block_dir_hdr.time1));
         yd_to_ymdhms_time(range->ru.day.start, base.year, &time_start);
         ierr = dbsrch_cnf(TIME_SEARCH, (char *) &time_start);
         break;
      case BLOCK_RANGE:
         bp.block   = range->ru.block.start;
         bp.profile = 0;
         ierr = dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp);
         break;
      case BLKPRF_RANGE:
         bp.block   = range->ru.blkprf.start.block;
         bp.profile = range->ru.blkprf.start.profile;
         ierr = dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp);
         break;
   }
   switch(ierr)
   {
      case 0:  return(SUCCESS);
      case SEARCH_BEFORE_BEGINNING:  return(SUCCESS);
      default:  return(ierr);
   }
}

/*

    FUNCTION:  goto_end_of_range

       It positions the database pointer at last profile within given range.

    NOTE:  Because DBSRCH by TIME positions the database pointer at a
           profile with time >= key, we need to back up one profile
           in the > case.

*/

#if PROTOTYPE_ALLOWED
int goto_end_of_range(RANGE_TYPE *range)
#else
int goto_end_of_range(range)
RANGE_TYPE *range;
#endif
{
   YMDHMS_TIME_TYPE base, time_end, tnow;
   int ierr;
   BLKPRF_INDEX_TYPE bp;
   unsigned int n = sizeof(YMDHMS_TIME_TYPE);

   switch(range->type)
   {
      case TIME_RANGE:
         ierr = dbsrch_cnf( TIME_SEARCH, (char *) &(range->ru.time.end) );
         if (!ierr)  /* make sure that prof time <= end of range */
         {           /* since TIME_SEARCH positions at >= key */
            if ( dbget_cnf(TIME, (char *) &tnow, &n, "getting time") )
               return(FAILURE);
            if ( TIMCMP(&tnow, &(range->ru.time.end)) == 2)
               return(dbmove_cnf(-1));
            return(SUCCESS);
         }
         break;
      case DAY_RANGE:
         UPCKTIM(&base, &(db->block_dir_hdr.time1));
         yd_to_ymdhms_time(range->ru.day.end, base.year, &time_end);
         ierr = dbsrch_cnf( TIME_SEARCH, (char *) &(time_end) );
         if (!ierr)  /* make sure that prof time <= end of range */
         {           /* since TIME_SEARCH positions at >= key */
            if ( dbget_cnf(TIME, (char *) &tnow, &n, "getting time") ) return(FAILURE);
            if ( TIMCMP(&tnow, &(time_end)) == 2) return(dbmove_cnf(-1));
            return(SUCCESS);
         }
         break;
      case BLOCK_RANGE:
         bp.block   = range->ru.block.end;
         bp.profile = 0;
         ierr = dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp);
         if (!ierr)
         {
            bp.profile = db->block_hdr.dir_nentries - 1;
            ierr = dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp);
         }
         return(ierr);
      case BLKPRF_RANGE:
         bp.block   = range->ru.blkprf.end.block;
         bp.profile = range->ru.blkprf.end.profile;
         return(dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp));
   }
   switch(ierr)
   {
      case SEARCH_BEYOND_END:  /* go to end of database */
         bp.block = db->block_dir_hdr.dir_nentries - 1;   /* last block no. */
         bp.profile = 0;
         if (!dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp))
         {
            bp.profile = db->block_hdr.dir_nentries - 1;  /* last profile no. */
            return(dbsrch_cnf(BLOCK_PROFILE_SEARCH, (char *) &bp));
         }
         return(FAILURE);
      default:
         return(ierr);
   }
}

/*

    FUNCTION:  check_range(range)

       Checks given <range> against the immediately preceding one
       (against database beginning if no preceding range).

       This is done by checking the current db position against
       the start of the given <range>.  The current db position
       is assumed to be 1 after the end of the immediately
       preceding range.

    RETURNS:  GAP       if current db position is < start of range
              OKAY      if current db position is = start of range
              OVERLAP   if current db position is > start of range
              BAD_RANGE if error (misspecified range, etc.)

    PARAMETER:

       range = pointer to range to check against current db position

    NOTE:  It always updates the db position at one profile
           past the end of <range>--in preparation for the
           next call to check_range.

           Comparison is always resolved in terms of block-profile
           indices regardless of the range type to allow for
           rounding in the case of time-type ranges.

*/

#if PROTOTYPE_ALLOWED
int check_range(RANGE_TYPE *range)
#else
int check_range(range)
RANGE_TYPE *range;
#endif
{
   int save_bp[2], result;

   save_bp[0] = db->block_dir_index;     /* save current position */
   save_bp[1] = db->profile_dir_index;
   if (goto_start_of_range(range)) return(BAD_RANGE);
   result = (db->block_dir_index  == save_bp[0]) ?  /* if same block no. */
            (db->profile_dir_index - save_bp[1]) :  /* check profile no. */
            (db->block_dir_index   - save_bp[0]) ;
   if (goto_end_of_range(range)) return(BAD_RANGE);
   dbmove_cnf(1); /* so next call compares 1 profile beyond current range */
   return( (result == 0) ? OKAY : ( (result > 0) ? GAP : OVERLAP ) );
}

/*

    FUNCTION:  check_for_gap_at_end(range)

       Checks last given <range> against the end of the database.
       (Supplements check_range() which checks first given <range>
       against beginning of the database).

       This is done by attempting to DBMOVE one profile beyond the last <range>.

    RETURNS:  GAP       if there are more profiles beyond last <range>
              OKAY      if last <range> covers to end of database
              ierr      otherwise

    PARAMETER:

       range = pointer to range to check against current db position

    NOTE:  It leaves the db position at one profile
           past the end of <range>, if not already at the end.

*/
int check_for_gap_at_end(range)
RANGE_TYPE *range;
{
   int ierr = 0, nsteps = 1;

   if (goto_end_of_range(range)) return(BAD_RANGE);
   DBMOVE(&nsteps, &ierr);
   if (ierr == SEARCH_BEYOND_END) return(OKAY);
   if (ierr == 0) return(GAP);  /* more profiles follow */
   return(ierr);
}

/*

    FUNCTION:  print_range

       Prints <range> to stream file <fp> with optional lead string/delimiter.

*/

#if PROTOTYPE_ALLOWED
void print_range(FILE *fp, RANGE_TYPE *range, char *string)
#else
void print_range(fp, range, string)
FILE *fp;
RANGE_TYPE *range;
char *string;
#endif
{
   fprintf(fp, "%s", string); /* optional lead string, newline and/or delimiter */
   switch(range->type)
   {
      case TIME_RANGE:
         print_ymdhms_time(fp, &(range->ru.time.start));
         fprintf(fp, " to ");
         print_ymdhms_time(fp, &(range->ru.time.end));
         if (fp == stdout) fprintf(fp, "\n");
         return;
      case DAY_RANGE:
         fprintf(fp, "%g to %g", range->ru.day.start, range->ru.day.end);
         if (fp == stdout) fprintf(fp, "\n");
         return;
      case BLOCK_RANGE:
         fprintf(fp, "%d to %d", range->ru.block.start, range->ru.block.end);
         if (fp == stdout) fprintf(fp, "\n");
         return;
      case BLKPRF_RANGE:
         fprintf(fp, "%d %d to %d %d",
            range->ru.blkprf.start.block, range->ru.blkprf.start.profile,
            range->ru.blkprf.end.block, range->ru.blkprf.end.profile);
         if (fp == stdout) fprintf(fp, "\n");
         return;
   }
}

