                      /* readdb.c, 18 May 11 */

/*
 * Extract information from ndb (number basebase) files.
 * The text file format is:

# Hash starts a comment line
# A | specifies a directive which is an identifier followed
# by some text.
! title|msgprefix  some text

digit_sequence message which may contain printf conversion specifiers

 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "global.h"
#include "errors.h"
#include "isprop.h"
#include "fltval.h"
#include "printnum.h"
#include "matchnum.h"
#include "utils.h"
#include "readdb.h"

#define DB_SEPERATOR '|'
#define MAX_LINE_LEN 1024
#define MAX_INCLUDE_NEST 10

struct {
	char *dir_path;
	FILE *file_ptr;
	} ndb_file[MAX_INCLUDE_NEST];

BOOL do_trace_ndb = FALSE;
CHAR cur_db_msg[MAX_MSG_LEN];
CHAR *msg_prefix,
     *msg_postfix,
     *msg_ptr,
     *ndb_title,
     *db_path;
DIGIT_CHAR cur_num_mantissa[MAX_NUM_DIGITS];
DIGIT_CHAR cur_num_exponent[MAX_NUM_DIGITS];
int cur_mant_len,
    cur_exp_len,
    cur_msg_len,
    cur_line_len,
    msg_postfix_len,
    msg_prefix_len,
    ndb_title_len;

static int include_nesting = -1;
static struct need_num_rec *cur_need_info = NULL;
static int cur_need_num_cnt; // Number of numbers in the current need sequence
static int need_seq_num;  // Number of !need seen

static CHAR line_buf[MAX_LINE_LEN]; // Current input line


void read_db_line(void)
{
cur_line_len=0;
// Read a line
if (fgets(line_buf, MAX_LINE_LEN, ndb_file[include_nesting].file_ptr) == NULL)
   return;

for (; cur_line_len < MAX_LINE_LEN; cur_line_len++)
   if (line_buf[cur_line_len] == NULL_CHAR)
      {
      cur_line_len--; // Assume newline is one character long
      line_buf[cur_line_len]=NULL_CHAR;
      break;
      }

// printf("%d:%s\n", cur_line_len, line_buf);
}


int skip_space(int ch_offset)
{
while (isspace(line_buf[ch_offset]) && (ch_offset < cur_line_len))
   ch_offset++;
return ch_offset;
}


void read_db_num(int cur_num_start)
{
int dig_num,
    cur_num_end;

cur_exp_len = 0;
cur_mant_len=0;

if (is_mantissa_sign(line_buf[cur_num_start]))
   cur_num_start++;

cur_num_start=skip_space(cur_num_start); // Any whitespace after optional sign

if (!(is_dec_point(line_buf[cur_num_start]) ||
      is_start_digit(line_buf[cur_num_start])))
   {
   cur_num_mantissa[0]='1';
   cur_mant_len=1;
   return;
   }

cur_num_end = cur_num_start;
while ((line_buf[cur_num_end] != DB_SEPERATOR) && (cur_num_end < cur_line_len))
   cur_num_end++;

// Allow spaces between mantissa digits
for (dig_num=cur_num_start; dig_num < cur_num_end; dig_num++)
   {
   if (is_exp_prefix(line_buf[dig_num]) ||
       (line_buf[dig_num] == '(') ||
       (line_buf[dig_num] == '['))
      break;
   if ((!isspace(line_buf[dig_num])) && (line_buf[dig_num] != '_'))
      {
      if (cur_mant_len < MAX_NUM_DIGITS)
	 {
	 cur_num_mantissa[cur_mant_len]=line_buf[dig_num];
	 cur_mant_len++;
	 }
      }
   }

// TODO handle accuracy specifications
if ((line_buf[dig_num] == '(') ||
    (line_buf[dig_num] == '['))
   {
   while ((line_buf[dig_num] != ')') &&
          (!is_exp_prefix(line_buf[dig_num])) &&
          (line_buf[dig_num] != DB_SEPERATOR) && (dig_num < cur_line_len))
      dig_num++;
   if ((line_buf[dig_num] != ')') && (line_buf[dig_num] == ']'))
      print_warning("Missing ')' in ndb numeric value\n");
   else
      dig_num++;
   }

dig_num=skip_space(dig_num);

if (is_exp_prefix(line_buf[dig_num]))
   {
   dig_num++;  // skip exponent prefix
   for (; dig_num < cur_num_end; dig_num++)
      {
      if (cur_exp_len < MAX_NUM_DIGITS)
         {
         cur_num_exponent[cur_exp_len]=line_buf[dig_num];
         cur_exp_len++;
         }
      }
   }
if (cur_exp_len < MAX_NUM_DIGITS)
   cur_num_exponent[cur_exp_len]=NULL_CHAR;
else
   cur_num_exponent[MAX_NUM_DIGITS-1]=NULL_CHAR;

// printf("db num %s\n", cur_num_mantissa);
}


void process_msg(char *msg_ptr,
                 int msg_start)
{
int ind;

cur_msg_len=0;

msg_start++; // Just skip the first space

// Convert any 'printf' escape sequences in the message
for (ind=msg_start; ind < cur_line_len; ind++)
   {
   if (line_buf[ind] == '\\')
      {
      if (line_buf[ind+1] == 'n')
	 msg_ptr[cur_msg_len]='\n';
      else if (line_buf[ind+1] == 't')
	 msg_ptr[cur_msg_len]='\t';
      else if (line_buf[ind+1] == 'r')
	 msg_ptr[cur_msg_len]='\r';
      else if (line_buf[ind+1] == 'f')
	 msg_ptr[cur_msg_len]='\f';
      else if (line_buf[ind+1] == 'a')
	 msg_ptr[cur_msg_len]='\a';
      else if (line_buf[ind+1] == 'b')
	 msg_ptr[cur_msg_len]='\b';
      else if (line_buf[ind+1] == 'v')
	 msg_ptr[cur_msg_len]='\v';
      else if (line_buf[ind+1] == '\\')
	 msg_ptr[cur_msg_len]='\\';
      else if (line_buf[ind+1] == '"')
	 msg_ptr[cur_msg_len]='\"';
      else if (line_buf[ind+1] == '\'')
	 msg_ptr[cur_msg_len]='\'';
      else if (line_buf[ind+1] == '?')
	 msg_ptr[cur_msg_len]='\?';
      else
	 {
	 msg_ptr[cur_msg_len]=line_buf[ind];
	 cur_msg_len++;
	 msg_ptr[cur_msg_len]=line_buf[ind+1];
	 }
      ind++;
      }
   else
      msg_ptr[cur_msg_len]=line_buf[ind];
   cur_msg_len++;
   }
msg_ptr[cur_msg_len]=NULL_CHAR;

// printf("db msg %s\n", msg_ptr);
}

void get_msg_pfix(int prefix_offset)
{
// Get character sequence that occurs after ! msgprefix/msgpostfix
msg_ptr=get_heap(sizeof(char)*(cur_line_len-prefix_offset+1));

if (msg_ptr != NULL)
   process_msg(msg_ptr, prefix_offset);
}


void get_ndb_title(int prefix_offset)
{
// Get character sequence that occurs after ! title
ndb_title=get_heap(sizeof(char)*(cur_line_len-prefix_offset+1));

if (ndb_title != NULL)
   {
   process_msg(ndb_title, prefix_offset);
   ndb_title_len=cur_msg_len;
   }
}


void get_word_list(int prefix_offset)
{
// Get word list that occurs after ! word
}


void read_db_msg(void)
{
int cur_msg_start;

cur_msg_start=0;

while ((line_buf[cur_msg_start] != DB_SEPERATOR) &&
       (cur_msg_start < cur_line_len))
   cur_msg_start++;

if ((cur_need_info != NULL) &&
   (line_buf[cur_msg_start] == DB_SEPERATOR))
   print_warning("Number message in !need sequence is suspicious\n");

cur_msg_start++;
process_msg(cur_db_msg, cur_msg_start);
}


void finalise_need(void)
{
cur_need_info=NULL;
}


static CHAR *mk_new_msg(void)
{
CHAR * msg=get_heap(sizeof(CHAR)*(msg_prefix_len+msg_postfix_len+cur_msg_len+2));

if (msg == NULL)
   return NULL;

memcpy(msg, msg_prefix, sizeof(CHAR)*msg_prefix_len);
memcpy(msg+msg_prefix_len, cur_db_msg, sizeof(CHAR)*cur_msg_len);
memcpy(msg+msg_prefix_len+cur_msg_len, msg_postfix, sizeof(CHAR)*msg_postfix_len);
msg[msg_prefix_len+cur_msg_len+msg_postfix_len]='\0';

return msg;
}


void start_need(char *need_str)
{
cur_need_info=get_heap(sizeof(struct need_num_rec));
cur_need_num_cnt=0;
if (cur_need_info == NULL)
   {
   print_error("Unable to allocate storage: start_need_1");
   return;
   }
cur_need_info->num_list=get_heap(DFLT_NEED_SIZE*sizeof(struct need_db_rec *));
if (cur_need_info->num_list == NULL)
   {
   print_error("Unable to allocate storage: start_need_2");
   finalise_need();
   return;
   }
need_seq_num++;
cur_need_info->need_id=need_seq_num;
cur_msg_len=0; // Remove any previous current message
cur_need_info->msg=mk_new_msg();
cur_need_info->ndb_title=ndb_title;
cur_need_info->list_len=0;
cur_need_info->max_list_len=DFLT_NEED_SIZE;
cur_need_info->num_mantissa_matches=0;
cur_need_info->notify_level=0;
cur_need_info->seen_need_num=NULL;
}


static void add_need_num(struct num_db_rec *num_ptr)
{
/*
 * If we are in a need list create appropriate information.
 */
