// Copyright (94) by melonSoft Ralf Suckow Berlin, All Rights Reserved

#import "MLParameters.h"

typedef struct { NXAtom       name;
                 const char * value;
                 BOOL         literal;
               } param_struct;

static id template;

@implementation MLParameters

+ initialize 
{
  if (self == [MLParameters class]) 
    template = [[self alloc] init];

  return self;
}

+ template
{
  return template;
}

+ newFromStream:(NXStream *)stream syntaxOK:(BOOL *)flag
{  
  return [[self alloc] initFromStream:stream syntaxOK:flag];
}

- init
{
  return [super initCount:0 elementSize:sizeof (param_struct)
                            description:
  // @encode (param_struct) gives the wrong result {?=**c}, better is:
                                         "{%*c}"];
}

- initFromStream:(NXStream *)stream syntaxOK:(BOOL *)flag
{  
  [super init];
  
  if ([self readFromStream:stream syntaxOK:flag])
    return self;
  else
    return [self free];
}

- copy
{
  id             newParameters;
  int            i;
  param_struct * oldElement;
  param_struct   newElement;
  
  newParameters = [[[self class] alloc] init];
  for (i = 0; oldElement = [self elementAt:i]; i++) {
    newElement.name = oldElement->name; 
    newElement.value = oldElement->value ? 
                         NXCopyStringBuffer (oldElement->value) : NULL; 
    newElement.literal = oldElement->literal;
    [newParameters addElement:&newElement];
  }
  return newParameters;
}

- initCount:(unsigned int)count 
  elementSize:(unsigned int)sizeInBytes 
  description:(const char *)string
{ 
  // there is no choice of contents
  return [self init];
}

- free
{
  [self empty];
  return [super free];
}

- empty
{
  char   * value;
  int      iterator = [self startParameterIteration];
  
  while ([self next:&iterator parName:NULL
                              value:(const char **)&value
                              literal:NULL])
    if (value)
      free (value);

  return [super empty];
}

- (int)startParameterIteration
{
  return 0; // using the index as iterator
}

- (BOOL)next:(int *)state parName:(NXAtom *)name
                          value:(const char **)value
                          literal:(BOOL *)flag
{
  NXAtom       dummyName;
  const char * dummyValue;
  BOOL         dummyFlag;
  
  if (!name)  name  = &dummyName;
  if (!value) value = &dummyValue;
  if (!flag)  flag  = &dummyFlag;
    
  return [self getParName:name value:value literal:flag at:(*state)++];
}

- (BOOL)getParName:(NXAtom *)name
        value:(const char **)value
        literal:(BOOL *)flag
        at:(int)index
{
  param_struct * parp;
  
  parp = (param_struct *) [self elementAt:index];
  
  if (parp == NULL)
    return NO;
  else {
    if (name)  *name  = parp->name;
    if (value) *value = parp->value;
    if (flag)  *flag  = parp->literal;
    return YES;
  }
}

- (param_struct *)_getElementFor:(NXAtom)name
{
  int i;
  param_struct * parp;
  
  for (i = 0; parp = (param_struct *) [self elementAt:i]; i++)
    if (parp->name == name)
      return parp;

  return NULL;
}

- _setValue:(const char *)value forElement:(param_struct *)parp
{
  if (parp->value)
    free ((char *)parp->value);

  if (value)
    value = NXCopyStringBuffer (value);
    
  parp->value = value;
  return self;
}

- (const char *)_stringFromDouble:(double)value;
{
  // valid only until the next call
  static char stringValue[80];
  sprintf (stringValue, "%.5g", value);
  return stringValue;
}

- (const char *)_stringFromInt:(int)value;
{
  // valid only until the next call
  static char stringValue[80];
  sprintf (stringValue, "%d", value);
  return stringValue;
}

- getValue:(const char **)value
  literal:(BOOL *)flag
  forParName:(NXAtom)name
{
  param_struct * parp;
  
  if ((parp = [self _getElementFor:name]) == NULL)
    return nil;

  if (value) *value = parp->value;
  if (flag)  *flag  = parp->literal;
  return self;
}
  
- setPar:(NXAtom)name to:(const char *)val
{
  param_struct * parp;
  
  if ((parp = [self _getElementFor:name]) == NULL)
    return nil;

  return [self _setValue:val forElement:parp];
}

- setPar:(NXAtom)name toDouble:(double)val
{
  return [self setPar:name to:[self _stringFromDouble:val]];
}

- setPar:(NXAtom)name toInt:(int)val
{
  return [self setPar:name to:[self _stringFromInt:val]];
}

- (const char *)valueFor:(NXAtom)name
{
  param_struct * parp;
  
  if ((parp = [self _getElementFor:name]) != NULL)
    return parp->value;
  else
    return NULL;
}

