/** GSWElementID.m - <title>GSWeb: Class GSWElementID</title>

   Copyright (C) 2004 Free Software Foundation, Inc.
   
   Written by:	Manuel Guesdon <mguesdon@orange-concept.com>
   Date: 	Dec 2004
         
   $Revision: 25027 $
   $Date: 2007-04-13 13:32:02 -0500 (Fri, 13 Apr 2007) $

   This file is part of the GNUstep Web Library.
   
   <license>
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   </license>
**/

#include "config.h"

RCS_ID("$Id: GSWElementID.m 25027 2007-04-13 18:32:02Z dwetzel $")

#include "GSWeb.h"

/*
  ElementID parts are stored in GSWElementIDPart.

  We don't care too much about memory as the should be no more than 2 GSWElementIDs 
  created temporarily per context (the current elementID and the senderElementID

  o About GSWElementIDPart:

  	_string: if elementID part is a string, _string is this string and _number 
		is the incremented part otherwise

        _number: if the elementID is not a string, it's the lementID, otherwise it's the 
		incremented part of the string

        _elementIDString: is a cache of the elementID string from first part to this part.
  		it is a mutable string to avoid too much string allocation/deallocation

        _elementIDString_setStringIMP; is the IMP for _elementIDString -setString:


  o About GSWElementID:

  _parts: is a dynamic array of GSWElementIDPart. We first alloc 
		GSWElementID_DefaultElementPartsCount

  _allocatedPartsCount: is the number of allocated parts 

  _partsCount: is the count of used parts

  _builtPartsCount: is the number of GSWElementIDPart which have their 
		elementIDString built

  _tmpString: is a working NSMutableString. There's no eed for locking since GSWElementID 
  		is a 'mutable'  object used only internally during a request life.

  _tmpString_appendStringIMP: is the IMP for _tmpString -appendString:

  _tmpString_setStringIMP: is the IMP for _tmpString -setString:

  _elementIDString: is the current non mutable elementIDstring (it will save a string 
		creation when elementIDString is called more than one time without changes; 
		otherwise, it cost nothing as we have to create it anywat

  _isSearchOverLastSenderIDString: is the parameter of last isSearchOver call. We cache isSearchOverLastSenderID 
		is it will be rused multiple times but we cache if the senderID is non mutable 
		(wich should always be the case).

  _isSearchOverLastSenderID: is the elementID built from isSearchOverLastSenderIDString

  _deleteElementsFromIndexIMP: IMP of -_deleteElementsFromIndex
  _buildElementIMP: IMP of -_buildElement


  Manuel Guesdon
*/

NSString* GSWElementIDPartDescription(GSWElementIDPart* part)
{
  return [NSString stringWithFormat:@"<GSWElementIDPart %p: number: %d string: %@ elementID: %@ IMP: %p",
                   part,
                   part->_number,
                   part->_string,
                   part->_elementIDString,
                   part->_elementIDString_setStringIMP];
};

//====================================================================
@implementation GSWElementID

// 'Standard' GSWElementID class. Used to get IMPs from standardElementIDIMPs
static Class standardClass=Nil;

// List of standardClass IMPs
static GSWElementIDIMPs standardElementIDIMPs;

// Internal Selectors
static SEL deleteElementsFromIndexSelector = NULL;
static SEL buildElementPartsSelector = NULL;
static SEL appendStringSelector = NULL;
static SEL setStringSelector = NULL;

// Public Selectors
SEL appendZeroElementIDComponentSEL=NULL;
SEL deleteLastElementIDComponentSEL=NULL;
SEL incrementLastElementIDComponentSEL=NULL;
SEL appendElementIDComponentSEL=NULL;
SEL deleteAllElementIDComponentsSEL=NULL;
SEL isParentSearchOverForSenderIDSEL=NULL;
SEL isSearchOverForSenderIDSEL=NULL;
SEL elementIDStringSEL=NULL;