num_ptr->need_info=cur_need_info;
if (cur_need_info == NULL)
   return;

if (cur_need_info->list_len == cur_need_info->max_list_len)
   {
   cur_need_info->num_list=realloc(cur_need_info->num_list,
				(DFLT_NEED_SIZE+cur_need_info->list_len)*
						sizeof(struct need_db_rec *));
   if (cur_need_info->num_list == NULL)
      {
      print_error("Unable to allocate storage: add_need_num");
      finalise_need();
      return;
      }
   cur_need_info->max_list_len+=DFLT_NEED_SIZE;
   }
cur_need_info->num_list[cur_need_info->list_len]=num_ptr;
cur_need_info->list_len++;
num_ptr->need_num=cur_need_num_cnt;
cur_need_num_cnt++;
}


static void store_num_info(void)
{
/*
 * Store everything given about a number.
 */
struct num_db_rec *num_ptr = malloc(sizeof(struct num_db_rec));

if (num_ptr == NULL)
   {
   print_warning("Unable to allocate storage: store_num_info_1\n");
   return;
   }

fp_init(&num_ptr->num_valu);
if (!string_to_fp(&num_ptr->num_valu, cur_num_mantissa, cur_mant_len,
							cur_num_exponent))
   {
// If we are using GMP there is no legitimate reason
// for not being able to convert a ndb number.
#if !HAVE_GMP
   if (do_trace_ndb)
#endif
      print_warning("Failed to convert number in ndb file\n");
// print_number(cur_num_mantissa, cur_mant_len, cur_num_exponent);
   return;
   }

fp_init(&num_ptr->match_fuzz);
fp_assign(&num_ptr->match_fuzz, default_fuzz_val);
simplify_match_num(cur_num_mantissa, cur_mant_len);
if (num_simp_digits == 0)
   {
   print_warning("Zero value in ndb file\n");
   return;
   }

num_ptr->mantissa=malloc(sizeof(DIGIT_CHAR)*(cur_mant_len+1));
num_ptr->simp_mant=malloc(sizeof(DIGIT_CHAR)*(num_simp_digits+1));
num_ptr->exponent=malloc(sizeof(DIGIT_CHAR)*(cur_exp_len+1));

if ((num_ptr->mantissa == NULL) ||
    (num_ptr->simp_mant == NULL))
   {
   print_warning("Unable to allocate storage: store_num_info_2\n");
   return;
   }

memcpy(num_ptr->mantissa, cur_num_mantissa, sizeof(DIGIT_CHAR)*cur_mant_len);
num_ptr->mantissa[cur_mant_len]='\0';
num_ptr->len_mantissa=cur_mant_len;
memcpy(num_ptr->simp_mant, simp_digit_seq, sizeof(DIGIT_CHAR)*num_simp_digits);
num_ptr->simp_mant[num_simp_digits]='\0';
num_ptr->simp_mant_len=num_simp_digits;
memcpy(num_ptr->exponent, cur_num_exponent, sizeof(DIGIT_CHAR)*cur_exp_len);
num_ptr->exponent[cur_exp_len]='\0';

if (cur_need_info == NULL)
   num_ptr->msg=mk_new_msg();
else
   num_ptr->msg=""; // Simplifies things if the value is always a non-null
num_ptr->ndb_title=ndb_title;

if (do_trace_ndb)
   {
   printf("| "); fp_print(num_ptr->num_valu); printf("\n");
   print_number(num_ptr->simp_mant, num_simp_digits, "");
   printf("%s\n", num_ptr->msg);
   }
// printf("%c %d\n", num_ptr->simp_mant[0], num_simp_digits);

add_need_num(num_ptr);
num_ptr->next=special_numbers.num_info[num_ptr->simp_mant[0]-'0'];
special_numbers.num_info[num_ptr->simp_mant[0]-'0']=num_ptr;
}