- (double)doubleValueFor:(NXAtom)name
{
  const char * val;
  
  if (val = [self valueFor:name])
    return atof (val);
  else
    return 0.0;
}

- (int)intValueFor:(NXAtom)name
{
  const char * val;
  
  if (val = [self valueFor:name])
    return atoi (val);
  else
    return 0;
}

- (BOOL)isLiteral:(NXAtom)name
{
  param_struct * parp;
  
  if ((parp = [self _getElementFor:name]) != NULL)
    return parp->literal;
  else
    return NO;
}

- (BOOL)contains:(NXAtom)name
{
  return ([self _getElementFor:name] != NULL);
}

- addPar:(NXAtom)name value:(const char *)value literal:(BOOL)flag
{
  param_struct newp, * parp;
  
  if ((parp = [self _getElementFor:name]) != NULL) {
    [self _setValue:value forElement:parp];
    parp->literal = flag;
  }
  else {
    newp.name  = name;
    newp.value = NULL;
    [self _setValue:value forElement:&newp];
    newp.literal = flag;
    [self addElement:&newp];
  }
  return self;
}

- addPar:(NXAtom)name doubleValue:(double)value literal:(BOOL)flag
{
  return [self addPar:name value:[self _stringFromDouble:value] literal:flag];
}

- addPar:(NXAtom)name intValue:(int)value literal:(BOOL)flag
{
  return [self addPar:name value:[self _stringFromInt:value] literal:flag];
}

- addPar:(NXAtom)name literal:(const char *)val
{
  return [self addPar:name value:val literal:YES];
}

- addPar:(NXAtom)name mathValue:(const char *)val
{
  return [self addPar:name value:val literal:NO];
}

- addPar:(NXAtom)name mathDoubleValue:(double)val
{
  return [self addPar:name doubleValue:val literal:NO];
}

- addParametersFrom:otherParameters
{
  int          state;
  NXAtom       name;
  const char * val;
  BOOL         flag;
  
  state = [otherParameters startParameterIteration];
  
  while ([otherParameters next:&state parName:&name value:&val literal:&flag])
    [self addPar:name value:val literal:flag];

  return self;
}

- (NXAtom)atomForPar:(const char *)name
  value:(const char *)value literal:(BOOL)flag
{
  NXAtom atom;
  
  if (name && *name) {
    atom = NXUniqueString (name);
    [self addPar:atom value:value literal:flag]; 
    return atom;
  }
  else
    return NULL;
}

- takePar:(NXAtom)name from:parameters
{
  const char * value;
  BOOL         flag;
  
  if (![parameters getValue:&value literal:&flag forParName:name])
    return nil;
  
  return [self addPar:name value:value literal:flag];
}

- removeElementAt:(int)index
{
  param_struct * parp;
  
  if (parp = (param_struct *) [self elementAt:index]) {
    if (parp->value)
      free ((char *)parp->value);
    [super removeElementAt:index];
  }
  return self;
}
  
- removePar:(NXAtom)name
{
  int i;
  param_struct * parp;
  
  for (i = 0; parp = (param_struct *) [self elementAt:i]; i++)
    if (parp->name == name) {
      [self removeElementAt:i];
      break;
    }
    
  return self;
}

- (BOOL)isEqual:otherList
{
  int i;
  param_struct * parp;
  const char   * value;
  BOOL           flag;

  if (!otherList || ![otherList isKindOf:[MLParameters class]])
    return NO;
    
  if ([self count] != [otherList count])
    return NO;
    
  for (i = 0; parp = [self elementAt:i]; i++) {
    if (![otherList getValue:&value literal:&flag forParName:parp->name])
      return NO;
    if ((value == NULL) != (parp->value == NULL))
      return NO;
    if (value && strcmp (value, parp->value) != 0)
      return NO;
    if (flag != parp->literal)
      return NO;
  }
  return YES;
}

// PARSING a note from a stream:  { pname = ["]<expression>["] ; ... ; }
// end of file not expected

#define MAXIDENT 1000
#define GETC(c, x)           if ((c = NXGetc (x)) == EOF) goto end_of_file
#define SKIP_SPACES(c, x)    while (NXIsSpace (c)) GETC (c, x)
#define GET_NONSPACE_C(c, x) GETC(c, x); while (NXIsSpace (c)) GETC (c, x)

