/* 
   NSAttributedString.m

   Implementation of string class with attributes

   Copyright (C) 1997,1999 Free Software Foundation, Inc.

   Written by: ANOQ of the sun <anoq@vip.cybercity.dk>
   Date: November 1997
   Rewrite by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Date: April 1999
   
   This file is part of GNUstep-base

   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.

   If you are interested in a warranty or support for this source code,
   contact Scott Christley <scottc@net-community.com> for more information.
   
   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., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

/* Warning -	[-initWithString:attributes:] is the designated initialiser,
 *		but it doesn't provide any way to perform the function of the
 *		[-initWithAttributedString:] initialiser.
 *		In order to work youd this, the string argument of the
 *		designated initialiser has been overloaded such that it
 *		is expected to accept an NSAttributedString here instead of
 *		a string.  If you create an NSAttributedString subclass, you
 *		must make sure that your implementation of the initialiser
 *		copes with either an NSString or an NSAttributedString.
 *		If it receives an NSAttributedString, it should ignore the
 *		attributes argument and use the values from the string.
 */

#include <base/preface.h>
#include <base/fast.x>
#include <base/Unicode.h>

#include <Foundation/NSAttributedString.h>
#include <Foundation/NSGAttributedString.h>
#include <Foundation/NSException.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSPortCoder.h>
#include <Foundation/NSRange.h>


@interface GSMutableAttributedStringTracker : NSMutableString
{
  NSMutableAttributedString	*_owner;
}
+ (NSMutableString*) stringWithOwner: (NSMutableAttributedString*)as;
@end


@implementation NSAttributedString

static Class NSAttributedString_abstract_class;
static Class NSAttributedString_concrete_class;
static Class NSMutableAttributedString_abstract_class;
static Class NSMutableAttributedString_concrete_class;

//Internal methods
+ (void) _setConcreteClass: (Class)c
{
  NSAttributedString_concrete_class = c;
}

+ (void) _setMutableConcreteClass: (Class)c
{
  NSMutableAttributedString_concrete_class = c;
}

+ (Class) _concreteClass
{
  return NSAttributedString_concrete_class;
}

+ (Class) _mutableConcreteClass
{
  return NSMutableAttributedString_concrete_class;
}

+ (void) initialize
{
  if (self == [NSAttributedString class])
    {
      NSAttributedString_abstract_class = self;
      NSAttributedString_concrete_class = [NSGAttributedString class];
      NSMutableAttributedString_abstract_class
	= [NSMutableAttributedString class];
      NSMutableAttributedString_concrete_class
	= [NSGMutableAttributedString class];
    }
}

+ (id) allocWithZone: (NSZone*)z
{
  if (self == NSAttributedString_abstract_class)
    return NSAllocateObject([self _concreteClass], 0, z);
  else
    return NSAllocateObject(self, 0, z);
}

//NSCoding protocol
- (void) encodeWithCoder: (NSCoder*)anEncoder
{
  [super encodeWithCoder: anEncoder];
}

- (id) initWithCoder: (NSCoder*)aDecoder
{
  return [super initWithCoder: aDecoder];
}

- (Class) classForPortCoder
{
  return [self class];
}

- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
  if ([aCoder isByref] == NO)
    return self;
  return [super replacementObjectForPortCoder: aCoder];
}

//NSCopying protocol
- (id) copyWithZone: (NSZone*)zone
{
  if ([self isKindOfClass: [NSMutableAttributedString class]] ||
        NSShouldRetainWithZone(self, zone) == NO)
    return [[[[self class] _concreteClass] allocWithZone: zone]
        initWithAttributedString: self];
  else
    return RETAIN(self);
}

//NSMutableCopying protocol
- (id) mutableCopyWithZone: (NSZone*)zone
{
  return [[[[self class] _mutableConcreteClass] allocWithZone: zone]
	  initWithAttributedString: self];
}

//Creating an NSAttributedString
- (id) init
{
  return [self initWithString: nil attributes: nil];
}

- (id) initWithString: (NSString*)aString
{
  return [self initWithString: aString attributes: nil];
}

