/**************************************************************************
* File:	       pds.c
* Programmers: Anne Raugh
*              Dorian Winterfeld
* Institution: PDS/Small Bodies Node
*              University of Maryland
* Date:	       Feb. 13, 1995
* Abstract:    Function implementations for PDS utilities.
* Description: This file contains functions for reading in the PDS
*	       label and building the FITS header object.
***************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "pds.h"
#include "fits_obj.h"
#include "pds_tbl.h"
#include "pdserror.h"
#include "pds_util.h"
#include "linklist.h"

/* global variables */
extern FILE   *PDS_file;
extern FILE   *log_file;

/**************************************************************************
* Function: read_PDS_file
* Abstract: Routine to step through the PDS label records and assign the
*           appropriate value strings the the FITS structure elements.
*
* Description: As each line is read, it is first broken into PDS keyword string
*    and value string.  The PDS object type is prepended onto the keyword,
*    except for the implicit FILE object.  A search is made of the translation
*    table to match this name exactly.   A warning is issued if the keyword is
*    not matched; otherwise the value string is then assigned
*    to all appropriate FITS elements.  Certain values are stored for
*    calculated FITS fields such as NAXIS.
* Input
*	main_object: pointer to FITS object.
*
* Return
*	1 on success, 0 on failure.
***************************************************************************/
int read_PDS_file(FITS_object *main_object,table *ttable,table *ltable)
{
	char	   line[100];        /* holds a line from a PDS label */
        char       test_buf[100];    /* used for testing */
	char       object_path[100]; /* PDS object path */
	char       type[20];         /* keyword data type */
	char       key[100];         /* PDS keyword */
	char	   key_str[200];     /* full keyword string; includes path */
	char	   value[10000];     /* PDS value string */
	int        first = TRUE;     /* first object encountered */
	int        len;
	int 	   status = OK;
	int        i;
	table_element *entry;
	list_type     *object_list;       /*keeps track of PDS objects */


	object_list = list_create();
	object_path[0] = '\0';
	skip_blanks(PDS_file,line);
	status = split_pds_line(line,key,value);

/* loop thru PDS file */
	while (strcmp(key,"END")) {
/* look for OBJECT and build object path */
		if (strcmp(key,"OBJECT") == 0 ) {
			if (first == TRUE){
				strcpy(object_path,value);
				strcat(object_path,".");
				first = FALSE;
			}
			else {
				strcat(object_path,value);
				strcat(object_path,".");
			}
			insert(object_list,value);
		}
		else
/* look for END_OBJECT and pop object path */
		if (strcmp(key,"END_OBJECT") == 0){
			i = strlen(object_path) - 2;
			while ((object_path[i] != '.') && (i > 0)) i--;
			object_path[i] = '\0';
			if (i > 0) strcat(object_path,".");
		}
		else
/* look for NOTE or DESCRIPTION */
		if ((strcmp(key,"NOTE") == 0) || (strcmp(key,"DESCRIPTION") == 0)) {
			if (strcmp(value,"\"") == 0){
				fgets(line,100,PDS_file);
				strcpy(test_buf,line);
				eat_white(test_buf);
				len = strlen(test_buf);
				strcpy(value,line);
				strcat(value,"\n");
			}
			else {
                                strcpy(test_buf,line);
				eat_white(test_buf);
				len = strlen(test_buf);
				strcat(value,"\n");
			}
			while (test_buf[len-1] != '\"'){
				fgets(line,101,PDS_file);
                                strcpy(test_buf,line);
				eat_white(test_buf);
				len = strlen(test_buf);
				strcat(value,line);
				strcat(value,"\n");
			}
		}
/* build key string */
		if (object_path[0] == '\0')
			strcpy(key_str,key);
		else {
			strcpy(key_str,object_path);
			strcat(key_str,key);
		}
/* search translation table for key string */
		entry = find_pds_keyword(ttable,key_str,NULL);

/* if key found then set value else add to log file */
		if (entry == NULL)
			fprintf(log_file,"%s\n",key_str);
		else {
		        if (!strcmp(entry->FITS_type,"NULL")){
			  search_PDSDD(key);
			  get_PDS_type(type);
			}
			else
			  strcpy(type,entry->FITS_type);
			set_value(value,type,entry,object_list,main_object);
		}
/* fetch next line */
		skip_blanks(PDS_file,line);
		status = split_pds_line(line,key,value);
		if (status <= ERROR) return status;
	} /* end while loop */
/* add literal table values */
	add_lit_table(main_object,ltable);
/* now add calculated fields for specific object type */
	post_process(main_object,ttable);

	return status;
}  /* end read_PDS */