//--------------------------------------------------------------------
// Fill impsPtr structure with IMPs for elementID
void GetGSWElementIDIMPs(GSWElementIDIMPs* impsPtr,GSWElementID* elementID)
{
  NSCAssert(elementID,@"No elementID in GetGSWElementIDIMPs()");
  if ([elementID class]==standardClass)
    {
      memcpy(impsPtr,&standardElementIDIMPs,sizeof(GSWElementIDIMPs));
    }
  else
    {
      memset(&standardElementIDIMPs,0,sizeof(GSWElementIDIMPs));

      impsPtr->_incrementLastElementIDComponentIMP = 
        [elementID methodForSelector:incrementLastElementIDComponentSEL];

      impsPtr->_appendElementIDComponentIMP = 
        [elementID methodForSelector:appendElementIDComponentSEL];

      impsPtr->_appendZeroElementIDComponentIMP = 
        [elementID methodForSelector:appendZeroElementIDComponentSEL];

      impsPtr->_deleteAllElementIDComponentsIMP = 
        [elementID methodForSelector:deleteAllElementIDComponentsSEL];

      impsPtr->_deleteLastElementIDComponentIMP = 
        [elementID methodForSelector:deleteLastElementIDComponentSEL];

      impsPtr->_isParentSearchOverForSenderIDIMP = 
        (GSWIMP_BOOL)[elementID methodForSelector:isParentSearchOverForSenderIDSEL];

      impsPtr->_isSearchOverForSenderIDIMP = 
        (GSWIMP_BOOL)[elementID methodForSelector:isSearchOverForSenderIDSEL];

      impsPtr->_elementIDStringIMP = 
        [elementID methodForSelector:elementIDStringSEL];
    };
};

//--------------------------------------------------------------------
// Initialize GSWElementID selectors
void InitializeGSWElementIDSELs()
{
  static BOOL initialized=NO;
  if (!initialized)
    {
      incrementLastElementIDComponentSEL = @selector(incrementLastElementIDComponent);
      appendElementIDComponentSEL = @selector(appendElementIDComponent:);
      appendZeroElementIDComponentSEL=@selector(appendZeroElementIDComponent);
      deleteAllElementIDComponentsSEL = @selector(deleteAllElementIDComponents);
      deleteLastElementIDComponentSEL=@selector(deleteLastElementIDComponent);
      isParentSearchOverForSenderIDSEL = @selector(isParentSearchOverForSenderID:);
      isSearchOverForSenderIDSEL = @selector(isSearchOverForSenderID:);
      elementIDStringSEL = @selector(elementIDString);
      initialized=YES;
    };
};

//--------------------------------------------------------------------
+ (void) initialize
{
  if (self == [GSWElementID class])
    {
      deleteElementsFromIndexSelector=@selector(_deleteElementsFromIndex:);
      buildElementPartsSelector=@selector(_buildElementParts);
      appendStringSelector=@selector(appendString:);
      setStringSelector=@selector(setString:);

      InitializeGSWElementIDSELs();
      memset(&standardElementIDIMPs,0,sizeof(GSWElementIDIMPs));
      [self setStandardClass:[GSWElementID class]];
    };
};

//--------------------------------------------------------------------
+(void)setStandardClass:(Class)aStandardClass
{
  // TODO MultiThread protection
  standardClass=aStandardClass;

  memset(&standardElementIDIMPs,0,sizeof(GSWElementIDIMPs));

  standardElementIDIMPs._incrementLastElementIDComponentIMP = 
    [self instanceMethodForSelector:incrementLastElementIDComponentSEL];

  standardElementIDIMPs._appendElementIDComponentIMP = 
    [self instanceMethodForSelector:appendElementIDComponentSEL];

  standardElementIDIMPs._appendZeroElementIDComponentIMP = 
    [self instanceMethodForSelector:appendZeroElementIDComponentSEL];

  standardElementIDIMPs._deleteAllElementIDComponentsIMP = 
    [self instanceMethodForSelector:deleteAllElementIDComponentsSEL];

  standardElementIDIMPs._deleteLastElementIDComponentIMP = 
    [self instanceMethodForSelector:deleteLastElementIDComponentSEL];

  standardElementIDIMPs._isParentSearchOverForSenderIDIMP = 
    (GSWIMP_BOOL)[self instanceMethodForSelector:isParentSearchOverForSenderIDSEL];

  standardElementIDIMPs._isSearchOverForSenderIDIMP = 
    (GSWIMP_BOOL)[self instanceMethodForSelector:isSearchOverForSenderIDSEL];

  standardElementIDIMPs._elementIDStringIMP = 
    [self instanceMethodForSelector:elementIDStringSEL];      
};