- (id) initWithAttributedString: (NSAttributedString*)attributedString
{
  return [self initWithString: (NSString*)attributedString attributes: nil];
}

- (id) initWithString: (NSString*)aString attributes: (NSDictionary*)attributes
{
  //This is the designated initializer
  [self subclassResponsibility: _cmd];/* Primitive method! */
  return nil;
}

//Retrieving character information
- (unsigned int) length
{
  return [[self string] length];
}

- (NSString *) string
{
  [self subclassResponsibility: _cmd];/* Primitive method! */
  return nil;
}

//Retrieving attribute information
- (NSDictionary*) attributesAtIndex: (unsigned)index
		     effectiveRange: (NSRange*)aRange
{
  [self subclassResponsibility: _cmd];/* Primitive method! */
  return nil;
}

- (NSDictionary*) attributesAtIndex: (unsigned)index
	      longestEffectiveRange: (NSRange*)aRange
			    inRange: (NSRange)rangeLimit
{
  NSDictionary	*attrDictionary, *tmpDictionary;
  NSRange	tmpRange;

  if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length])
    {
      [NSException raise: NSRangeException format: 
	@"RangeError in method -attributesAtIndex: longestEffectiveRange: inRange: in class NSAttributedString"];
    }
  attrDictionary = [self attributesAtIndex: index effectiveRange: aRange];
  if (!aRange)
    return attrDictionary;
  
  while(aRange->location > rangeLimit.location)
  {
    //Check extend range backwards
    tmpDictionary =
      [self attributesAtIndex: aRange->location-1
        effectiveRange: &tmpRange];
    if ([tmpDictionary isEqualToDictionary: attrDictionary])
      aRange->location = tmpRange.location;
  }
  while(NSMaxRange(*aRange) < NSMaxRange(rangeLimit))
  {
    //Check extend range forwards
    tmpDictionary =
      [self attributesAtIndex: NSMaxRange(*aRange)
        effectiveRange: &tmpRange];
    if ([tmpDictionary isEqualToDictionary: attrDictionary])
      aRange->length = NSMaxRange(tmpRange) - aRange->location;
  }
  *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit
  return attrDictionary;
}

- (id) attribute: (NSString*)attributeName
	 atIndex: (unsigned)index
  effectiveRange: (NSRange*)aRange
{
  NSDictionary *tmpDictionary;
  id attrValue;

  tmpDictionary = [self attributesAtIndex: index effectiveRange: aRange];
  //Raises exception if index is out of range, so that I don't have to test this...

  if (!attributeName)
  {
    if (aRange)
      *aRange = NSMakeRange(0,[self length]);
      //If attributeName is nil, then the attribute will not exist in the
      //entire text - therefore aRange of the entire text must be correct
    
    return nil;
  }
  attrValue = [tmpDictionary objectForKey: attributeName];  
  return attrValue;
}

- (id) attribute: (NSString*)attributeName atIndex: (unsigned int)index longestEffectiveRange: (NSRange *)aRange inRange: (NSRange)rangeLimit
{
  NSDictionary *tmpDictionary;
  id attrValue,tmpAttrValue;
  NSRange tmpRange;

  if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length])
  {
    [NSException raise: NSRangeException format: 
      @"RangeError in method -attribute: atIndex: longestEffectiveRange: inRange: in class NSAttributedString"];
  }
  
  attrValue = [self attribute: attributeName atIndex: index effectiveRange: aRange];
  //Raises exception if index is out of range, so that I don't have to test this...

  if (!attributeName)
    return nil;//attribute: atIndex: effectiveRange: handles this case...
  if (!aRange)
    return attrValue;
  
  while(aRange->location > rangeLimit.location)
  {
    //Check extend range backwards
    tmpDictionary =
      [self attributesAtIndex: aRange->location-1
        effectiveRange: &tmpRange];
    tmpAttrValue = [tmpDictionary objectForKey: attributeName];
    if (tmpAttrValue == attrValue)
      aRange->location = tmpRange.location;
  }
  while(NSMaxRange(*aRange) < NSMaxRange(rangeLimit))
  {
    //Check extend range forwards
    tmpDictionary =
      [self attributesAtIndex: NSMaxRange(*aRange)
        effectiveRange: &tmpRange];
    tmpAttrValue = [tmpDictionary objectForKey: attributeName];
    if (tmpAttrValue == attrValue)
      aRange->length = NSMaxRange(tmpRange) - aRange->location;
  }
  *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit
  return attrValue;
}