/**************************************************************************
* Function: find_pds_keyword
* Abstract: Routine to search the internal translation table for a given
*   	PDS keyord.
* Description: This routine attempts to find a matching translation table entry
*      for the given key.
*      The table is assumed to be sorted in alphabetic order.  First attempt is
*      to match exactly.  Failing that  the search is repeated with "[*]" after
*      every object in the pds key string.  If the no match is found then
*      return NULL
* Input
*		ttable 		pointer to translation table
*		key    		PDS keyword string
*		start_here  pointer to start element in table
* Return
*		table_element pointer to table element
***************************************************************************/
table_element    *find_pds_keyword(table *ttable,char *key,
                                   table_element *start_here)
{
  table_element *entry;
  char          object_str2[100];
  char          modified_key[100];
  int           len;
  int           dots[10];
  int 		i;
  int		dot_cnt = 0;

/* Exact match search: */

  if (start_here == NULL)
		 entry = ttable->elements;
  else
		 entry = start_here;

  while (entry != NULL) {
	if (strcmp(entry->PDS_string,key) == 0)
		return entry;
	else
		entry = entry->next;
  }

  /* If you made it this far, no success. Try again with wildcard "*" */
  /* parse PDS string into individual objects and store in linllist*/
		len = strlen(key);
		for (i=0; i<len; i++) {
			if (key[i] == '.'){
				dots[dot_cnt] = i;
				dot_cnt++;
			}
		}
		for (i=0; i<dot_cnt; i++){
			strncpy(modified_key,key,dots[i]);
			modified_key[dots[i]] = '\0';
			sub_strcpy(object_str2,key,dots[i],len-dots[i]);

			strcat(modified_key,"[*]");
			strcat(modified_key,object_str2);


/* now search with modified string */
			if (start_here == NULL)
				entry = ttable->elements;
			else
				entry = start_here;

			while (entry != NULL) {
				if (strcmp(entry->PDS_string,modified_key) == 0)
					return entry;
				else
					entry = entry->next;
			}

		}
  /* Still no success: */

  return NULL;
} /* end find_pds_keyword */


/**************************************************************************
* Function: set_value
* Abstract: Routine to assign the value string to the appropriate FITS keyword.
*          Objects and keywords are created on the fly as they are needed.
* Description:
* Input
* Return
***************************************************************************/
int set_value(char *value, char *type,table_element *entry, 
	      list_type *object_list, FITS_object *main_object)
{
	int  N = 1;
	char N_string[4];
	int  i;
	int  len;
	int  object_cnt;
	char obj_cnt_str[5];
	char value_copy[100];
	char PDS_copy[100];
	char *element;
	char *object1;
	char *object2;
	char *object;
	char modified_string[100];
	int  found = FALSE;
	FITS_keyword *FITSkey;
	list_type *element_list;
	list_node *node;

/* Determine if this is a  1->* relation */
	if (one_to_N(entry->PDS_string,entry->FITS_string)) {
		element_list = list_create();
		strcpy(value_copy,value);
		eat_white(value_copy);
		if (value_copy[0] == '{'){
			len = strlen(value_copy);
			value_copy[0] = ' ';
			value_copy[len-1] = ' ';
			eat_white(value_copy);
		}
		element = strtok(value_copy,",");
		while (element != NULL) {
			insert(element_list,element);
			element = strtok(NULL,",");
		}
		node = element_list->head;
		while (node != NULL) {
			strcpy(modified_string,entry->FITS_string);
			len = strlen(modified_string);
			modified_string[len-1] = '\0';
			sprintf(N_string,"%d",N);
			strcat(modified_string,N_string);
			FITSkey = find_FITS_keyword(modified_string,main_object);
			len = strlen(node->data);
			FITSkey->value = (char *)malloc(len + 1);
			if (FITSkey->value == NULL) return MALLOC_ERROR;
			strcpy(FITSkey->value,node->data);
			strcpy(FITSkey->type,type);
			N++;
			node = node->next;
		}
	}
	else
/* Determine if this is a *->* relation */
	if (NtoN(entry->PDS_string,entry->FITS_string)) {
/* first find PDS object containing "[*]" from PDS key string*/
		strcpy(PDS_copy,entry->PDS_string);
		object1 = strtok(PDS_copy,".");
		while (found == FALSE) {
			object2 = object1;
			len = strlen(object1);
			for (i=0;i<len;i++) if (object1[i] == '*') found = TRUE;
			object1 = strtok(NULL,".");
		}
/* now count how many objects of that type are in object_list */
		object = strtok(object2,"[");
		object_cnt = how_many_objects(object_list, object);
		sprintf(obj_cnt_str,"%d",object_cnt);
		strcpy(modified_string,entry->FITS_string);
		strtok(modified_string,"*");
		strcat(modified_string,obj_cnt_str);
		FITSkey = find_FITS_keyword(modified_string,main_object);
		len = strlen(value);
		FITSkey->value = (char *)malloc(len + 1);
		if (FITSkey->value == NULL) return MALLOC_ERROR;
		strcpy(FITSkey->value,value);
		strcpy(FITSkey->type,type);
	}
	else {
/* must be a 1->1 relation */
		FITSkey = find_FITS_keyword(entry->FITS_string, main_object);
		len = strlen(value);
		FITSkey->value = (char *)malloc(len + 1);
		if (FITSkey->value == NULL) return MALLOC_ERROR;
		strncpy(FITSkey->value,value,len+1);
		strcpy(FITSkey->type,type);
	}
	return OK;
} /* end set_value */