//--------------------------------------------------------------------
/** Allocate or reallocate allocPartsCount elements. Previous parts are in *partsPtr;
previously allocated parts count is in *allocatedPartsCountPtr.
New parts is stored id *partsPtr and new allocated parts count in *allocatedPartsCountPtr
**/
void GSWElementIDRealloc(GSWElementIDPart** partsPtr,int* allocatedPartsCountPtr,int allocPartsCount)
{

  //Really need ?  
  if (allocPartsCount>*allocatedPartsCountPtr)
    {
      int allocSize=allocPartsCount*sizeof(GSWElementIDPart);
      int allocatedSize=(*allocatedPartsCountPtr)*sizeof(GSWElementIDPart);
      GSWElementIDPart* newParts=NULL;

      newParts=NSZoneMalloc(NSDefaultMallocZone(),allocSize);
      NSCAssert2(newParts,@"Can't alloc %d parts (allocSize bytes)",
                 allocPartsCount,
                 allocSize);

      if ((*allocatedPartsCountPtr)>0)
        {
          // Copy previous parts
          memcpy(newParts,*partsPtr,allocatedSize);

          //Dealloc previous parts
          NSZoneFree(NSDefaultMallocZone(),*partsPtr);
        };

      // Zeroing new parts
      memset(newParts+(*allocatedPartsCountPtr),0,
             allocSize-allocatedSize);

      *allocatedPartsCountPtr=allocPartsCount;
      *partsPtr=newParts;
    };
};

//--------------------------------------------------------------------
/** Returns a elementID **/
+(GSWElementID*)elementID
{
  return [[[self alloc]init]autorelease];
};

//--------------------------------------------------------------------
/** Returns elementID initialized with 'string' **/
+(GSWElementID*)elementIDWithString:(NSString*)string
{
  return [[[self alloc]initWithString:string]autorelease];
};

//--------------------------------------------------------------------
-(id)init
{
  return [self initWithPartsCountCapacity:
                 GSWElementID_DefaultElementPartsCount];
};

//--------------------------------------------------------------------
/** Base initializer
partsCount is the number of parts to allocate
**/
-(id)initWithPartsCountCapacity:(int)partsCount
{
  if ((self=[super init]))
    {
      _deleteElementsFromIndexIMP=[self methodForSelector:deleteElementsFromIndexSelector];
      _buildElementPartsIMP=[self methodForSelector:buildElementPartsSelector];
      if (partsCount>0)
        {
          GSWElementIDRealloc(&_parts,&_allocatedPartsCount,partsCount);
        };
    };
  return self;
};