//Comparing attributed strings
- (BOOL) isEqualToAttributedString: (NSAttributedString *)otherString
{
  NSRange ownEffectiveRange,otherEffectiveRange;
  unsigned int length;
  NSDictionary *ownDictionary,*otherDictionary;
  BOOL result;

  if (!otherString)
    return NO;
  if (![[otherString string] isEqual: [self string]])
    return NO;
  
  length = [otherString length];
  if (length<=0)
    return YES;

  ownDictionary = [self attributesAtIndex: 0
    effectiveRange: &ownEffectiveRange];
  otherDictionary = [otherString attributesAtIndex: 0
    effectiveRange: &otherEffectiveRange];
  result = YES;
    
  while(YES)
  {
    if (NSIntersectionRange(ownEffectiveRange,otherEffectiveRange).length > 0 &&
      ![ownDictionary isEqualToDictionary: otherDictionary])
    {
      result = NO;
      break;
    }
    if (NSMaxRange(ownEffectiveRange) < NSMaxRange(otherEffectiveRange))
    {
      ownDictionary = [self
        attributesAtIndex: NSMaxRange(ownEffectiveRange)
        effectiveRange: &ownEffectiveRange];
    }
    else
    {
      if (NSMaxRange(otherEffectiveRange) >= length)
        break;//End of strings
      otherDictionary = [otherString
        attributesAtIndex: NSMaxRange(otherEffectiveRange)
        effectiveRange: &otherEffectiveRange];
    }
  }
  return result;
}

- (BOOL) isEqual: (id)anObject
{
  if (anObject == self)
    return YES;
  if ([anObject isKindOf: [NSAttributedString class]])
    return [self isEqualToAttributedString: anObject];
  return NO;
}


//Extracting a substring
- (NSAttributedString *) attributedSubstringFromRange: (NSRange)aRange
{
  NSAttributedString	*newAttrString;
  NSString		*newSubstring;
  NSDictionary		*attrs;
  NSRange		range;
  unsigned		len = [self length];

  GS_RANGE_CHECK(aRange, len);
  
  newSubstring = [[self string] substringFromRange: aRange];

  attrs = [self attributesAtIndex: aRange.location effectiveRange: &range];
  range = NSIntersectionRange(range, aRange);
  if (NSEqualRanges(range, aRange) == YES)
    {
      newAttrString = [[NSAttributedString alloc] initWithString: newSubstring
						      attributes: attrs];
    }
  else
    {
      NSMutableAttributedString	*m;
      NSRange			rangeToSet = range;

      m = [[NSMutableAttributedString alloc] initWithString: newSubstring
						 attributes: nil];
      rangeToSet.location = 0;
      [m setAttributes: attrs range: rangeToSet];
      while (NSMaxRange(range) < NSMaxRange(aRange))
	{
	  attrs = [self attributesAtIndex: NSMaxRange(range)
			   effectiveRange: &range];
	  rangeToSet = NSIntersectionRange(range, aRange);
	  rangeToSet.location -= aRange.location;
	  [m setAttributes: attrs range: rangeToSet];
	}
      newAttrString = [m copy];
      RELEASE(m);
    }

  IF_NO_GC(AUTORELEASE(newAttrString));
  return newAttrString;
}

@end //NSAttributedString

@implementation NSMutableAttributedString

+ allocWithZone: (NSZone*)z
{
  if (self == NSMutableAttributedString_abstract_class)
    return NSAllocateObject([self _mutableConcreteClass], 0, z);
  else
    return NSAllocateObject(self, 0, z);
}