/**************************************************************************
* Function: find_FITS_keyword
* Abstract: Workhorse routine to locate a specific keyword and extension,
*				if one exists.
* Description:
* Input
* Return
***************************************************************************/
FITS_keyword *find_FITS_keyword(char *key, FITS_object *object)
{
	char *ext_ptr;
	char *key_ptr;
	FITS_keyword *keyword_ptr;
	FITS_object *extension_ptr;
	char key_copy[100];

/*  SPLIT key into extension and keyword  */
	strcpy(key_copy,key);
	ext_ptr = strtok(key_copy,".");
	key_ptr = strtok(NULL,".");

	if (key_ptr == NULL)
		keyword_ptr = find_keyword_in_object(key,object);
	else {
		extension_ptr = find_extension_in_object(ext_ptr,object);
		keyword_ptr = find_keyword_in_object(key_ptr,extension_ptr);
	}

	return keyword_ptr;
} /* end find_FITS_keyword */


/**************************************************************************
* Function: find_extension_in_object
* Abstract: Routine to find a given extension object given the main header
*           object.  If it is not found, it is created.
* Description:
* Input
* Return
***************************************************************************/
FITS_object  *find_extension_in_object(char *extension,FITS_object *object)
{
	FITS_object *ext_obj;

	ext_obj  = object->next;
	while (ext_obj != NULL) {
		if (strcmp(ext_obj->name,extension) == 0)
			return ext_obj;

		ext_obj = ext_obj->next;
	}

/* Not found.  Create it: */

	ext_obj = create_FITS_object(extension, object);

  return ext_obj;
} /* end find_extension_in_object */
   


/**************************************************************************
* Function: find_keyword_in_object
* Abstract: Routine to find a given keyword in the given object.  If the
*           keyword is not found, it is created.
* Description:
* Input
* Return
***************************************************************************/
FITS_keyword *find_keyword_in_object(char *keyword,FITS_object *object)
{
	FITS_keyword *key;

	key = object->keyword_list;
	while (key != NULL) {
		if (strcmp(key->name,keyword) == 0)
			return key;

		key = key->next;
	}

/* Not found.  Create it: */

	key = create_FITS_keyword(keyword, object);

	return key;
}  /* end find_keyword_in_object */


/****************************************************************************
* Function: how_many_objects
* Abstract: counts number of
****************************************************************************/
int how_many_objects(list_type *object_list,char *object)
{
	list_node *node_ptr;
	int       object_cnt = 0;

	node_ptr = object_list->head;

	while (node_ptr != NULL) {
		if (strcmp(node_ptr->data,object) == 0)
			object_cnt++;
		node_ptr = node_ptr->next;
	}

	return object_cnt;
} /* end how_many_objects */

/*****************************************************************************
* Function: add_lit_table
* Abstract: add the keywords from the literal table to the FITS objects.
* Input
*	ltable: literal table pointer.
*	main_object: pointer to main FITS object.
*****************************************************************************/
int add_lit_table(FITS_object *main_object,table *ltable)
{
	table_element *entry; /* pointer to table element object */
	char *value;          /* value string */
	FITS_keyword *FITSkey;        /* pointer to FITS keyword */
	int  len;

/* start at beginning of literal table */
	entry = ltable->elements;

	while (entry != NULL){

/* get the PDS value string */
	  value = entry->PDS_string;

/* remove the quotes */
	  strip_quotes(value);	

/* set the value field of the corresponding FITS object */
	  FITSkey = find_FITS_keyword(entry->FITS_string, main_object);
	  len = strlen(value);
	  FITSkey->value = (char *)malloc(len + 1);
	  if (FITSkey->value == NULL) return MALLOC_ERROR;
	  strcpy(FITSkey->value,value);
          strcpy(FITSkey->type,"LIT_STR");
	  
	  entry = entry->next;		
	}
	return OK;
} /* end add_lit_table */