- readFromStream:(NXStream *)stream syntaxOK:(BOOL *)flag
{
  unsigned int c;
  char     token[MAXIDENT + 1];
  int      index;
  NXAtom   name;
  BOOL     literal;
  BOOL     inside;
  
  inside = NO;
  GET_NONSPACE_C (c, stream);
  if (c != '{') goto syntax_error;            // {
  inside = YES;
  
again:
  GET_NONSPACE_C (c, stream);
  if (c == '}') goto success;                 // }

  index = 0;
  while (NXIsAlNum (c) && index < MAXIDENT) { // pname
    token[index++] = c;
    GETC (c, stream);
  }
  if (index == MAXIDENT) goto syntax_error;
  token [index] = '\0';
  name = NXUniqueString (token);
  
  SKIP_SPACES (c, stream);
  if (c != '=') goto syntax_error;            // =
  GET_NONSPACE_C (c, stream);

  if (c == '"') {                             // "...
    literal = YES;
    index = 0;
    GETC (c, stream);
    while (index < MAXIDENT && c != '"') {    // ..."
      token[index++] = c;                     // expression
      GETC (c, stream);
    }
    if (index == MAXIDENT) goto syntax_error;
    token [index] = '\0';
    GET_NONSPACE_C (c, stream);
    if (c != ';') goto syntax_error;          // ;
  }
  else {                                      // expression;
    literal = NO;
    index = 0;
    while (index < MAXIDENT && c != ';') {
      token[index++] = c;
      GETC (c, stream);
    }
    if (index == MAXIDENT) goto syntax_error;
    token [index] = '\0';
  }
    
  [self addPar:name value:token literal:literal];
  goto again;
  
success:
  *flag = YES;
  return self;

end_of_file:
  if (inside)
    *flag = NO;
  else
    *flag = YES;
  return nil;
    
syntax_error:
  *flag = NO;
  return nil;
}

- readFromMathString:(const char *)string syntaxOK:(BOOL *)flag
{
  NXStream * stream;
  id         result;
  
  // we are doing it a bit tricky
  
  if (!string || !*string) { // it's empty but OK
    *flag = YES;
    return self;
  }
  
  if ((stream = NXOpenMemory (NULL, 0, NX_READWRITE)) == NULL) {
    *flag = YES;
    return nil;
  }
  
  NXPrintf (stream, "{\n");
  NXWrite (stream, string, strlen (string));
  NXPrintf (stream, "}\n");    
  NXSeek (stream, 0, NX_FROMSTART);
  
  result = [self readFromStream:stream syntaxOK:flag];
  NXCloseMemory (stream, NX_FREEBUFFER);
  return result;
}

- writeToStream:(NXStream *)stream
{  
  NXPrintf (stream, "{\n");
  [self writeMathToStream:stream];
  NXPrintf (stream, "}\n");    
  return self;
}
  
- writeMathToStream:(NXStream *)stream
{
  int i;
  param_struct * parp;
  
  for (i = 0; i < [self count]; i++)
    if (parp = (param_struct *) [self elementAt:i])
      if (parp->literal)
        NXPrintf (stream, "  %s = \"%s\";\n", parp->name, 
                                           (parp->value ? parp->value : ""));
      else
        NXPrintf (stream, "  %s = %s;\n",   parp->name, 
                                        (parp->value ? parp->value : "0"));
  return self;
}
  
- (char *)getMathString
{
  NXStream   * inputParameters;
  const char * buf;
  int          len, maxlen;
  char       * string;
  
  inputParameters = NXOpenMemory (NULL, 0, NX_READWRITE);
  [self writeMathToStream:inputParameters];
  NXPutc (inputParameters, '\0');
  NXSeek (inputParameters, 0, NX_FROMSTART);
  NXGetMemoryBuffer (inputParameters, &buf, &len, &maxlen);
  string = malloc (len);
  strcpy (string, buf);
  NXCloseMemory (inputParameters, NX_FREEBUFFER);
  return string;
}

// methods of the NXTransport protocol

- encodeUsing:(id <NXEncoding>)portal
{
  int            i;
  param_struct * element;
      
  i = [self count];
  [portal encodeData:&i ofType:"i"];
  
  for (i = 0; element = [self elementAt:i]; i++) 
    [portal encodeData:element ofType:"{?=%*c}"]; // @encode (param_struct)];

  return self;
}

- decodeUsing:(id <NXDecoding>)portal
{
  int          i, n;
  param_struct element;

  [portal decodeData:&n ofType:"i"];
  [super initCount:n elementSize:sizeof (param_struct)
                     description:"{%*c}"]; // @encode(param_struct)];
  for (i = 0; i < n; i++) {
    [portal decodeData:&element ofType:"{?=%*c}"]; // @encode (param_struct)];
    [self addElement:&element];
  }
  return self;
}

- encodeRemotelyFor:(NXConnection *)connection
  freeAfterEncoding:(BOOL *)flagp
  isBycopy:(BOOL)isBycopy
{
  if (isBycopy)
    return self;
      
  return [super encodeRemotelyFor:connection
                freeAfterEncoding:flagp
                isBycopy:isBycopy];
}

@end
