/*************************************************************************
 * File Name: NSColor+MiscExtensions.m
 * Version  : 0.0 alpha
 * Date     : Thu 31-Aug-1997
 *************************************************************************
 *  COPYWHAT (C) 1997 by Tomi Engel
 *
 * This notice may not be removed from this source code.
 * The use and distribution of this software is governed by the
 * terms of the MiscKit license agreement.  Refer to the license
 * document included with the MiscKit distribution for the terms.
 *                    ALL RIGHTS RESERVED
 *
 *************************************************************************
 * Notes      :
 * Bugs       :
 * Author(s)  : tsengel
 * Last update: $Date: 1997/08/31 20:02:57 $
 * History    : $Log: NSColor+MiscExtensions.m,v $
 * History    : Revision 1.0  1997/08/31 20:02:57  tsengel
 * History    : Added to the MiscKit repository
 * History    :
 *************************************************************************/ 

#import "NSColor+MiscExtensions.h"

#import "NSArray+MiscExtensions.h"


@implementation NSColor (MiscExtensions)

/*"
This category is mainly intended to simplify the storage of colors in the defaults database. For this task we need a NSPPL compliant object like an NSString.
The general representation of a color is a string which contains multiple parameters separated by whitespaces (blanks). The first parameter is considered to be the name of the color space. The meaning of all subsequent parameters depends on the color space. Our format is almost identical with the output created by the #description method of NSColor.

 Since calibrated RGBA colors are the most common colors in non publishing Openstep applications they are an exception to the rule. Their string representation is allowed to have no color space parameter in the string. So if the color space is unknown it is considered to be a NSCalibratedRGBColorSpace.
 This exception makes it easier to manually create, adjust, modify or reuse already stored values from the defaults database.

 NSDynamicSystemColorSpace colors can be archived but will we treated as regular RGB colors. However this is not really useful since colors from that space are not static but unarchiving them will create a static RGB color. Since this is a conceptual problem it is not clear if we can provide a fix.

The regular NSColor methods refer to CMYK as CMYB since they are more focused on the real color components. Our methods are more focused on the color spaces and therefore stick to CMYK for naming.

#Note: We should come up with a more general and "bulletproof" archiving for other color spaces. Perhaps archiving a dictionary with a NSString type and NSData entry. This should allow for proper reconstructuion. Maybe the Pasteboard mechanims could be "misused" for this purpose sicne it then would even scale to yet unknown color spaces.
    The current solution does not really scale nicely since there is too much state hardcoded into this category.
    We would have to make categories for private classes (like NSCachedRGBColor) having a #stringRepresentation  method which knowns about the encoding... being nicer OO this has the problem of using undocumented classes.
"*/

+ (NSColor *)colorWithStringRepresentation:(NSString *)aString
/*"
   Returns a color instance which is described by aString. The first parameter of aString is supposed to be the color space name. If no decoding method for the color space is known it is considered to be a calibrated RGB representation. On failure this method returns nil.
    
"*/
{
    id		colorSpace = [[aString componentsSeparatedByString:@" "] objectAtIndex:0];

    if( [colorSpace isEqual:NSDeviceCMYKColorSpace] )
        return [self colorWithCMYKColorStringRepresentation:aString];

    else if( [colorSpace isEqual:NSDeviceWhiteColorSpace] ||
             [colorSpace isEqual:NSCalibratedWhiteColorSpace] )
        return [self colorWithWhiteColorStringRepresentation:aString];

    else if( [colorSpace isEqual:NSNamedColorSpace] )
        return [self colorWithNamedColorStringRepresentation:aString];

    // Otherwise we will always try to create a RGB color from the information we have.

    else
        return [self colorWithRGBColorStringRepresentation:aString];
}