/*****************************************************************************
* Function: post_process
* Abstract:
* Input
*
*****************************************************************************/
int post_process(FITS_object *main, table *ttable)
{
	if (!strcmp(ttable->PDS_form,"BINARY_TABLE"))
		process_binary_table(main);
	else
	if (!strcmp(ttable->PDS_form,"ASCII_TABLE"))
		process_ascii_table(main);
	return OK;
} /* end post_process */

/*****************************************************************************
* Function: process_binary_table
* Abstract: calculate TFORMn value for each column
* Input
*
*****************************************************************************/
int process_binary_table(FITS_object *main)
{
	FITS_object *sub_obj;  /* PDS object pointer */
	FITS_keyword *key_ptr; /* PDS keyword object pointer */
	char dtype[20];
	char bytes[20];
	char tform[20];
	char dtype_val[20];
	char data_type[3];
	char index[4];
	char tform_val[5];
	char items[6];
	char ibytes[3];
	int  tfields;
	int  byte_cnt = 0;     /* bytes in data type */
	int  bytes_val = 0;    /* value of dummy key BYTES */
	int  items_val = 0;    /* value of dummy key ITEMS */
	int  ibytes_val = 0;
	int i;

/* goto first suboject list */
	sub_obj = main->next;

/* Find Column object list */
	while (strcmp(sub_obj->name,"BINTABLE") != 0){
		sub_obj = sub_obj->next;
	}
	/* search for TFIELDS keyword */
	key_ptr = find_FITS_keyword("TFIELDS",sub_obj);
	tfields = atoi(key_ptr->value);

	for (i=1; i <= tfields; i++) {
/* search for DTYPE keyword (it's a dummy) */
		sprintf(index,"%d",i);
		strcpy(dtype,"DTYPE");
		strcat(dtype,index);
		key_ptr = find_FITS_keyword(dtype,sub_obj);
		strcpy(dtype_val,key_ptr->value);
		strip_quotes(dtype_val);

/* search for BYTES keyword (it's a dummy, too) */
		strcpy(bytes,"BYTES");
		strcat(bytes,index);
		key_ptr = find_FITS_keyword(bytes,sub_obj);
		bytes_val = atoi(key_ptr->value);

/* search for ITEMS keyword (it's a dummy, too) */
		strcpy(items,"ITEMS");
		strcat(items,index);
		key_ptr = find_FITS_keyword(items,sub_obj);
		if (key_ptr->value) {
			items_val = atoi(key_ptr->value);
/* search for IBYTES keyword (it's a dummy, too) */
			strcpy(ibytes,"IBYTES");
			strcat(ibytes,index);
			key_ptr = find_FITS_keyword(ibytes,sub_obj);
			if (key_ptr->value)
				ibytes_val = atoi(key_ptr->value);
		}

/* calculate TFORMn (it's required) */
		if (!strcmp(dtype_val,"LOGICAL")){
			strcpy(data_type,"L");
			byte_cnt = 1;
		}
		else
		if (!strcmp(dtype_val,"MSB_BIT_STRING") ||
			 !strcmp(dtype_val,"BIT")){
			strcpy(data_type,"X");
			byte_cnt = 1;
		}
		else
		if (!strcmp(dtype_val,"CHARACTER")){
			strcpy(data_type,"A");
			byte_cnt = 1;
		}
		else
		if (!strcmp(dtype_val,"BYTE")){
			strcpy(data_type,"B");
			byte_cnt = 1;
		}
		else
		if (!strcmp(dtype_val,"MSB_INTEGER")||
			 !strcmp(dtype_val,"INTEGER")){
			if (items_val == 0)
				byte_cnt = bytes_val;
			else
				byte_cnt = ibytes_val;
			if (byte_cnt == 1)
				strcpy(data_type,"B");
			else
			if (byte_cnt == 2)
				strcpy(data_type,"I");
			else
			if (byte_cnt == 4)
				strcpy(data_type,"J");
		}
		else
		if (!strcmp(dtype_val,"FLOAT") ||
			 !strcmp(dtype_val,"IEEE_REAL")){
			if (items_val == 0)
				byte_cnt = bytes_val;
			else
				byte_cnt = ibytes_val;
			if (byte_cnt == 4)
				strcpy(data_type,"E");
			else
			if (byte_cnt == 8)
				strcpy(data_type,"D");
		}
		else
		if (!strcmp(dtype_val,"COMPLEX") ||
			 !strcmp(dtype_val,"IEEE_COMPLEX")){
			strcpy(data_type,"C");
			byte_cnt = 8;
		}
		sprintf(tform_val,"%d",bytes_val/byte_cnt);
		strcat(tform_val,data_type);

/* create TFORM keyword */
		strcpy(tform,"TFORM");
		strcat(tform,index);
		key_ptr = find_FITS_keyword(tform,sub_obj);
		if ((key_ptr->value = (char *)malloc(6)) == NULL)
			return MALLOC_ERROR;
		strcpy(key_ptr->value,tform_val);
		strcpy(key_ptr->type,"CHARACTER");
	}
	return OK;
} /* end process_binary_table */

