#include "dbinc.h"
/*****************************************************************************
*                                                                            *
*      COMMON OCEANOGRAPHIC DATA ACCESS SYSTEM (CODAS)                       *
*                                                                            *
*      WRITTEN BY:  RAMON CABRERA, ERIC FIRING, and JULIE RANADA             *
*                   JOINT INSTITUTE FOR MARINE AND ATMOSPHERIC RESEARCH      *
*                   1000 POPE ROAD  MSB 312                                  *
*                   HONOLULU, HI 96822                                       *
*                                                                            *
*      VERSION:     3.10                                                     *
*                                                                            *
*      DATE:        APRIL 1989, AUGUST 1995                                  *
*                                                                            *
*****************************************************************************/
/*

       FILE:  dbopsrch.c

              SYSTEM FUNCTIONS FOR OPENING AND SEARCHING
              AN EXISTING DATABASE

*/
/*-----------------------------------------------------------------------------

   FUNCTION:  DBOPEN

      It opens an existing database.

   PARAMETERS:

      db_id = pointer to database number

      db_name = pointer to database name and path;
                replaced by name without path.

      access_mode = pointer to access mode = 0 for READ_ONLY
                                             1 for READ_WRITE

      memory_mode = pointer to memory mode for directories 
                    0 for directory on disk
                    1 for directory in memory

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBOPEN(int *db_id, char *db_name, int *access_mode, int *memory_mode, int *ierr)
#else
void DBOPEN(db_id, db_name, access_mode, memory_mode, ierr)
int *db_id, *access_mode, *memory_mode, *ierr;
char *db_name;
#endif
{
   int i, id, d[2];
   FILE_NAME_TYPE path_name, path, db_root;          /* 80 characters each */

   if ((*ierr = alignment_check()) != 0)
      return;                                /**jr++**/
   if (first_database_call)
   {
      for (i = 0;  i < MAX_OPEN_DATABASES;  i++)
         database_table[i] = NULL;
      first_database_call = 0;
   }
   *ierr = 0;
   id = *db_id - 1;
   if ((id < 0) || (id >= MAX_OPEN_DATABASES))
   {
      *ierr = INVALID_DATABASE_NUMBER;
      return;
   }
   if (database_table[id])
   {
      db = database_table[id];
      *ierr = DB_ALREADY_OPEN;
      return;
   }
   if ((database_table[id] = (DATABASE_TYPE *) malloc(DATABASE_SIZE)) == NULL)
   {
      db->error_code = INSUFFICIENT_MEMORY;
      db->error_data = BLOCK_DIR;
      goto error_found;
   }
   current_database = id;
   db = database_table[id];
   set_byte((UBYTE *) db, 0, DATABASE_SIZE);
   db->memory_directory = *memory_mode;
   db->access_mode = *access_mode;
   db->block_dir_host = UNKNOWN_HOST; /* to be set in open_block_dir() */
   db->block_host = UNKNOWN_HOST;  /* to be set when block file is opened */

   strtrim(path_name, db_name, sizeof(FILE_NAME_TYPE));
   if (strlen(path_name) + 8 > sizeof(FILE_NAME_TYPE))
   {                         /* need 8 char for file number, ext, NUL */
      *ierr = PATHNAME_TOO_LONG;
      return;
   }
   split_path(path_name, path, db_root);
   db->path_nchar = strlen(path);

   strcpy(db->block_file, path);
   strcpy(db->block_dir_file, path);
   strcat(db->block_dir_file, db_root);
   strcat(db->block_dir_file, "dir.blk");

   if (open_block_dir())
      goto error_found;

   if (strncmp(db->block_dir_hdr.db_version, DATABASE_SYSTEM_VERSION, 2))
   {
      *ierr = INCORRECT_DATABASE_VERSION;
      return;
   }

   if (db->memory_directory)
   {
      if (allocate_memory_for_directories())
         goto error_found;
      if (db->block_dir_hdr.dir_nentries)
         if (load_block_dir())
            goto error_found;
   }

   if (db->block_dir_hdr.dir_nentries)
   {
      d[0] = 0;
      d[1] = 0;
      i = BLOCK_PROFILE_SEARCH;
      DBSRCH(&i, (char *) d, ierr);
   }

   return;

   error_found:
      report_db_error("DBOPEN");
      *ierr = db->error_code * 1000 - db->error_data;
      release_database();
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DBSRCH

      It sets the database pointer to the first profile that satisfies the
      given search parameters.

   PARAMETERS:

      search_type = pointer to type of search requested

      search_parameters = pointer to appropriate parameters
                          for given search type

      ierr = pointer to error code

   RETURNS:  VOID