+ (NSColor *)colorWithRGBColorStringRepresentation:(NSString *)aString
/*"
    Returns the NSColor instance created by #colorWithDeviceRed:green:blue:alpha: based on the four values stored in the string. The values must be seperated by blank. Since RGB colors are the most common colors, they don't necessarily have to contain the colorspace information in the specified string parameter.
    If a HSB encoded color is found #colorWithHSBColorStringRepresentation is called.
"*/
{
    id		parameterArray = [self _parameterArrayFromString:aString];
    id		colorSpace = [parameterArray objectAtIndex:0];

    // Less teh 3 parameters is useless...

    if( [parameterArray count] < 3 ) return nil;

    // If the second parameter is the "magic" HSB encoding then we will forward our request...

    if( [[parameterArray objectAtIndex:1] isEqual:@"HSBEncoding"] )
        return [self colorWithHSBColorStringRepresentation:aString];

    // Others lets check our facts...

    if( [parameterArray count] == 5 )
    {
        if( [colorSpace isEqual:NSDeviceRGBColorSpace] )
            return [self colorWithDeviceRed:[parameterArray floatAtIndex:1]
                                      green:[parameterArray floatAtIndex:2]
                                       blue:[parameterArray floatAtIndex:3]
                                      alpha:[parameterArray floatAtIndex:4]];

        else if( [colorSpace isEqual:NSCalibratedRGBColorSpace] )
            return [self colorWithCalibratedRed:[parameterArray floatAtIndex:1]
                                          green:[parameterArray floatAtIndex:2]
                                           blue:[parameterArray floatAtIndex:3]
                                          alpha:[parameterArray floatAtIndex:4]];
    }

    // And for backwards compatibility we will also give our best on other parametere numbers..

    else if( [parameterArray count] == 4 )
        return [self colorWithCalibratedRed:[parameterArray floatAtIndex:0]
                                      green:[parameterArray floatAtIndex:1]
                                       blue:[parameterArray floatAtIndex:2]
                                      alpha:[parameterArray floatAtIndex:3]];

    else if( [parameterArray count] == 3 )
        return [self colorWithCalibratedRed:[parameterArray floatAtIndex:0]
                                      green:[parameterArray floatAtIndex:1]
                                       blue:[parameterArray floatAtIndex:2]
                                      alpha:1];
    return nil;
}

+ (NSColor *)colorWithHSBColorStringRepresentation:(NSString *)aString;
/*"
    Returns the NSColor instance created from the valus stored inside aString. If the string did not contain a valid encoding of a HSBEncoded NSDeviceRGBColorSpace or NSCalibratedRGBColorSpace instance this method returns nil.
    This method is automatically call from #colorWithRGBColorStringRepresentation if a HSB encoded value is encountered.
"*/
{
    id		parameterArray = [self _parameterArrayFromString:aString];
    id		colorSpace = [parameterArray objectAtIndex:0];

    if( [parameterArray count] != 6 ||
        ![[parameterArray objectAtIndex:1] isEqual:@"HSBEncoding"] ) return nil;

    if( [colorSpace isEqual:NSDeviceRGBColorSpace] )
        return [self colorWithDeviceHue:[parameterArray floatAtIndex:2]
                             saturation:[parameterArray floatAtIndex:3]
                             brightness:[parameterArray floatAtIndex:4]
                                  alpha:[parameterArray floatAtIndex:5]];

    else if( [colorSpace isEqual:NSCalibratedRGBColorSpace] )
        return [self colorWithCalibratedHue:[parameterArray floatAtIndex:2]
                                 saturation:[parameterArray floatAtIndex:3]
                                 brightness:[parameterArray floatAtIndex:4]
                                      alpha:[parameterArray floatAtIndex:5]];
    else
        return nil;
}

+ (NSColor *)colorWithCMYKColorStringRepresentation:(NSString *)aString
/*"
    Returns the NSColor instance created from the valus stored inside aString. If the string did not contain a valid encoding of a NSDeviceCMYKColorSpace instance this method returns nil.
"*/
{
    id		parameterArray = [self _parameterArrayFromString:aString];
    id		colorSpace = [parameterArray objectAtIndex:0];

    if( [parameterArray count] != 6 ) return nil;

    if( [colorSpace isEqual:NSDeviceCMYKColorSpace] )
        return [self colorWithDeviceCyan:[parameterArray floatAtIndex:1]
                                 magenta:[parameterArray floatAtIndex:2]
                                  yellow:[parameterArray floatAtIndex:3]
                                   black:[parameterArray floatAtIndex:4]
                                   alpha:[parameterArray floatAtIndex:5]];
    else
        return nil;
}

