/*
   ldcodas.c: general CODAS database loader.

*/

#include <geninc.h>
#include <ioserv.h>
#include <use_db.h>
#include <data_id.h>
#include <data_lst.h>
#include <dblscale.h>

#include <dbinc.h>    /* may be temporary; needed now for access to db */

extern NAME_LIST_ENTRY_TYPE VALUE_CODE[];

/* for message logging: */
static char msg[256];
static FILE *fp_log;

/* load options and variables (could be put in struct) */
static FILE_NAME_TYPE dbname = "", prdname = "", logname = "ldcodas.log";

static int YearBase = -1;

static OPTION_TYPE load_options[] =
{
   {"END"                 , 0          , NULL        , (char *) NULL},
   {"DATABASE_NAME:"      , TYPE_STRING, op_get_param, dbname},
   {"DEFINITION_FILE:"    , TYPE_STRING, op_get_param, prdname},
   {"LOG_FILE:"           , TYPE_STRING, op_get_param, logname},
   {"YEAR_BASE:"          , TYPE_INT   , op_get_param, (char *) &YearBase},
   {NULL                  , 0          , NULL        , (char *) NULL}
};

/* loading commands and variables */

static FILE *fp_bin;

static struct {
   int db_is_open,
       block_is_open,
       profile_is_open;
} dbstatus;
   /* This parallels internal db structures; one could access them
      instead.  In fact, it is not clear to me why the basic calls
      such as DBNEWBLK do not automatically call DBENDBLK when
      a block is open, instead of raising an error.  Rather than
      redesign all that, we will just build a higher level here,
      and maintain the separation between low level calls that
      access the db structure, and user-level calls that don't.
   */

static int bin_file(FILE *fp, char *arg, int code)
{
   FILE_NAME_TYPE fname;

   fscanf(fp, "%150s", fname);
   if (fp_bin) fclose(fp_bin);
   fp_bin = check_fopen(fname, "rb");
   /* OK to exit here on error because we haven't opened the DB yet. */
   return(0);
}

static int new_block(FILE *fp, char *arg, int code)
{
   int error;   /* TODO: add actual error handling */

   if (dbstatus.profile_is_open)
   {
      DBENDPRF(&error);
      DBERROR(&error, "in new_block");
      dbstatus.profile_is_open = 0;
   }
   if (dbstatus.block_is_open)
   {
      DBENDBLK(&error);
      DBERROR(&error, "in new_block");
      dbstatus.block_is_open = 0;
   }
   DBNEWBLK(&error);
   DBERROR(&error, "in new_block");
   dbstatus.block_is_open = 1;
   return(error);
}

static int new_profile(FILE *fp, char *arg, int code)
{
   int error;
   YMDHMS_TIME_TYPE prftime;

   get_time(fp, &prftime);
   if (dbstatus.profile_is_open)
   {
      DBENDPRF(&error);
      DBERROR(&error, "in new_profile");
      dbstatus.profile_is_open = 0;
   }
   DBNEWPRF(&prftime, &error);
   DBERROR(&error, "in new_profile");
   dbstatus.profile_is_open = 1;
   return(error);
}

/* The following is very inefficient, because the
   same info is extracted from the db at each call
   instead of once at the beginning.  But it is
   easier to code this way, so it will do for now.
*/
static int binary_data(FILE *fp, char *arg, int code)
{
   char data_name_buf[40], value_type_buf[40];
   long offset;
   int data_id, bin_value_type, db_value_type;
   int nv, nb, bin_value_size, n;
   unsigned int nbad;
   int ierr;
   static char buf[10000], buf2[10000];

   n = fscanf(fp, "%39s %39s %d %ld",
               data_name_buf, value_type_buf, &nv, &offset);
   if (n != 4) return(1);  /* minimal error check */
   data_id = get_data_list_index(data_name_buf);
   if (data_id == -1) return(1);
   bin_value_type = get_code(VALUE_CODE, value_type_buf);
   bin_value_size = VALUE_SIZE[bin_value_type];
   /* Q&D method, violates intended separation between low-level
      and user-level, but let's try it first.
   */
   db_value_type = db->data_list[data_id].value_type;
   fseek(fp_bin, offset, SEEK_SET);  /* add error check */
   fread(buf, 1, nv * bin_value_size, fp_bin);
   if (bin_value_type == FLOAT_VALUE_CODE)
   {
      DBADD_F(&data_id, (FLOAT *) buf, &nv, &nbad, &ierr);
      DBERROR(&ierr, "in binary_data");
   }
   else if (bin_value_type < FLOAT_VALUE_CODE)
   {
      if (bin_value_size != VALUE_SIZE[db_value_type])
      {
         report_msg("integer size mismatch");
         return(1);
      }
      nb = nv * bin_value_size;
      DBADD(&data_id, buf, &nb, &ierr); /* or use dbadd_cnf? */
      DBERROR(&ierr, "in binary_data");
   }
   else if (bin_value_type == DOUBLE_VALUE_CODE &&
            db_value_type == STRUCT_VALUE_CODE)
   {
      nb = dbl_struc((double *)buf, buf2, data_name_buf, db->block_strdef);
      DBADD(&data_id, buf2, &nb, &ierr);
      DBERROR(&ierr, "in binary_data");
   }
   else
   {
      sprintf(msg, "%s %s %d %ld------value type mismatch\n",
                    data_name_buf, value_type_buf, nv, offset);
      report_msg(msg);
      return(1);
   }
   return ierr;
}