//--------------------------------------------------------------------
/** Initialize from 'string' elementID
**/
-(id)initWithString:(NSString*)string
{  
  int partsCount=0;
  unichar* stringChars=NULL;
  int length=0;
  unichar* ptr=NULL;
  unichar* stringEndPtr=NULL;


  length=[string length];
  if (length>0)
    {
      stringChars=NSZoneMalloc(NSDefaultMallocZone(),(length+1)*sizeof(unichar));
      NSAssert1(stringChars,@"Can't allocate memeory for string of length %d",length);

      [string getCharacters:stringChars];
      stringChars[length]=(unichar)0;

      ptr=stringChars;
      stringEndPtr=stringChars+length;

      partsCount=1;
      while(ptr<stringEndPtr)
        {
          if (*ptr=='.')
            partsCount++;
          ptr++;
        };

      partsCount+=16; // keeps space for extensions
    }
  else
    partsCount=GSWElementID_DefaultElementPartsCount;

  if ((self=[self initWithPartsCountCapacity:partsCount]))
    {
      if (stringChars)
        {
          GSWElementIDPart* part=_parts;
          unichar* startPartPtr=NULL;
          unichar* endPartPtr=NULL;          
          startPartPtr=stringChars;

          // For each part, we'll find start and end of part, if it is all numeric 
          // or a string (+numeric part).
          while(startPartPtr<stringEndPtr)
            {
              int number=0; // result numeric part
              BOOL isAllNumeric=YES; // is entirely numeric ?
              unichar* numericIndexPtr=NULL; // end numeric part pointer
              
              ptr=startPartPtr;
              endPartPtr=NULL; // end part pointer

              while(ptr<stringEndPtr)
                {
                  // End of part ?
                  if (*ptr=='.')
                    {
                      endPartPtr=ptr-1;
                      break;
                    }                  
                  else if (isdigit(*ptr)) // Is digit ?
                    {
                      // (re-)start calculating numeric part
                      if (!isAllNumeric && !numericIndexPtr)
                        numericIndexPtr=ptr;

                      number=number*10+(*ptr-'0');
                    }
                  else // Not a digit ?
                    {
                      //Stop numeric calculation
                      isAllNumeric=NO;
                      numericIndexPtr=NULL;
                    };
                  ptr++;
                };

              // no '.' found ==> last part
              if (!endPartPtr)
                endPartPtr=stringEndPtr-1;
              
              // Entirely numeric ?
              if (isAllNumeric)
                {
                  // number is calculated
                  part->_number=number;
                }
              else
                {
                  // Numeric part (if any) is calculated
                  if (numericIndexPtr)
                    part->_number=number;
                  else
                    numericIndexPtr=stringEndPtr+1;
                  
                  ASSIGN(part->_string,([NSString stringWithCharacters:startPartPtr
                                                  length:(numericIndexPtr-1)-startPartPtr+1]));
                };
              //We could also build part elementIDString but I'm not sure it's interesting as 
              //initializing GSWElementID from string is mainly to be used for 'statics' elementIDs
              //Assigning _elementIDString at the end should be sufficient.
              
              _partsCount++;
              part++;
              startPartPtr=endPartPtr+2;//skip dot
            };
        };
    };      
  ASSIGN(_elementIDString,string);

  return self;
};

//--------------------------------------------------------------------
/** dealloc object **/
-(void)dealloc
{

  if (_allocatedPartsCount>0)
    {
      int i=0;
      GSWElementIDPart* part=NULL;
      // allocated parts even if not used may keey _elementIDString
      for(i=0,part=_parts;i<_allocatedPartsCount;i++,part++) 
        {
          DESTROY(part->_string);
          DESTROY(part->_elementIDString);
        };
      NSZoneFree(NSDefaultMallocZone(),_parts);
    };

  DESTROY(_tmpString);
  DESTROY(_elementIDString);
          
  DESTROY(_isSearchOverLastSenderIDString);
  DESTROY(_isSearchOverLastSenderID);

  [super dealloc];
};

//--------------------------------------------------------------------
/** Init from coder **/
-(id)initWithCoder:(NSCoder*)decoder
{
  NSString* aString=nil;
  [decoder decodeValueOfObjCType:@encode(id)
          at:&aString];
  return [self initWithString:aString];
};

//--------------------------------------------------------------------
/** Encode into coder **/
-(void)encodeWithCoder:(NSCoder*)encoder
{
  NSString* aString=[self elementIDString];
  [encoder encodeValueOfObjCType:@encode(id)
           at:&aString];
};

//--------------------------------------------------------------------
/** Returns a copy **/
-(id)copyWithZone:(NSZone*)zone
{
  int i=0;
  GSWElementID* clone = [[[self class] alloc]initWithPartsCountCapacity:_partsCount+16];

  NSAssert(clone,@"No clone of GSWElementID");

  for(i=0;i<_partsCount;i++)
    {      
      GSWElementIDPart* selfPart=_parts+i;
      GSWElementIDPart* clonePart=clone->_parts+i;
      ASSIGN(clonePart->_string,selfPart->_string);
      clonePart->_number=selfPart->_number;
      //Should we copy part caches ? I don't think is interesting
    };

  //_builtPartCount stay to 0;

  // Copy pre-built _elementIDString if any
  ASSIGN(clone->_elementIDString,_elementIDString);

  return clone;
};

//--------------------------------------------------------------------
-(NSString*)description
{
  return [self elementIDString];
};

