//
// IBIS_AMI_tree.c
//
// Implementation of IBIS AMI parameter tree input and output routines
// 
// Copyright (c) 2007. Signal Integrity Software, Inc. (SiSoft)
// All Rights Reserved.
//
// History:
// 8 June 2007, Mike Steinberger: Added support for array-type parameters.
// 22 May 2007, Mike Steinberger: Initial file creation
//
//
// Permission to use, copy, modify, and distribute this software and 
// associated documentation for educational, research and commercial 
// purposes, without fee and without a signed licensing agreement, is hereby
// granted, provided that the above copyright notice, this paragraph and
// the following three paragraphs appear in all copies, modifications, and
// distributions. 
//
// This is part of a sample IBIS Algorithmic Modeling Interface (AMI) model. 
// This code has been provided by SiSoft to help demonstrate and drive 
// adoption of the IBIS AMI modeling standard.  For more information
// on this and other IBIS AMI models, contact Signal Integrity Software, 
// 6 Clock Tower Place, Maynard, MA 01754, (978) 461-0449 or email
// ibis-ami@sisoft.com.
// 
// THIS MATERIAL IS PROVIDED FOR THE RECIPIENT'S USE AT THE RECIPIENT's OWN RISK.
// IN NO EVENT SHALL SISOFT BE LIABLE TO ANY PARTY FOR FOR ANY DIRECT,
// INDIRECT, INCIDENTAL,SPECIAL OR CONSEQUENTIAL DAMAGES, OR ANY OTHER DAMAGES 
// WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, 
// BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) 
// ARISING OUT OF THE USE OR INABILITY TO USE THIS LICENSED MATERIAL, WHETHER 
// OR NOT THE POSSIBILITY OR CAUSE OF SUCH DAMAGES WAS KNOWN TO SISOFT.
// 
// SISOFT SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE. THIS SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
// ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". SISOFT HAS NO OBLIGATION
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
// MODIFICATIONS.

#include "IBIS_AMI_tree.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

int AddToArray( const InLineString text_in,
                ParamType type_in,
                ArrayVal* array_in ) {
   int type_size,
       len,
       cnt = 0;
   void *tmp_array;

   switch( type_in ) {
      case ami_double_array_type:
         type_size = sizeof( double );
         break;
      case ami_long_array_type:
         type_size = sizeof( long );
         break;
      case ami_int_array_type:
         type_size = sizeof( int );
         break;
      case ami_short_array_type:
         type_size = sizeof( short );
         break;
      default:
         assert(0);
   }

   len = array_in->length;
   tmp_array = malloc( (len+1)*type_size );
   if( len ) {
      memcpy( tmp_array, array_in->contents, len*type_size );
      free( array_in->contents );
   }
   array_in->contents = tmp_array;
   array_in->length   = len+1;

   switch( type_in ) {
      case ami_double_array_type:
         cnt = sscanf( text_in.start,"%lf", ((double*)array_in->contents)+len );
         break;
      case ami_long_array_type:
         cnt = sscanf( text_in.start,"%li", ((long*)array_in->contents)+len );
         break;
      case ami_int_array_type:
         cnt = sscanf( text_in.start, "%i", ((int*)array_in->contents)+len );
         break;
      case ami_short_array_type:
         cnt = sscanf( text_in.start,"%hi", ((short*)array_in->contents)+len );
         break;
      default:
         assert(0);
   }

   return cnt;
}