+ (NSColor *)colorWithWhiteColorStringRepresentation:(NSString *)aString
/*"
    Returns the NSColor instance created from the valus stored inside aString. If the string did not contain a valid encoding NSDeviceWhiteColorSpace or NSCalibratedWhiteColorSpace instance this method returns nil.
"*/
{
    id		parameterArray = [self _parameterArrayFromString:aString];
    id		colorSpace = [parameterArray objectAtIndex:0];

    if( [parameterArray count] != 3 ) return nil;

    if( [colorSpace isEqual:NSDeviceWhiteColorSpace] )
        return [self colorWithDeviceWhite:[parameterArray floatAtIndex:1]
                                    alpha:[parameterArray floatAtIndex:2]];

    else if( [colorSpace isEqual:NSCalibratedWhiteColorSpace] )
        return [self colorWithCalibratedWhite:[parameterArray floatAtIndex:1]
                                        alpha:[parameterArray floatAtIndex:2]];
    else
        return nil;
}

+ (NSColor *)colorWithNamedColorStringRepresentation:(NSString *)aString
/*"
    Returns the NSColor instance created from the valus stored inside aString. If the string did not contain a valid encoding NSNamedColorSpace instance this method returns nil.

    #Bug:  Due to a hacky parameter decoding method this will fail if catalog or color names contain whitespaces .. yuck.
"*/
{
    id		parameterArray = [self _parameterArrayFromString:aString];
    id		colorSpace = [parameterArray objectAtIndex:0];

    if( [parameterArray count] != 3 ) return nil;

    if( [colorSpace isEqual:NSNamedColorSpace] )
        return [self colorWithCatalogName:[parameterArray objectAtIndex:1]
                                colorName:[parameterArray objectAtIndex:2]];
    else
        return nil;
}

+ (NSArray *)_parameterArrayFromString:(NSString *)aString
/*"
     This private method is used to create an array of whitespace separated parameters from inside a string. This is an evil HACK!! since it does not work if the string contains quoted parameters...this might/will cause trouble with named colorspaces !!!
"*/
{
    // What we do is to remove trailing and

    // <<HACKK>> This really needs some useful NSScanner support whcih handles escaped character etc nicely.
    // We propalby should make a general NSString cateory for this sort of scanneing...or a NSSCanner category etc.
    // something like componetensSeparatedByCharactersFromSet:... withQuotedValues:YES/NO what so ever...

    // NSString is already doing such stuff for the properyl lists...we should find a way to reuse its code...

    return [aString componentsSeparatedByString:@" "];
}

- (NSString *)stringRepresentation
/*"
    Returns a string which represents the values of the color instance. The recommended format of the string contains the value returned by #colorSpaceName followed by a number of additional values. All parameters must be separated by whitespace inside the generated string.
    Subclasses should implement a proper stringRepresentation method since the default behavior is to encode unknown colors using the NSCalibratedRGBColorSpace.

 This method never generates a HSB representation of a color since HSB is just an alternative encoding of RGB colors and are not used  by default.

    #Note: Since we can't implement this "smoothly" for Apples private color classes we have hardcoded string representations for all supported color spaces.
"*/
{
    id		ourColorSpace = [self colorSpaceName];

    if( [ourColorSpace isEqual:NSDeviceWhiteColorSpace] ||
        [ourColorSpace isEqual:NSCalibratedWhiteColorSpace] )
        return [self whiteColorStringRepresentation];

    else if( [ourColorSpace isEqual:NSDeviceCMYKColorSpace] )
        return [self cmykColorStringRepresentation];

    else if( [ourColorSpace isEqual:NSNamedColorSpace] )
        return [self namedColorStringRepresentation];

    // All other colors are converted to calibrated RGB and then archived.
    // While this is "lossy" since unarchiving will create a different color
    // instance...it at least preserves the color somehow.
    // The affected colorspaces are all NSColor suclasses which don't
    // do a proper encoding and color from the NSDynamicSystemColorSpace color space.

    else
        return [self rgbColorStringRepresentation];
}