//--------------------------------------------------------------------
/** Returns YES if we should stop search (if self is greater than senderID)
For better performences, senderID should be an immutable string
**/
-(BOOL)isSearchOverForSenderID:(NSString*)senderID
                      onParent:(BOOL)onParentFlag
{
  BOOL over=NO;

  if (senderID == nil)
    [NSException raise:NSInvalidArgumentException
                 format:@"compare with nil"];
  else
    {
      int count=0;
      int i=0;
      GSWElementID* senderElementID=nil;
      GSWElementIDPart* selfElementPart=NULL;
      GSWElementIDPart* senderElementPart=NULL;

      //We can make a == test because we cache only immutable senderIDs
      if (senderID==_isSearchOverLastSenderIDString)
        senderElementID=_isSearchOverLastSenderID;
      else
        {
          senderElementID=[[self class]elementIDWithString:senderID];

          //Cache it if it is not mutable
          if ([senderID isKindOfClass:[NSMutableString class]])
            {
              // NSWarnLog(@"Performances: senderID passed to -isSearchOverForSenderID: is a mutable string");
            }
          else
            {
              ASSIGN(_isSearchOverLastSenderIDString,senderID);
              ASSIGN(_isSearchOverLastSenderID,senderElementID);
            };          
        };

      count=min((onParentFlag ? _partsCount-1 : _partsCount),senderElementID->_partsCount);

      for(i=0,selfElementPart=_parts,senderElementPart=senderElementID->_parts;
          i<count && !over;
          i++,selfElementPart++,senderElementPart++)
        {
          if (selfElementPart->_string)
            {
              if (senderElementPart->_string) // string & string
                {
                  NSComparisonResult cResult=[selfElementPart->_string compare:senderElementPart->_string];
                  if (cResult==NSOrderedDescending)
                    over=YES;
                  else if (cResult==NSOrderedSame)
                    {
                      if (selfElementPart->_number>senderElementPart->_number)
                        over=YES;
                      else if (selfElementPart->_number<senderElementPart->_number) // Not over => break
                        break;
                      // else continue
                    }
                  else //NSOrderedAscending: not over => break
                    break;
                }
              else // string and num
                {
                  //Shouldn't happen logically as the root of 2 elementIDs should be the same
                  //Anyway, we consider not over and break here
                };
            }
          else
            {
              if (senderElementPart->_string) // num & string
                {
                  //Shouldn't happen logically as the root of 2 elementIDs should be the same
                  //Anyway, we consider not over and break here
                }
              else // num & num
                {
                  if (selfElementPart->_number>senderElementPart->_number)
                    over=YES;
                  else if (selfElementPart->_number<senderElementPart->_number)
                    break; //not over
                  // else continue
                };
            };
        };
    };

  return over;
}

//--------------------------------------------------------------------
/** Returns YES if we should stop search (if self is greater than senderID)
For better performences, senderID should be an immutable string
**/
-(BOOL)isSearchOverForSenderID:(NSString*)senderID
{
  //NSLog(@"ELEMENTID: [elementID isSearchOverForSenderID:@\"%@\"];",senderID);
  return [self isSearchOverForSenderID:senderID
               onParent:NO];
};

//--------------------------------------------------------------------
/** Returns YES if we should stop search (if self is greater than senderID)
For better performences, senderID should be an immutable string
**/
-(BOOL)isParentSearchOverForSenderID:(NSString*)senderID
{
  //NSLog(@"ELEMENTID: [elementID isParentSearchOverForSenderID:@\"%@\"];",senderID);
  return [self isSearchOverForSenderID:senderID
               onParent:YES];
};

