/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Chimera code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Simon Fraser <sfraser@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#import <AppKit/AppKit.h>		// for NSStringDrawing.h

#import "NSString+Utils.h"

#include "nsString.h"
#include "nsPromiseFlatString.h"
#include "nsCRT.h"


@implementation NSString (ChimeraStringUtils)


+ (id)ellipsisString
{
  static NSString* sEllipsisString = nil;
  if (!sEllipsisString)
  {
    unichar ellipsisChar = 0x2026;
    sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:1];
  }
  
  return sEllipsisString;
}

+ (id)stringWithPRUnichars:(const PRUnichar*)inString
{
  if (inString)
    return [self stringWithCharacters:inString length:nsCRT::strlen(inString)];
  else
    return [self string];
}

+ (id)stringWith_nsAString:(const nsAString&)inString
{
  nsPromiseFlatString flatString = PromiseFlatString(inString);
  return [self stringWithCharacters:flatString.get() length:flatString.Length()];
}

#define ASSIGN_STACK_BUFFER_CHARACTERS  256

- (void)assignTo_nsAString:(nsAString&)ioString
{
  PRUnichar     stackBuffer[ASSIGN_STACK_BUFFER_CHARACTERS];
  PRUnichar*    buffer = stackBuffer;
  
  // XXX maybe fix this to use SetLength(0), SetLength(len), and a writing iterator.
  unsigned int len = [self length];
  
  if (len + 1 > ASSIGN_STACK_BUFFER_CHARACTERS)
  {
    buffer = (PRUnichar *)malloc(sizeof(PRUnichar) * (len + 1));
    if (!buffer)
      return;
  }

  [self getCharacters: buffer];   // does not null terminate
  ioString.Assign(buffer, len);
  
  if (buffer != stackBuffer)
    free(buffer);
}

- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet
{
  NSScanner*       cleanerScanner = [NSScanner scannerWithString:self];
  NSMutableString* cleanString    = [NSMutableString stringWithCapacity:[self length]];
  
  while (![cleanerScanner isAtEnd])
  {
    NSString* stringFragment;
    if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
      [cleanString appendString:stringFragment];

    [cleanerScanner scanCharactersFromSet:characterSet intoString:nil];
  }

  return cleanString;
}

- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet withString:(NSString*)string
{
  NSScanner*       cleanerScanner = [NSScanner scannerWithString:self];
  NSMutableString* cleanString    = [NSMutableString stringWithCapacity:[self length]];
  
  while (![cleanerScanner isAtEnd])
  {
    NSString* stringFragment;
    if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
      [cleanString appendString:stringFragment];

    if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil])
      [cleanString appendString:string];
  }

  return cleanString;
}

- (NSString*)stringByTruncatingTo:(unsigned in)maxCharacters at:(ETruncationType)truncationType
{
  if ([self length] > maxCharacters)
  {
    NSMutableString *mutableCopy = [self mutableCopy];
    [mutableCopy truncateTo:maxCharacters at:truncationType];
    return [mutableCopy autorelease];
  }

  return [[self copy] autorelease];
}

- (PRUnichar*)createNewUnicodeBuffer
{
  PRUint32 length = [self length];
  PRUnichar* retStr = (PRUnichar*)nsMemory::Alloc((length + 1) * sizeof(PRUnichar));
  [self getCharacters:retStr];
  retStr[length] = PRUnichar(0);
  return retStr;
}


@end


@implementation NSMutableString (ChimeraMutableStringUtils)

- (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType
{
  if ([self length] > maxCharacters)
  {
  NSRange replaceRange;
  replaceRange.length = [self length] - maxCharacters;

  switch (truncationType)
  {
    case kTruncateAtStart:
      replaceRange.location = 0;
      break;

    case kTruncateAtMiddle:
      replaceRange.location = maxCharacters / 2;
      break;

    case kTruncateAtEnd:
      replaceRange.location = maxCharacters;
      break;

    default:
#if DEBUG
      NSLog(@"Unknown truncation type in stringByTruncatingTo::");
#endif          
      replaceRange.location = maxCharacters;
      break;
  }
  
  [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisString]];
  }
}


- (void)truncateToWidth:(float)maxWidth at:(ETruncationType)truncationType withAttributes:(NSDictionary *)attributes
{
  // First check if we have to truncate at all.
  if ([self sizeWithAttributes:attributes].width > maxWidth)
  {
    // Essentially, we perform a binary search on the string length
    // which fits best into maxWidth.

    float width;
    int lo = 0;
    int hi = [self length];
    int mid;

    // Make a backup copy of the string so that we can restore it if we fail low.
    NSMutableString *backup = [self mutableCopy];
    
    while (hi >= lo)
    {
      mid = (hi + lo) / 2;
      
      // Cut to mid chars and calculate the resulting width
      [self truncateTo:mid at:truncationType];
      width = [self sizeWithAttributes:attributes].width;
      
      if (width > maxWidth) {
        // Fail high - string is still to wide. For the next cut, we can simply
        // work on the already cut string, so we don't restore using the backup. 
        hi = mid - 1;
      } else if (width == maxWidth) {
        // Perfect match, abort the search.
        break;
      } else {
        // Fail low - we cut off to much. Restore the string before cutting again.
        lo = mid + 1;
        [self setString:backup];
      }
    }
    // Perform the final cut (unless this was already a perfect match).
    if (width != maxWidth)
      [self truncateTo:hi at:truncationType];
    [backup release];
  }
}

@end