//Retrieving character information
- (NSMutableString *) mutableString
{
  return [GSMutableAttributedStringTracker stringWithOwner: self];
}

//Changing characters
- (void) deleteCharactersInRange: (NSRange)aRange
{
  [self replaceCharactersInRange: aRange withString: nil];
}

//Changing attributes
- (void) setAttributes: (NSDictionary *)attributes range: (NSRange)aRange
{
  [self subclassResponsibility: _cmd];// Primitive method!
}

- (void) addAttribute: (NSString *)name value: (id)value range: (NSRange)aRange
{
  NSRange effectiveRange;
  NSDictionary *attrDict;
  NSMutableDictionary *newDict;
  unsigned int tmpLength;

  tmpLength = [self length];
  GS_RANGE_CHECK(aRange, tmpLength);
  
  attrDict = [self attributesAtIndex: aRange.location
    effectiveRange: &effectiveRange];

  while(effectiveRange.location < NSMaxRange(aRange))
  {
    effectiveRange = NSIntersectionRange(aRange,effectiveRange);
    
    newDict = [[NSMutableDictionary alloc] initWithDictionary: attrDict];
    IF_NO_GC(AUTORELEASE(newDict));
    [newDict setObject: value forKey: name];
    [self setAttributes: newDict range: effectiveRange];
    
    if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange))
      effectiveRange.location = NSMaxRange(aRange);//This stops the loop...
    else if (NSMaxRange(effectiveRange) < tmpLength)
    {
      attrDict = [self attributesAtIndex: NSMaxRange(effectiveRange)
        effectiveRange: &effectiveRange];
    }
  }
}

- (void) addAttributes: (NSDictionary *)attributes range: (NSRange)aRange
{
  NSRange effectiveRange;
  NSDictionary *attrDict;
  NSMutableDictionary *newDict;
  unsigned int tmpLength;
  
  if (!attributes)
  {
    //I cannot use NSParameterAssert here, if is has to be an NSInvalidArgumentException
    [NSException raise: NSInvalidArgumentException
      format: @"attributes is nil in method -addAttributes: range: in class NSMutableAtrributedString"];
  }
  tmpLength = [self length];
  if (aRange.location <= 0 || NSMaxRange(aRange) > tmpLength)
  {
    [NSException raise: NSRangeException
      format: @"RangeError in method -addAttribute: value: range: in class NSMutableAttributedString"];
  }
  
  attrDict = [self attributesAtIndex: aRange.location
    effectiveRange: &effectiveRange];

  while(effectiveRange.location < NSMaxRange(aRange))
  {
    effectiveRange = NSIntersectionRange(aRange,effectiveRange);
    
    newDict = [[NSMutableDictionary alloc] initWithDictionary: attrDict];
    IF_NO_GC(AUTORELEASE(newDict));
    [newDict addEntriesFromDictionary: attributes];
    [self setAttributes: newDict range: effectiveRange];
    
    if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange))
      effectiveRange.location = NSMaxRange(aRange);//This stops the loop...
    else if (NSMaxRange(effectiveRange) < tmpLength)
    {
      attrDict = [self attributesAtIndex: NSMaxRange(effectiveRange)
        effectiveRange: &effectiveRange];
    }
  }
}

- (void) removeAttribute: (NSString *)name range: (NSRange)aRange
{
  NSRange effectiveRange;
  NSDictionary *attrDict;
  NSMutableDictionary *newDict;
  unsigned int tmpLength;
  
  tmpLength = [self length];
  GS_RANGE_CHECK(aRange, tmpLength);
  
  attrDict = [self attributesAtIndex: aRange.location
    effectiveRange: &effectiveRange];

  while(effectiveRange.location < NSMaxRange(aRange))
  {
    effectiveRange = NSIntersectionRange(aRange,effectiveRange);
    
    newDict = [[NSMutableDictionary alloc] initWithDictionary: attrDict];
    IF_NO_GC(AUTORELEASE(newDict));
    [newDict removeObjectForKey: name];
    [self setAttributes: newDict range: effectiveRange];
    
    if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange))
      effectiveRange.location = NSMaxRange(aRange);//This stops the loop...
    else if (NSMaxRange(effectiveRange) < tmpLength)
    {
      attrDict = [self attributesAtIndex: NSMaxRange(effectiveRange)
        effectiveRange: &effectiveRange];
    }
  }
}