//--------------------------------------------------------------------
/** Build parts _elementIDString **/
-(void)_buildElementParts
{
  static NSString* preBuiltDotPlusNum[] = {
    @".0", @".1", @".2", @".3", @".4", @".5", @".6", @".7", @".8", @".9", 
    @".10", @".11", @".12", @".13", @".14", @".15", @".16", @".17", @".18", @".19", 
    @".20", @".21", @".22", @".23", @".24", @".25", @".26", @".27", @".28", @".29", 
    @".30", @".31", @".32", @".33", @".34", @".35", @".36", @".37", @".38", @".39", 
    @".40", @".41", @".42", @".43", @".44", @".45", @".46", @".47", @".48", @".49", 
    @".50", @".51", @".52", @".53", @".54", @".55", @".56", @".57", @".58", @".59", 
    @".60", @".61", @".62", @".63", @".64", @".65", @".66", @".67", @".68", @".69" };
  static int preBuiltDotPlusNumCount = sizeof(preBuiltDotPlusNum)/sizeof(NSString*);

  NSAssert1(_builtPartCount>=0,@"_builtPartCount=%d",_builtPartCount);

  if (_partsCount>0)
    {
      GSWElementIDPart* part=NULL;
      if (_builtPartCount<_partsCount)
        {
          int i=0;
          
          // No working string created ? 
          if (!_tmpString)
            {
              // Create working string and cache -appendString: IMP
              _tmpString=(NSMutableString*)[NSMutableString new]; //Retained !
              _tmpString_appendStringIMP=[_tmpString methodForSelector:appendStringSelector];
              _tmpString_setStringIMP=[_tmpString methodForSelector:setStringSelector];
            };
          
          // Start from previous built element if one otherwise, start from empty string
          part=_parts+_builtPartCount-1;
          (*_tmpString_setStringIMP)(_tmpString,appendStringSelector,
                                     (_builtPartCount>0 ?
                                      (NSString*)(part->_elementIDString) : (NSString*)@""));
          
          for(i=_builtPartCount,part=_parts+_builtPartCount;i<_partsCount;i++,part++)
            {
              if (part->_string)
                {
                  if (i>0)
                    {
                      (*_tmpString_appendStringIMP)(_tmpString,
                                                    appendStringSelector,@".");
                    };

                  (*_tmpString_appendStringIMP)(_tmpString,
                                                appendStringSelector,part->_string);
                  if (part->_number>0)
                    {
                      (*_tmpString_appendStringIMP)(_tmpString,
                                                    appendStringSelector,
                                                    GSWIntToNSString(part->_number));
                    };
                }
              else
                {
                  if (i>0)
                    {
                      if (part->_number<preBuiltDotPlusNumCount)
                        {
                          // Save a appendString :-)
                          (*_tmpString_appendStringIMP)(_tmpString,
                                                        appendStringSelector,
                                                        preBuiltDotPlusNum[part->_number]);
                        }
                      else
                        {
                          (*_tmpString_appendStringIMP)(_tmpString,
                                                        appendStringSelector,
                                                        @".");                  
                          
                          (*_tmpString_appendStringIMP)(_tmpString,
                                                        appendStringSelector,
                                                        GSWIntToNSString(part->_number));
                        };
                    }
                  else
                    {
                      (*_tmpString_appendStringIMP)(_tmpString,
                                                    appendStringSelector,
                                                    GSWIntToNSString(part->_number));
                    };
                };

              if (part->_elementIDString)
                {
                  (*part->_elementIDString_setStringIMP)(part->_elementIDString,
                                                         setStringSelector,
                                                         _tmpString);
                }
              else
                {
                  part->_elementIDString=[_tmpString mutableCopy]; //Retained !
                  part->_elementIDString_setStringIMP=[part->_elementIDString 
                                                           methodForSelector:setStringSelector];
                };
            };
          _builtPartCount=_partsCount;
        };  

      part=_parts+_partsCount-1;
      ASSIGN(_elementIDString,([NSString stringWithString:part->_elementIDString]));
    };
};

//--------------------------------------------------------------------
/** Returns elementID string representation or empty string if there's not 
elements **/
-(NSString*)elementIDString
{
  NSString* elementIDString=@"";

  if (_partsCount>0)
    {
      if (!_elementIDString) // Not alreday built ?
        (*_buildElementPartsIMP)(self,buildElementPartsSelector);
      elementIDString=_elementIDString;
      AUTORELEASE(RETAIN(elementIDString));
    };

  return elementIDString;
}