void get_directive(int ch_offset)
{
int id_len = 0;
CHAR directive[MAX_MSG_LEN];

ch_offset=skip_space(ch_offset+1); // Skip !

while ((!isspace(line_buf[ch_offset])) && (ch_offset < cur_line_len))
   {
   if (isalpha(line_buf[ch_offset]) && (id_len < MAX_MSG_LEN))
      {
      directive[id_len]=tolower(line_buf[ch_offset]);
      id_len++;
      }
   ch_offset++;
   }

directive[id_len]=NULL_CHAR;

if (strcmp(directive, "fuzz") == 0)
   {
   get_msg_pfix(ch_offset);
   set_fuzz_val(&default_fuzz_val, msg_ptr);
   }
else if (strcmp(directive, "include") == 0)
   {
   get_msg_pfix(ch_offset);
   process_number_list("", msg_ptr);
   }
else if (strcmp(directive, "msgpostfix") == 0)
   {
   get_msg_pfix(ch_offset);
   msg_postfix=msg_ptr;
   msg_postfix_len=cur_msg_len;
   }
else if (strcmp(directive, "msgprefix") == 0)
   {
   get_msg_pfix(ch_offset);
   msg_prefix=msg_ptr;
   msg_prefix_len=cur_msg_len;
   }
else if (strcmp(directive, "need") == 0)
   {
   finalise_need();
   get_msg_pfix(ch_offset);
   start_need(msg_ptr);
   }
else if (strcmp(directive, "title") == 0)
   get_ndb_title(ch_offset);
else if (strcmp(directive, "word") == 0)
   get_word_list(ch_offset);
else
   print_warning("Unknown directive: %s\n", line_buf);
}