//Changing characters and attributes
- (void) appendAttributedString: (NSAttributedString *)attributedString
{
  [self replaceCharactersInRange: NSMakeRange([self length],0)
    withAttributedString: attributedString];
}

- (void) insertAttributedString: (NSAttributedString *)attributedString atIndex: (unsigned int)index
{
  [self replaceCharactersInRange: NSMakeRange(index,0)
    withAttributedString: attributedString];
}

- (void) replaceCharactersInRange: (NSRange)aRange withAttributedString: (NSAttributedString *)attributedString
{
  NSRange effectiveRange,clipRange,ownRange;
  NSDictionary *attrDict;
  NSString *tmpStr;
  
  tmpStr = [attributedString string];
  [self replaceCharactersInRange: aRange
    withString: tmpStr];
  
  effectiveRange = NSMakeRange(0,0);
  clipRange = NSMakeRange(0,[tmpStr length]);
  while(NSMaxRange(effectiveRange) < NSMaxRange(clipRange))
  {
    attrDict = [attributedString attributesAtIndex: effectiveRange.location
      effectiveRange: &effectiveRange];
    ownRange = NSIntersectionRange(clipRange,effectiveRange);
    ownRange.location += aRange.location;
    [self setAttributes: attrDict range: ownRange];
  }
}

- (void) replaceCharactersInRange: (NSRange)aRange withString: (NSString *)aString
{
  [self subclassResponsibility: _cmd];// Primitive method!
}

- (void) setAttributedString: (NSAttributedString *)attributedString
{
  [self replaceCharactersInRange: NSMakeRange(0,[self length])
    withAttributedString: attributedString];
}

//Grouping changes
- (void) beginEditing
{
  //Overridden by subclasses
}

- (void) endEditing
{
  //Overridden by subclasses
}

@end //NSMutableAttributedString




/*
 * The GSMutableAttributedStringTracker class is a concrete subclass of
 * NSMutableString which keeps it's owner informed of any changes made
 * to it.
 */
@implementation GSMutableAttributedStringTracker

+ (NSMutableString*) stringWithOwner: (NSMutableAttributedString*)as
{
  GSMutableAttributedStringTracker	*str;
  NSZone	*z = NSDefaultMallocZone();

  str = (GSMutableAttributedStringTracker*) NSAllocateObject(self, 0, z);

  str->_owner = RETAIN(as);
  return AUTORELEASE(str);
}

- (void) dealloc
{
  RELEASE(_owner);
  NSDeallocateObject(self);
}

- (unsigned int) length
{
  return [[_owner string] length];
}

- (unichar) characterAtIndex: (unsigned int)index
{
  return [[_owner string] characterAtIndex: index];
}

- (void)getCharacters: (unichar*)buffer
{
  return [[_owner string] getCharacters: buffer];
}

- (void)getCharacters: (unichar*)buffer range: (NSRange)aRange
{
  return [[_owner string] getCharacters: buffer range: aRange];
}

- (const char *) cString
{
  return [[_owner string] cString];
}

- (unsigned int) cStringLength
{
  return [[_owner string] cStringLength];
}

- (NSStringEncoding) fastestEncoding
{
  return [[_owner string] fastestEncoding];
}

- (NSStringEncoding) smallestEncoding
{
  return [[_owner string] smallestEncoding];
}

- (int) _baseLength
{
  return [[_owner string] _baseLength];
} 

- (void) encodeWithCoder: (NSCoder*)aCoder
{
  return [[_owner string] encodeWithCoder: aCoder];
}

- (Class) classForCoder
{
  return [[_owner string] classForCoder];
}

- (void) replaceCharactersInRange: (NSRange)aRange
		       withString: (NSString*)aString
{
  [_owner replaceCharactersInRange: aRange withString: aString]; 
}

@end