//--------------------------------------------------------------------
/** Deletes element parts starting at fromIndex. **/
-(void)_deleteElementsFromIndex:(int)fromIndex
{
  int i=0;
  GSWElementIDPart* part=NULL;

  NSAssert1(fromIndex>=0,@"fromIndex (%d) <0",
            fromIndex);
  NSAssert2(fromIndex<_partsCount,@"fromIndex (%d) >= _partsCount (%d)",
            fromIndex,_partsCount);

  for(i=fromIndex,part=_parts+fromIndex;i<_partsCount;i++,part++)
    {
      DESTROY(part->_string);
      part->_number=0;
    };

  // update cache state information
  if (_builtPartCount>fromIndex)
    _builtPartCount=fromIndex;
  DESTROY(_elementIDString);      

  _partsCount=fromIndex;

}

  
//--------------------------------------------------------------------
/** empties elementID **/
-(void)deleteAllElementIDComponents
{

  if (_partsCount>0)
    (*_deleteElementsFromIndexIMP)(self,deleteElementsFromIndexSelector,0);

};

//--------------------------------------------------------------------
/** Deletes last elementID part **/
-(void)deleteLastElementIDComponent
{

  if (_partsCount>0)
    (*_deleteElementsFromIndexIMP)(self,deleteElementsFromIndexSelector,_partsCount-1);

};

//--------------------------------------------------------------------
/** Increments last elementID part **/
-(void)incrementLastElementIDComponent
{

  //NSLog(@"ELEMENTID: [elementID incrementLastElementIDComponent];");

  if (_partsCount<1)
    {
    #warning checkme!
    return;
    
      // NSWarnLog(@"Can't incrementLastElementIDComponent on an empty elementID");
      [NSException raise:NSInvalidArgumentException
                  format:@"Can't incrementLastElementIDComponent on an empty elementID. In %s",
                         __PRETTY_FUNCTION__];      
    }
  else
    {
      GSWElementIDPart* part=NULL;

      // Update part number
      part=_parts+_partsCount-1;
      part->_number++;

      // update cache state information
      if (_builtPartCount>=_partsCount)
        _builtPartCount=_partsCount-1;
      DESTROY(_elementIDString);      

    };
};
  
//--------------------------------------------------------------------
/** Append zero element ID after last elementID part **/
-(void)appendZeroElementIDComponent
{
  GSWElementIDPart* part=NULL;

  if (_partsCount>=_allocatedPartsCount)
    GSWElementIDRealloc(&_parts,&_allocatedPartsCount,
                        _allocatedPartsCount+GSWElementID_DefaultElementPartsCount);

  // Set to new part
  part=_parts+_partsCount;
  part->_number=0;

  // update cache state information
  DESTROY(_elementIDString);

  // Increments parts count
  _partsCount++;

};

//--------------------------------------------------------------------
/** Append 'element' element ID after last elementID part 
You should avoid element ending with digits.
**/
-(void)appendElementIDComponent:(NSString*)element
{
  int elementLength=0;
  GSWElementIDPart* part=NULL;

  elementLength=[element length];
  
  if (elementLength==0)
    {
      NSWarnLog(@"append empty empty element");
    }
  else
    {
// do we really need this stuff?? davew    
//      if (isdigit([element characterAtIndex:elementLength-1]))
//        {
//          NSWarnLog(@"You'll may get problems if you use anElementID which ends with digit(s) like you do: '%@'",
//                    element);
//        };
    }

  if (_partsCount>=_allocatedPartsCount)
    GSWElementIDRealloc(&_parts,&_allocatedPartsCount,
                        _allocatedPartsCount+GSWElementID_DefaultElementPartsCount);

  // Set to new part
  part=_parts+_partsCount;
  part->_number=0;
  ASSIGNCOPY(part->_string,element);

  NSDebugMLLog(@"GSWElementID",@"Part #%d: %@",
               _partsCount,GSWElementIDPartDescription(part));

  // update cache state information
  DESTROY(_elementIDString);
  
  // Increments parts count
  _partsCount++;

};

//--------------------------------------------------------------------
//NDFN
/** Returns parent element ID **/
-(NSString*)parentElementIDString
{
  NSString* elementIDString=@"";
  if (_partsCount>1)
    {
      GSWElementIDPart* part=NULL;
      if (_builtPartCount<(_partsCount-1))
          (*_buildElementPartsIMP)(self,buildElementPartsSelector);
      
      part=_parts+_partsCount-2;
      elementIDString=[NSString stringWithString:part->_elementIDString];
    };
  return elementIDString;
};

//--------------------------------------------------------------------
//NDFN
/** returns number of element ID parts **/
-(int)elementsCount
{
  return _partsCount;
};

@end