int ParseTree( const char *parameters_in, ParamList param_list_in ) {
   ParamList branch_stack[ MAX_TREE_DEPTH ];
   ParamListItem the_root,
                 *the_leaf;
   int stack_ptr = 0,
       len = 0,
       indx,
       process_text = 0,
       cnt,
       item_count = 0;
   const char *text_ptr;
   LexerState  lexer_state  = ami_white_space;
   ParserState parser_state = ami_start;
   InLineString token_text = {0,0};

   the_root.name   = param_list_in.name;
   the_root.p_type = ami_list_type;
   the_root.p_val.ptr_val  = (void*)&param_list_in;
   branch_stack[0].name     = "MotherEarth";
   branch_stack[0].length   = 1;
   branch_stack[0].contents = &the_root;

   text_ptr = parameters_in;

   while( *text_ptr ) {
      //lexer
      //   Detects tokens "(", text, ")"
      switch( *text_ptr ) {
         case '(':
            process_text = ( lexer_state == ami_text ) ? 1:0;
            lexer_state = ami_open_paren;
            break;
         case ')':
            process_text = ( lexer_state == ami_text ) ? 1:0;
            lexer_state = ami_close_paren;
            break;
         case ' ':
         case '\t':
         case '\r':
         case '\n':
            process_text = ( lexer_state == ami_text ) ? 1:0;
            lexer_state = ami_white_space;
            break;
         default:
            process_text = 0;
            if( lexer_state == ami_text ) {
               token_text.length++;
            }
            else {
               token_text.start = text_ptr;
               token_text.length   = 1;
            }
            lexer_state = ami_text;
            break;
      }
   
      //parser
      //   States:
      //      start:  Waiting for open paren
      //      branch: Waiting for branch label
      //      leaf:   Waiting for leaf value or start of next branch
      //      end:    Waiting for close paren
      //   State transition diagram:
      //
      //                          ---------
      //             ")"   ------>| start |<---------
      //         decrement |      ---------         |
      //           depth   |       |  |             |
      //                   --------   | "("         |
      //                             \|/            |
      //                          ----------        |
      //                   ------>| branch |        |
      //                   |      ----------        |
      //             "("   |          |             |
      //         increment |          | text        |  ")"
      //           depth   |         \|/            |
      //                   |       --------         |
      //                   --------| leaf |         |
      //                   ------->--------         |
      //                   |          |             |
      //             text  |          | text        |
      //                   |         \|/            |
      //                   |       -------          |
      //                   --------| end |-----------
      //                           -------
      //
      if( process_text ) {
         switch( parser_state ) {
            case ami_start:
               printf(
"WARNING: Text encountered in parameter string when \'(\' or \')\' was expected.\n"
               );
               break;
            case ami_branch:
               the_leaf = 0;
               for( indx = 0; indx < branch_stack[stack_ptr].length; indx++ ) {
                  if( strncmp( token_text.start,
                               branch_stack[stack_ptr].contents[ indx ].name,
                               token_text.length ) == 0 ) {
                     the_leaf = branch_stack[stack_ptr].contents + indx;
                     break;
                  }
               }
               parser_state = ami_leaf;
               break;
            case ami_leaf:
               if( the_leaf ) {
                  switch( the_leaf->p_type ) {
                     case ami_double_type:
                        cnt = sscanf( token_text.start,
                                      "%lf",
                                      &(the_leaf->p_val.dbl_val) );
                        break;
                     case ami_long_type:
                        cnt = sscanf( token_text.start,
                                      "%li",
                                      &(the_leaf->p_val.long_val) );
                        break;
                     case ami_int_type:
                        cnt = sscanf( token_text.start,
                                      "%i",
                                      &(the_leaf->p_val.int_val) );
                        break;
                     case ami_short_type:
                        cnt = sscanf( token_text.start,
                                      "%hi",
                                      &(the_leaf->p_val.short_val) );
                        break;
                     case ami_char_type:
                        cnt = sscanf( token_text.start,
                                      "%c",
                                      &(the_leaf->p_val.char_val) );
                        break;
                     case ami_double_array_type:
                        cnt = AddToArray( token_text,
                                          ami_double_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_long_array_type:
                        cnt = AddToArray( token_text,
                                          ami_long_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_int_array_type:
                        cnt = AddToArray( token_text,
                                          ami_int_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_short_array_type:
                        cnt = AddToArray( token_text,
                                          ami_short_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_string_type:
                        the_leaf->p_val.string_val =
                        (char*)malloc( (token_text.length+1) * sizeof( char ) );
                        strncpy( the_leaf->p_val.string_val,
                                 token_text.start,
                                 token_text.length );
                        the_leaf->p_val.string_val[ token_text.length ] = 0;
                        cnt = 1;
                        break;
                     case ami_list_type:
                        cnt = 0;
                        break;
                  } //switch the_leaf->p_type
                  item_count += cnt;
                  if( cnt == 0 ) {
                     printf( "WARNING: Could not convert value for %s.\n",
                             the_leaf->name );
                  }
               } //if the_leaf
               parser_state = ami_end;
               break;
            case ami_end:
               if( the_leaf ) {
                  switch( the_leaf->p_type ) {
                     case ami_double_type:
                     case ami_long_type:
                     case ami_int_type:
                     case ami_short_type:
                     case ami_char_type:
                     case ami_string_type:
                     case ami_list_type:
                        cnt = 0;
                        printf(
"WARNING: Extra white space and text encountered in value for %s.\n",
                                the_leaf->name );
                        break;
                     case ami_double_array_type:
                        cnt = AddToArray( token_text,
                                          ami_double_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_long_array_type:
                        cnt = AddToArray( token_text,
                                          ami_long_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_int_array_type:
                        cnt = AddToArray( token_text,
                                          ami_int_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_short_array_type:
                        cnt = AddToArray( token_text,
                                          ami_short_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                  } //switch the_leaf->p_type
                  item_count += cnt;
                  if( cnt == 0 ) {
                     printf( "WARNING: Could not convert value for %s.\n",
                             the_leaf->name );
                  }
               } //if the_leaf
               break;
         } //switch parser_state
         process_text = 0;
      } //if process_text

      switch( lexer_state ) {
         case ami_open_paren:
            switch( parser_state ) {
            case ami_start:
               parser_state = ami_branch;
               break;
            case ami_branch:
               printf(
"WARNING: \'(\' encountered in parameter string when text was expected.\n"
               );
               break;
            case ami_leaf:
               //Increment the stack.
               if( stack_ptr < MAX_TREE_DEPTH - 1 ) {
                  stack_ptr++;
               }
               else {
                  printf( "WARNING: Parameter string nested too deeply.\n" );
               }
               if( the_leaf && the_leaf->p_type == ami_list_type ) {
                  branch_stack[ stack_ptr ] =
                                         *(ParamList*)(the_leaf->p_val.ptr_val);
               }
               else {
                  branch_stack[ stack_ptr ].name = "NotMine";
                  branch_stack[ stack_ptr ].length = 0;
                  branch_stack[ stack_ptr ].contents = 0;
               }
               if( the_leaf && the_leaf->p_type != ami_list_type ) {
                  printf(
"WARNING: \'(\' encountered in parameter string when value for %s was expected.\n",
                    the_leaf->name );
               }
               parser_state = ami_branch;
               break;
            case ami_end:
               printf(
"WARNING: \'(\' encountered in parameter string when \')\' was expected.\n"
               );
               break;
            } //switch parser_state
            break;
         case ami_text:
            break;
         case ami_white_space:
            break;
         case ami_close_paren:
            switch( parser_state ) {
            case ami_start:
               //Decrement the stack.
               if( stack_ptr > 0 ) {
                  stack_ptr--;
               }
               else {
                  printf(
                      "WARNING: Too many close parens in parameter string.\n" );
               }
               parser_state = ami_start;
               break;
            case ami_branch:
               printf(
"WARNING: \')\' encountered in parameter string when text was expected.\n"
               );
               break;
            case ami_leaf:
               printf(
"WARNING: \')\' encountered in parameter string when \'(\' or text was expected.\n"
               );
               break;
            case ami_end:
               parser_state = ami_start;
               break;
            } //switch parser_state
            break;
      } //switch lexer_state
      text_ptr++;
   } //while *text_ptr

   return item_count;
}

//Allocate a buffer and concatenate two strings into it.
char* string_cat( const char* str1, const char* str2 ) {
   char *rtn_val;

   rtn_val = (char*)malloc( (strlen(str1) + strlen(str2) + 1)*sizeof( char ) );
   strcpy( rtn_val, str1 );
   strcat( rtn_val, str2 );

   return rtn_val;
}

//Create the string for a value array
char* print_array( const char* name, const ArrayVal values ) {
   char *tmp = 0,
         buf[ MAX_VAL_SIZE ];
   int indx, len;

   if( values.length ) {
      len = MAX_LEAF_SIZE + (values.length - 1)*MAX_VAL_SIZE;
      tmp = (char*)malloc( len*sizeof( char ) );
      memset( tmp, 0, len*sizeof( char ) );
      sprintf( tmp, "(%s ", name );
      switch( values.array_type ) {
         case ami_double_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%g", *((double*)values.contents) );
            break;
         case ami_long_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%ld", *((long*)values.contents) );
            break;
         case ami_int_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%d", *((int*)values.contents) );
            break;
         case ami_short_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%hd", *((short*)values.contents) );
            break;
         default:
            assert(0);
      }
      strcat( tmp, buf );
      for( indx = 1; indx < values.length; indx++ ) {
         strcat( tmp, " " );
         switch( values.array_type ) {
            case ami_double_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%g", *((double*)values.contents+indx) );
               break;
            case ami_long_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%ld", *((long*)values.contents+indx) );
               break;
            case ami_int_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%d", *((int*)values.contents+indx) );
               break;
            case ami_short_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%hd", *((short*)values.contents+indx) );
               break;
            default:
               assert(0);
         }
         strcat( tmp, buf );
      }
      strcat( tmp, ")" );
   }

   return tmp;
}

//recursive implementation
char* GrowTree( const ParamList list_in ) {
   char *rtn_val,
        *tmp,
        *tmp2,
        buf[ MAX_LEAF_SIZE+1 ];
   int indx;

   rtn_val = (char*)malloc( (strlen( list_in.name )+2)*sizeof( char ) );
   *rtn_val = '(';
   strcpy( rtn_val+1, list_in.name );

   for( indx = 0; indx < list_in.length; indx++ ) {
      switch( list_in.contents[indx].p_type ) {
         case ami_double_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %g)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.dbl_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_long_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %ld)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.long_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_int_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %d)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.int_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_short_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %hd)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.short_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_char_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %c)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.char_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_double_array_type:
         case ami_long_array_type:
         case ami_int_array_type:
         case ami_short_array_type:
            tmp2 = print_array( list_in.contents[indx].name,
                                *(list_in.contents[indx].p_val.array_val) );
            if( tmp2 ) {
               tmp = string_cat( rtn_val, tmp2 );
               free( tmp2 );
               free( rtn_val );
               rtn_val = tmp;
            }
            break;
         case ami_string_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s ",
                     list_in.contents[indx].name );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            tmp = string_cat( rtn_val,
                              list_in.contents[indx].p_val.string_val );
            free( rtn_val );
            rtn_val = tmp;
            tmp = string_cat( rtn_val, ")" );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_list_type:
            tmp2 = GrowTree(*(ParamList*)list_in.contents[indx].p_val.ptr_val);
            tmp = string_cat( rtn_val, tmp2 );
            free( tmp2 );
            free( rtn_val );
            rtn_val = tmp;
            break;
      } //switch contents.p_type
   } //for indx < list_in.length

   tmp = string_cat( rtn_val, ")" );
   free( rtn_val );
   rtn_val = tmp;

   return rtn_val;
}

//Recursive implementation of parameter list destructor
// Assume that names are static strings that should not be destroyed/freed.
void FreeTree( ParamList param_list_in ) {
   int indx;
   for( indx = 0; indx < param_list_in.length; indx++ ) {
      switch( param_list_in.contents[ indx ].p_type ) {
         case ami_double_type:
         case ami_long_type:
         case ami_int_type:
         case ami_short_type:
         case ami_char_type:
            break;
         case ami_double_array_type:
         case ami_long_array_type:
         case ami_int_array_type:
         case ami_short_array_type:
            free( param_list_in.contents[ indx ].p_val.array_val->contents );
            break;
         case ami_string_type:
            free( param_list_in.contents[ indx ].p_val.string_val );
            break;
         case ami_list_type:
            FreeTree(
                    *(ParamList*)param_list_in.contents[ indx ].p_val.ptr_val );
            break;
      } //switch ... p_type
   }
}