static int dp_mask(FILE *fp, char *arg, int code)
{
   long dpm;
   LONG dpm_L;
   int n, error = 0;

   n = fscanf(fp, "%ld", &dpm);
   dpm_L = dpm;
   if (error_found((n != 1), "scanning dp_mask"))  return (1);
   error = dbadd_cnf(DATA_PROC_MASK, (char *) &dpm_L, sizeof(LONG),
                 "adding dp_mask");
   return(error);
}

static int depth_range(FILE *fp, char *arg, int code)
{
   DEPTH_RANGE_TYPE drt;
   int dr[2];
   int n, error = 0;

   n = fscanf(fp, "%d %d", dr, dr+1);
   if (error_found((n != 2), "scanning depth_range"))  return (1);
   drt.min_depth = dr[0];
   drt.max_depth = dr[1];
   error = dbadd_cnf(DEPTH_RANGE, (char *) &drt, sizeof(DEPTH_RANGE_TYPE),
                 "adding depth_range");
   return(error);
}

static OPTION_TYPE load_commands[] =
{
   {"end"            , 0             , NULL          , (char *) NULL},
   {"binary_file:"   , 0             , bin_file      , (char *) NULL},
   {"new_block"      , 0             , new_block     , (char *) NULL},
   {"new_profile:"   , 0             , new_profile   , (char *) NULL},
   {"binary_data:"   , 0             , binary_data   , (char *) NULL},
   {"dp_mask:"       , 0             , dp_mask       , (char *) NULL},
   {"depth_range:"   , 0             , depth_range   , (char *) NULL},
   {NULL             , 0             , NULL          , (char *) NULL}
};

void do_it(FILE *fpcnt)
{
   int ierr, db_id = 1, memmode = DIR_IN_MEMORY;

   if (execute_options(fpcnt, load_options, ECHO) <= 0)
   {
      fprintf(stderr, "\n ERROR: executing load_options.\n");
      return;
   }
   fp_log = check_fopen(logname, "w");
   set_msg_file(fp_log);

   if (!strcmp(dbname, "") || !strcmp(prdname, "") || YearBase < 0)
   {
      report_msg(
         "\n ERROR: DATABASE_NAME:, DEFINITION_FILE:, and YEAR_BASE:\n\
         must be specified in the control file\n\n");
      return;
   }

   DBCREATE(&db_id, dbname, prdname, &memmode, &ierr);
   DBERROR(&ierr, "DBCREATE in do_it");    printf("\n->%s<-\n", dbname);
   if (ierr) return;
   dbstatus.block_is_open = 0;
   dbstatus.profile_is_open = 0;

   if (execute_options(fpcnt, load_commands, ECHO) <= 0)
   {
      report_msg("\n ERROR: executing load_commands.\n");
   }
   if (dbstatus.profile_is_open)
   {
      DBENDPRF(&ierr);
      DBERROR(&ierr, "in do_it");
   }
   if (dbstatus.block_is_open)
   {
      DBENDBLK(&ierr);
      DBERROR(&ierr, "in do_it");
   }
   DBCLOSE(&ierr);
   DBERROR(&ierr, "in do_it");

}