- (NSString *)rgbColorStringRepresentation
/*"
    The created string contains the colorspace name and decimal number representations of the red, green, blue and alpha components of the current color.
     If the color is not from the NSDeviceRGBColorSpace or NSCalibratedRGBColorSpace this method will try converting it, by using #{colorUsingColorSpaceName:} into a color from the NSCalibratedRGBColorSpace.
     Returns nil if the conversion was not possible.
"*/
{
    id		ourColorSpace = [self colorSpaceName];

    if( [ourColorSpace isEqual:NSDeviceRGBColorSpace] ||
        [ourColorSpace isEqual:NSCalibratedRGBColorSpace] )
        return [NSString stringWithFormat:@"%@ %f %f %f %f",
            ourColorSpace,
            [self redComponent],
            [self greenComponent],
            [self blueComponent],
            [self alphaComponent]];

    else
        return [[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace] rgbColorStringRepresentation];
}

- (NSString *)hsbColorStringRepresentation
/*"
    The created string contains the colorspace name, a special HSBEncoding note and decimal number representations of the hue, saturation, brightness and alpha components of the current color.
     If the color is not from the NSDeviceRGBColorSpace or NSCalibratedRGBColorSpace this method will try converting it, by using #{colorUsingColorSpaceName:} into a color from the NSCalibratedRGBColorSpace.
     Returns nil if the conversion was not possible.

     Since "HSB colors" are colors from the RGB colorspace they are encoded with a special "HSB" remark. 
"*/
{
    id		ourColorSpace = [self colorSpaceName];

    if( [ourColorSpace isEqual:NSDeviceRGBColorSpace] ||
        [ourColorSpace isEqual:NSCalibratedRGBColorSpace] )
        return [NSString stringWithFormat:@"%@ %@ %f %f %f %f",
            ourColorSpace,
            @"HSBEncoding",
            [self hueComponent],
            [self saturationComponent],
            [self brightnessComponent],
            [self alphaComponent]];

    else
        return [[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace] hsbColorStringRepresentation];
}

- (NSString *)cmykColorStringRepresentation
/*"
        The created string contains the colorspace name and decimal number representations of the cyan, magenta, yellow, black and alpha components of the current color.
 If the color is not from the NSDeviceCMYKColorSpace this method will try converting it, by using #{colorUsingColorSpaceName:}.
 Returns nil if the conversion was not possible.
"*/
{
    id		ourColorSpace = [self colorSpaceName];

    if( [ourColorSpace isEqual:NSDeviceCMYKColorSpace] )
    return [NSString stringWithFormat:@"%@ %f %f %f %f %f",
        ourColorSpace,
        [self cyanComponent],
        [self magentaComponent],
        [self yellowComponent],
        [self blackComponent],
        [self alphaComponent]];

    else
        return [[self colorUsingColorSpaceName:NSDeviceCMYKColorSpace] cmykColorStringRepresentation];
}

- (NSString *)whiteColorStringRepresentation
/*"
    The created string contains the colorspace name and decimal number representations of the white and alpha components.
     If the color is not from the NSDeviceWhiteColorSpace or NSCalibratedWhiteColorSpace this method will try converting it, by using #{colorUsingColorSpaceName:}, into a color from the NSCalibratedWhiteColorSpace.
    Returns nil if the conversion was not possible. 
"*/
{
    id		ourColorSpace = [self colorSpaceName];

    if( [ourColorSpace isEqual:NSDeviceWhiteColorSpace] ||
        [ourColorSpace isEqual:NSCalibratedWhiteColorSpace] )
        return [NSString stringWithFormat:@"%@ %f %f",
            ourColorSpace,
            [self whiteComponent],
            [self alphaComponent]];

    else
        return [[self colorUsingColorSpaceName:NSCalibratedWhiteColorSpace] whiteColorStringRepresentation];
}

- (NSString *)namedColorStringRepresentation
/*"
    The returned string contains the colorspace name and the strings for the colors catalog and color name which need to be properly quoted and escaped when necessary.
 If the color is not from the NSNamedColorSpace this method returns nil. No conversion is attempted since it is unclear which color catalog to use. 
"*/
{
    id		catalogName;
    id		colorName;

    if( [[self colorSpaceName] isEqual:NSNamedColorSpace] )
    {
        catalogName = [self catalogNameComponent];
        colorName = [self colorNameComponent];

        // <<HACK>. here we should ensure that the strings are properly quoted and escaped when necessary.
        // For PANTONE it seems save to ignor it for the current time.

        return [NSString stringWithFormat:@"%@ %@ %@",
            NSNamedColorSpace,
            catalogName,
            colorName];
    }
    else
        return nil;
}

@end