*/
#if PROTOTYPE_ALLOWED
void DBSRCH(int *search_type, char *search_parameters, int *ierr)
#else
void DBSRCH(search_type, search_parameters, ierr)
int *search_type, *ierr;
char *search_parameters;
#endif
{
   if ((!(db)) || first_database_call)
   {
      *ierr = DB_IS_NOT_OPEN;
      return;
   }
   if (db->create_mode)
   {
      *ierr = OPEN_FOR_CREATE;
      return;
   }
   if (!(db->block_dir_hdr.dir_nentries))
   {
      *ierr = DATABASE_IS_EMPTY;
      return;
   }
   *ierr = 0;
   switch (*search_type)
   {
      case BLOCK_PROFILE_SEARCH:
         if (lock_on_blkprf_index((int *) search_parameters, ierr))
            goto error_found;
         break;
      case TIME_SEARCH:
         if (lock_on_time((YMDHMS_TIME_TYPE *) search_parameters, ierr))
            goto error_found;
         break;
      default:
         *ierr = INVALID_TYPE_REQUEST;
   }
   return;

   error_found:
      report_db_error("DBSRCH");
      *ierr = db->error_code * 1000 - db->error_data;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  lock_on_blkprf_index

   PARAMETERS:

      d = block and profile index array

      ierr = pointer to error code on exit = 0 if okay
                                             NO_SUCH_BLOCK_PROFILE otherwise

   RETURNS:  0 if okay or NO_SUCH_BLOCK_PROFILE
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int lock_on_blkprf_index(int *d, int *ierr)
#else
int lock_on_blkprf_index(d, ierr)
int *d, *ierr;
#endif
{
   int iblock, iprofile, oldblock;

   *ierr = 0;  /*ef*/
   if (((iblock = d[0]) < 0) || ((iprofile = d[1]) < 0) ||
      (iblock >= db->block_dir_hdr.dir_nentries))
   {
      *ierr = NO_SUCH_BLOCK_PROFILE;
      return(0);
   }
   if (iblock == db->block_dir_index)
   {
      if (!(db->block_is_open))
         if (lock_on_block())
            goto error_found;
      if (iprofile >= db->block_hdr.dir_nentries)
      {
         *ierr = NO_SUCH_BLOCK_PROFILE;
         return(0);
      }
      else
      {
         db->profile_dir_index = iprofile;
         if (lock_on_profile())
            goto error_found;
      }
   }
   else
   {
      oldblock = db->block_dir_index;
      if (db->block_is_open)
         if (close_block())
            goto error_found;
      db->block_dir_index = iblock;
      if (lock_on_block())
         goto error_found;
      if (iprofile >= db->block_hdr.dir_nentries)
      {
         *ierr = NO_SUCH_BLOCK_PROFILE;
         db->block_dir_index = oldblock;
         if (oldblock > 0)
            if (lock_on_block())
               goto error_found;
      }
      else
      {
         db->profile_dir_index = iprofile;
         if (lock_on_profile())
            goto error_found;
      }
   }
   return(0);

   error_found:
      report_db_error("lock_on_blkprf_index");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  lock_on_time

      It resets the database to the block and profile with time equal to
      or greater than the time key.

   PARAMETERS:

      t = pointer to YMDHMS_TIME_TYPE structure to search for

      ierr = pointer to error code on exit = 0 if okay
                                             INVALID_TIME if t is not valid
                                             SEARCH_BEFORE_BEGINNING
                                             SEARCH_BEYOND_END

   MODIFIED: Thu  09-22-1988  by E.F. to remove a bug found in
             application of DBSRCH.

*/
#if PROTOTYPE_ALLOWED
int lock_on_time(YMDHMS_TIME_TYPE *t, int *ierr)
#else
int lock_on_time(t, ierr)
YMDHMS_TIME_TYPE *t;
int *ierr;
#endif
{
   ULONG tpck;
   LONG time_ofs;
   int oldind, newind, err_code;  /*ef*/
   YMDHMS_TIME_TYPE t0, t1;
   ULONG tpck0 = 0;
   LONG  sec60 = 60;

   *ierr = 0;  /*ef*/
   if (invalid_time(t))
   {
      *ierr = INVALID_TIME;
      return(0);
   }
   /* Initial check and possible block search is done with
      the key time rounded DOWN to the minute, and converted
      to packed block time.
   */
   tpck = PCKTIM(t);
   /* Search the block directory if no block is open, or if t is
      outside the rounded minute block time range, or if it is
      within the first minute of that range; in the latter case
      the correct profile time could actually be in the preceding
      block, because of the overlap of the rounded block time ranges.
      A fresh search will then find the earlier of the two blocks that
      might contain the profile.
      The opposite case, of t in the last minute of the current range,
      is taken care of later.
   */
   if (db->block_is_open)
   {
      UPCKTIM(&t1, &db->block_hdr.time1);
      DIFTIM(&t1, &t0, &sec60);
      tpck0 = PCKTIM(&t0);
   }
   if (!(db->block_is_open) ||
      (tpck < tpck0) ||
      (tpck > db->block_hdr.time2))
   {
      oldind = db->block_dir_index;  /* save, in case an old block needs closing */
      if (search_for_block_by_time(tpck, ierr))
         goto error_found;
      if ((db->block_is_open) && (oldind != db->block_dir_index))
      {
         /* the old block that is open is not the new one that was found */
         newind = db->block_dir_index;   /*ef*/
         db->block_dir_index = oldind;   /*ef*/
         if (close_block())    /* so close the old one */
            goto error_found;
         db->block_dir_index = newind;   /*ef*/
      }
      if (!db->block_is_open)
         if (lock_on_block())            /*ef*/
            goto error_found;
   }
   switch (*ierr)   /* possible non-fatal error from block search */
   {
      case SEARCH_BEFORE_BEGINNING:
         db->profile_dir_index = 0;
         break;
      case SEARCH_BEYOND_END:
         db->profile_dir_index = db->block_hdr.dir_nentries - 1;
         break;
      case 0:
         time_ofs = HTIMDIF(&(db->block_base_time), t);
         if (db->block_hdr.dir_time_flag == DIR_TIME_IN_SECONDS)
         {
            time_ofs /= 100;
         }
         /* HTIMDIF yields hundredths, regardless of whether
         its arguments use seconds or hundredths; so we just
         calculate hundredths in any case, and then convert
         to seconds if that is what the block is using.
         */
         err_code = search_for_profile_by_time(time_ofs);

         /*
         An err_code of SEARCH_BEFORE_BEGINNING here can be
         ignored; it means the profile landed in a gap
         between blocks.
         If the key is after the last profile in the
         block, check the next block.
         */
         if (err_code == SEARCH_BEYOND_END)
         {
            if (db->block_dir_index >= db->block_dir_hdr.dir_nentries - 1)
               *ierr = SEARCH_BEYOND_END; /* No more blocks to check. */
            else
            {
               if (close_block())
                  goto error_found;
               db->block_dir_index++;
               if (lock_on_block())
                  goto error_found;
               time_ofs = HTIMDIF(&(db->block_base_time), t);
               if ( db->block_hdr.dir_time_flag == DIR_TIME_IN_SECONDS )
               {
                  time_ofs /= 100;
               }
               *ierr = search_for_profile_by_time(time_ofs);
               if (*ierr == SEARCH_BEFORE_BEGINNING)
                  *ierr = 0; /* key was in gap between blocks */
            }
         }

   }
   lock_on_profile();
   return(0);

   error_found:
      report_db_error("lock_on_time");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  search_for_block_by_time

      It searches the block directory for the earliest entry
      that includes the key time, or whose times are greater
      than the key time.  On return, db->block_dir_index
      points to this entry.  The search is based on the
      block end time, not the start time; the earliest block
      with an end time (minutes, rounded up) >= the key
      time.  This could find the block BEFORE the right one,
      but will never find the block AFTER the right one,
      assuming the actual profile time ranges of the blocks
      do not overlap.

   PARAMETERS:

      tpck = packed block time key

      ierr = pointer to returned error code = 0 if entry found
                                              6 if SEARCH_BEYOND_END
                                              7 if SEARCH_BEFORE_BEGINNING

     RETURNS:  0

*/
#if PROTOTYPE_ALLOWED
int search_for_block_by_time(ULONG tpck, int *ierr)
#else
int search_for_block_by_time(tpck, ierr)
ULONG tpck; 
int *ierr;
#endif
{
   long ofs;
   long *key_base;
   int entry_length, nentries;
   int time2_ofs;

   *ierr = 0;
   if (tpck < db->block_dir_hdr.time1)
   {
      db->block_dir_index = 0;
      *ierr = SEARCH_BEFORE_BEGINNING;
   }
   else if (tpck > db->block_dir_hdr.time2)
   {
      db->block_dir_index = db->block_dir_hdr.dir_nentries - 1;
      *ierr = SEARCH_BEYOND_END;
   }
   else
   {
      entry_length = db->block_dir_hdr.dir_entry_nbytes;
      nentries = db->block_dir_hdr.dir_nentries;
      /* Find offset of block time2 (end time rounded up in
         minutes) in block directory entry.
      */
      time2_ofs = sizeof(PRODUCER_ID_TYPE) + sizeof(ULONG);
      if (db->block_dir_loaded)
      {
         key_base = (LONG *) (db->block_dir + time2_ofs);
         db->block_dir_index = DSRCHGE((ULONG *)&tpck,
            (CHAR *)key_base, &entry_length, &nentries);
      }
      else
      {
         ofs = sizeof(BLOCK_DIR_HDR_TYPE) + time2_ofs;
         db->block_dir_index = dir_search_GE_file(db->fpblkdir,
            tpck, ofs, entry_length, nentries,
            db->block_dir_host, HOST_ENVIRONMENT);
      }
      if (db->block_dir_index == -1)
      {
         db->block_dir_index = nentries - 1;
         *ierr = SEARCH_BEYOND_END;     /* jr+ bec. of change in DSRCHGE,... */
      }
   }
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  search_for_profile_by_time

      It searches the profile directory for the earliest entry that has a time
      equal to or greater than the key time.  On return, db->profile_dir_index
      points to this entry.

   PARAMETER:

      time_ofs = number of seconds or hundredths of seconds
                 from the first block time

   RETURNS:  0 if profile found
             SEARCH_BEFORE_BEGINNING
             SEARCH_BEYOND_END


*/
#if PROTOTYPE_ALLOWED
int search_for_profile_by_time(LONG time_ofs)
#else
int search_for_profile_by_time(time_ofs)
/*LONG secs;*/
LONG time_ofs;
#endif
{
   long ofs;
   ULONG *key_base;
   int entry_length, nentries;

   if (time_ofs < 0)
   {
      db->profile_dir_index = 0;
      return(SEARCH_BEFORE_BEGINNING);   /* jr+ */
   }
   entry_length = db->block_hdr.dir_entry_nbytes;
   nentries = db->block_hdr.dir_nentries;

   if (db->profile_dir_loaded)
   {
      key_base = (ULONG *) (db->profile_dir + sizeof(LONG));
      db->profile_dir_index = DSRCHGE((ULONG *)&time_ofs, (CHAR *)key_base,
         &entry_length, &nentries);
   }
   else
   {
      ofs = db->block_hdr.dir_ofs + sizeof(LONG);
      db->profile_dir_index = dir_search_GE_file(db->fpblkdata,
         time_ofs, ofs, entry_length, nentries,
         db->block_host, HOST_ENVIRONMENT);
   }
   if (db->profile_dir_index == -1)
   {
      db->profile_dir_index = nentries - 1;
      return(SEARCH_BEYOND_END);       /* jr+ */
   }
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION: lock_on_block

      It resets the database to the block pointed to by
      db->block_dir_index.

   RETURNS:  0 if okay
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int lock_on_block(void)
#else
int lock_on_block()
#endif
{
   if (load_block_dir_entry())
      goto error_found;
   if (open_block())
      goto error_found;
   if (set_block_access())
      goto error_found;
   return(0);

   error_found:
      report_db_error("lock_on_block");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  lock_on_profile

      It resets the database to the profile pointed to by
      db->profile_dir_index.

   RETURNS:  0 if okay
             <0 otherwise
*/
#if PROTOTYPE_ALLOWED
int lock_on_profile(void)
#else
int lock_on_profile()
#endif
{
   if (load_profile_dir_entry())
      goto error_found;
   if (set_profile_access())
      goto error_found;
   db->profile_is_open = 1;
   return(0);

   error_found:
      report_db_error("lock_on_profile");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  set_block_access()

      It sets block access data structures.

   RETURNS:  0 if okay
             db->error_code otherwise

*/
#if PROTOTYPE_ALLOWED
int set_block_access(void)
#else
int set_block_access()
#endif
{
   if (db->block_hdr.time1 != MAXULONG)
   {
      UPCKTIM(&(db->block_base_time), &(db->block_hdr.time1));
      UPCKTIM(&(db->block_last_time), &(db->block_hdr.time2));
   }
   if (load_data_list())
      goto error_found;
   if (db->profile_dir_in_memory)
      if (load_profile_dir())
         goto error_found;
   return(0);

   error_found:
      report_db_error("set_block_access");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  set_profile_access

      It sets profile access data structures.

   RETURNS:  0 if okay
             READ_ERROR or SEEK_ERROR otherwise

*/
#if PROTOTYPE_ALLOWED
int set_profile_access(void)
#else
int set_profile_access()
#endif
{
   unsigned int nb;
   long ofs;

   nb = db->block_hdr.data_dir_nentries * DATA_DIR_ENTRY_SIZE;
   ofs = db->profile_dir_entry_ptr->ofs;
   if ((db->error_code = read_data(db->fpblkdata, (char *)db->data_dir,
      ofs, nb)) != 0)
   {
      db->error_data = DATA_DIR;
      goto error_found;
   }
   if (db->block_host != HOST_ENVIRONMENT)
   {
      if (convert_array_struct((char *) db->data_dir, (char *) db->data_dir,
         db->block_host, HOST_ENVIRONMENT,
         db->block_hdr.data_dir_nentries, "data_dir_entry",
         &(dbint_sd[0].data_dir_entry)) == BADUINT)
      {
         db->error_code = CONVERT_ERROR;
         db->error_data = DATA_DIR;
         goto error_found;
      }
   }
   return(0);

   error_found:
      report_db_error("set_profile_access");
      return(db->error_code);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  load_block_dir_entry()

      It loads the block directory entry pointed to by db->block_dir_index
      into *db->block_dir_entry_ptr

   RETURNS:  0 if okay
             READ_ERROR OR SEEK_ERROR otherwise

*/
#if PROTOTYPE_ALLOWED
int load_block_dir_entry(void)
#else
int load_block_dir_entry()
#endif
{
   unsigned int nb;
   long ofs;

   nb = db->block_dir_hdr.dir_entry_nbytes;
/*
 ---> IF THE DIRECTORY IS IN MEMORY JUST SET ENTRY POINTER
*/
   if (db->block_dir_loaded)
   {
      db->block_dir_entry_ptr = (BLOCK_DIR_ENTRY_TYPE *) (db->block_dir
         + nb * db->block_dir_index);
   }
/*
 ---> OTHERWISE READ THE ENTRY FROM BLOCK DIRECTORY FILE INTO
      db->block_dir_entry AND THEN SET THE POINTER
*/
   else
   {
      ofs = BLOCK_DIR_HDR_SIZE + nb * db->block_dir_index;
      if ((db->error_code = read_data(db->fpblkdir,
         (char *) &(db->block_dir_entry), ofs, nb)) != 0)
      {
         db->error_data = BLOCK_DIR;
         goto error_found;
      }
      if (db->block_dir_host != HOST_ENVIRONMENT)
      {
         if (convert_struct((char *) &(db->block_dir_entry),
                            (char *) &(db->block_dir_entry),
                            db->block_dir_host, HOST_ENVIRONMENT,
                            "block_dir_entry",
                            &(dbint_sd[0].block_dir_entry)) == BADUINT)
         {
            db->error_code = CONVERT_ERROR;
            db->error_data = BLOCK_DIR;
            goto error_found;
         }
      }
      db->block_dir_entry_ptr = &(db->block_dir_entry);
   }
   return(0);

   error_found:
      report_db_error("load_block_dir_entry");
      return (db->error_code);
}

/*-----------------------------------------------------------------------------

    FUNCTION:  load_profile_dir_entry()

       It loads the profile directory entry pointed to by db->profile_dir_index
       into *db->profile_dir_entry_ptr.

    RETURNS:  0 if okay
              READ_ERROR OR SEEK_ERROR otherwise

*/
#if PROTOTYPE_ALLOWED
int load_profile_dir_entry(void)
#else
int load_profile_dir_entry()
#endif
{
   unsigned int nb;
   long ofs;
   char struct_def_name[20];

   nb = db->block_hdr.dir_entry_nbytes;
/*
 ---> IF THE DIRECTORY IS IN MEMORY JUST SET ENTRY POINTER
*/
   if (db->profile_dir_loaded)
   {
      db->profile_dir_entry_ptr = (PROFILE_DIR_3_ENTRY_TYPE *)
         (db->profile_dir + nb * db->profile_dir_index);
   }
/*
 ---> OTHERWISE READ THE ENTRY FROM PROFILE DIRECTORY FILE INTO
      db->profile_dir_entry AND THEN SET POINTER
*/
   else
   {
      ofs = db->block_hdr.dir_ofs + nb * db->profile_dir_index;
      if ((db->error_code = read_data(db->fpblkdata,
         (char *) &(db->profile_dir_entry), ofs, nb)) != 0)
      {
         db->error_data = PROFILE_DIR;
         goto error_found;
      }
      if (db->block_host != HOST_ENVIRONMENT)
      {
         sprintf(struct_def_name, "profile_dir_%1d_entry",
            (int) db->block_hdr.dir_type);
         if (convert_struct((char *) &(db->profile_dir_entry),
                        (char *) &(db->profile_dir_entry),
                        db->block_host, HOST_ENVIRONMENT,
                        struct_def_name,
                        &(dbint_sd[0].profile_dir_0_entry)) == BADUINT)
         {
            db->error_code = CONVERT_ERROR;
            goto error_found;
         }
      }
      db->profile_dir_entry_ptr = &(db->profile_dir_entry);
   }
   return(0);

   error_found:
      report_db_error("load_profile_dir_entry");
      return (db->error_code);
}