char *ndb_file_path(char *dir_path, char * filename)
{
int path_len=strlen(filename);
char *new_dir_path;

// printf("%s  %s\n", dir_path, filename);

while (path_len > DSEP_LEN-1)
   {
// Support both the host environment directory separator and the
// *nix directory separator.
   if ((strncmp(filename+path_len-DSEP_LEN,
                                     directory_separator, DSEP_LEN) == 0) ||
        (strncmp(filename+path_len-1, "/", 1) == 0))
      {
      new_dir_path=get_heap(strlen(dir_path)+path_len+1);
      strcpy(new_dir_path, dir_path);
      strncat(new_dir_path, filename, path_len);
      return new_dir_path;
      }
   else
      path_len--;
   }
new_dir_path=get_heap(strlen(dir_path)+1);
strcpy(new_dir_path, dir_path);
return new_dir_path;
}


static void add_number_list(char * dir_path, char * filename)
{
/*
 * Open a file and process all of its contents.
 */
int first_non_blank;
char *alt_ndb_name = filename;

ndb_file[include_nesting].file_ptr = fopen(filename, "r");

if (ndb_file[include_nesting].file_ptr == NULL)
   {
   ndb_file[include_nesting].dir_path = ndb_file_path(dir_path, filename);
   alt_ndb_name=get_heap(strlen(dir_path)+ strlen(filename)+1);
   strcpy(alt_ndb_name, dir_path);
   strcat(alt_ndb_name, filename);
   if (do_trace_ndb)
      printf("Trying %s\n", alt_ndb_name);
   ndb_file[include_nesting].file_ptr = fopen(alt_ndb_name, "r");
   }
else
   ndb_file[include_nesting].dir_path = ndb_file_path("", filename);

if (ndb_file[include_nesting].file_ptr == NULL)
   {
   print_warning("Could not open: %s\n", filename);
   return;
   }

if (do_trace_ndb)
   printf("Opened %s\n", alt_ndb_name);

msg_postfix_len=0;
msg_prefix_len=0;
ndb_title=filename;
ndb_title_len=strlen(ndb_title);
while (!feof(ndb_file[include_nesting].file_ptr))
   {
   read_db_line();
   if (cur_line_len > 0)
      {
      first_non_blank=skip_space(0);
      if (first_non_blank == cur_line_len)
	 continue;
      if (line_buf[first_non_blank] == '!')
	 get_directive(first_non_blank);
      else if (line_buf[first_non_blank] != '#')
         {
         read_db_num(first_non_blank);
         read_db_msg();
         store_num_info();
         }
      }
   }

finalise_need();
fclose(ndb_file[include_nesting].file_ptr);
if (include_nesting != 0)
   free(ndb_file[include_nesting].dir_path);
}


void process_number_list(char * start_db_path,
			 char * filename)
{
// Extract information about a list of numbers from filename
// filename may be given in !include on on command line

fp_t cur_fuzz_val;

// Save current fuzz value and restore at the end of the include
fp_init(&cur_fuzz_val);
fp_assign(&cur_fuzz_val, default_fuzz_val);

if (include_nesting == MAX_INCLUDE_NEST)
   {
   print_warning("!include %s: nesting level exceeds %d\n",
					 filename, MAX_INCLUDE_NEST);
   return;
   }

include_nesting++;
if (include_nesting == 0)
   add_number_list(start_db_path, filename);
else
   add_number_list(ndb_file[include_nesting-1].dir_path, filename);
include_nesting--;

fp_assign(&default_fuzz_val, cur_fuzz_val);
}