/*****************************************************************************
* Function: process_ascii_table
* Abstract: calculate TFORMn value for each column
* Input
*
*****************************************************************************/
int process_ascii_table(FITS_object *main)
{
	FITS_object *sub_obj;  /* PDS object pointer */
	FITS_keyword *key_ptr; /* PDS keyword object pointer */
	char dtype[20];
	char bytes[20];
	char tform[20];
	char dtype_val[20];
	char data_type[3];
	char index[4];
	char tform_val[5];
	char items[6];
	char ibytes[3];
        char tform_num[10];
	int  tfields;
	int  byte_cnt = 0;     /* bytes in data type */
	int  bytes_val = 0;    /* value of dummy key BYTES */
	int  items_val = 0;    /* value of dummy key ITEMS */
	int  ibytes_val = 0;
	int i;
/* goto first suboject list */
	sub_obj = main->next;

/* Find Column object list */
	while (strcmp(sub_obj->name,"TABLE") != 0){
		sub_obj = sub_obj->next;
	}
	/* search for TFIELDS keyword */
	key_ptr = find_FITS_keyword("TFIELDS",sub_obj);
	tfields = atoi(key_ptr->value);

	for (i=1; i <= tfields; i++) {
/* search for DTYPE keyword (it's a dummy) */
		sprintf(index,"%d",i);
		strcpy(dtype,"DTYPE");
		strcat(dtype,index);
		key_ptr = find_FITS_keyword(dtype,sub_obj);
		strcpy(dtype_val,key_ptr->value);
		strip_quotes(dtype_val);

/* search for BYTES keyword (it's a dummy, too) */
		strcpy(bytes,"BYTES");
		strcat(bytes,index);
		key_ptr = find_FITS_keyword(bytes,sub_obj);
		bytes_val = atoi(key_ptr->value);

/* search for ITEMS keyword (it's a dummy, too) */
		strcpy(items,"ITEMS");
		strcat(items,index);
		key_ptr = find_FITS_keyword(items,sub_obj);
		if (key_ptr->value) {
			items_val = atoi(key_ptr->value);
/* search for IBYTES keyword (it's a dummy, too) */
			strcpy(ibytes,"IBYTES");
			strcat(ibytes,index);
			key_ptr = find_FITS_keyword(ibytes,sub_obj);
			if (key_ptr->value)
				ibytes_val = atoi(key_ptr->value);
		}

/* calculate TFORMn (it's required) */
		if (!strcmp(dtype_val,"CHARACTER") ||
                    !strcmp(dtype_val,"BYTE")){
			strcpy(data_type,"A");
			byte_cnt = 1;
		}
		else
		if (!strcmp(dtype_val,"ASCII_INTEGER")||
		    !strcmp(dtype_val,"INTEGER")){
			if (items_val == 0)
				byte_cnt = bytes_val;
			else
				byte_cnt = ibytes_val;
		 
			strcpy(data_type,"I");
		}
		else
		if (!strcmp(dtype_val,"FLOAT") ||
		    !strcmp(dtype_val,"ASCII_REAL")){
			if (items_val == 0)
				byte_cnt = bytes_val;
			else
				byte_cnt = ibytes_val;
			if (byte_cnt == 4)
				strcpy(data_type,"E");
			else
			if (byte_cnt == 8)
				strcpy(data_type,"D");
		}
                strcpy(tform_val,data_type);
		sprintf(tform_num,"%d",bytes_val/byte_cnt);
		strcat(tform_val,tform_num);

/* create TFORM keyword */
		strcpy(tform,"TFORM");
		strcat(tform,index);
		key_ptr = find_FITS_keyword(tform,sub_obj);
		if ((key_ptr->value = (char *)malloc(6)) == NULL)
			return MALLOC_ERROR;
		strcpy(key_ptr->value,tform_val);
		strcpy(key_ptr->type,"CHARACTER");
	}
	return OK;
} /* end process_ascii_table */