#import "IconView.h"
#import "ShelfView.h"
#import "Controller.h"

#import <appkit/workspaceRequest.h>
#import <appkit/appkit.h>
#import <mach/mach.h>
#import <bsd/sys/file.h>


#define	IMAGE_INSET	2

@implementation NXImage(UsefulMethod)
- scaleToFitInside:(NXSize) size max:(NXSize) maxSize
{
    NXSize	imageSize;
    float	scale;

    [self getSize:&imageSize];

    scale = MIN(size.width/imageSize.width, size.height/imageSize.height);

    size.width = (scale * (float)imageSize.width);
    size.height = (scale * (float)imageSize.height);
    if (size.width > maxSize.width)
    	size.width = maxSize.width;
    if (size.height > maxSize.height)
    	size.height = maxSize.height;
 
    [self setSize:&size];
    return self;
}
@end


@implementation IconView : View

static id	scaledHilite;
static id	scaledTile;

+ initialize
{
  scaledHilite = nil;
  scaledTile = nil;
  [self resetCachedImages];
  return self;
}

+ resetCachedImages
{
  [scaledTile free];
  scaledTile = [[NXImage findImageNamed:"NXAppTile"]
		copyFromZone:[self zone]];

  [scaledHilite free];
  scaledHilite = [[NXImage findImageNamed:"hilite"]
		  copyFromZone:[self zone]];
  return self;
}

+ hilite
{
  return scaledHilite;
}

+ appTile
{
  return scaledTile;
}

/*
 *  Create a copy of an IconView (or an IconView subclass).  Note that this
 *  method cheats by potentially changing the class of the copied image.
 */
+ copyIconView:aView
{
    NXRect		aFrame;
    void		*oldData;
    unsigned int	len;
    
    [aView getFrame:&aFrame];
    [aView getData:&oldData andLength:&len];

    return [[self allocFromZone:[aView zone]] initFrame:&aFrame
			  image:[[aView image] copy]
			   data:oldData andLength:len
			useSize:YES
		      useTile:[aView useTile]];
}


/*
 *  Initialize a new IconView.  The only thing sneaky about this
 *  method is that we may interpret the size of the frame as the
 *  max size for the frame.
 */
- initFrame:(const NXRect *) newFrame
 image:anImage
 data:(const void *) someData
 andLength:(unsigned int) newLength
 useSize:(BOOL) sizeValid
 useTile:(BOOL) showTile
{
    NXSize	imageSize = {48, 48};

    tempImage = NULL;

    selected = NO;
    ghost = NO;
    useTile = showTile;

    appTile = [IconView appTile];
    [appTile getSize:&tileMax];

    hilite = [IconView hilite];
    [hilite getSize:&hiliteMax];

    image = anImage;
    imageMax = imageSize;
    
    /*
     *  Copy the data, with null termination if it's not already.
     */
    if (newLength > 0 && *((char *)someData + newLength - 1))
    	length = newLength + 1;
    else
	length = newLength;

    data = NXZoneMalloc([self zone], length);
    bcopy(someData, data, length);
    *((char *)data + length - 1) = '\0';

    /*
     *  Allocate a cell, and slam the filename into it.  Make sure to use
     *  only the last component of the path.
     */
    titleCell = [[TextFieldCell allocFromZone:[self zone]] init];
    [titleCell setAlignment:NX_CENTERED];
    [titleCell setBackgroundTransparent:YES];

    if (rindex(data, '/'))
	[titleCell setStringValue:rindex(data, '/') + 1];
    else
	[titleCell setStringValue:data];

    /*
     *  If there's no frame, make one that's the right size to hold
     *  everything.
     */
    if (!sizeValid) {
    	NXSize	titleSize;
	NXRect	aRect = { {0,0}, {0,0} };

	if (useTile) {
	  [appTile getSize:&imageSize];
	  if (newFrame)
	    aRect.origin = newFrame->origin;
	  aRect.size.height = imageSize.height;
	  aRect.size.width = imageSize.width;
	}
	else {
	  [hilite getSize:&imageSize];
	  [titleCell calcCellSize:&titleSize];

	  if (newFrame)
	    aRect.origin = newFrame->origin;
	  aRect.size.height = imageSize.height + titleSize.height;
	  aRect.size.width = MAX(titleSize.width, imageSize.width);
	}
	[super initFrame:&aRect];
    }
    else
	[super initFrame:newFrame];

    [self setImageSize];

    return self;
}


- initFromDragContext:(id <NXDraggingInfo>)context andSize:(NXSize *) aSize
  isOn:(BOOL)showTile;
{
    NXImage		*copiedImage = [context draggedImageCopy];
    Pasteboard		*pb = [Pasteboard newName:NXDragPboard];
    char		*pbData;
    NXRect		aRect = { {0,0}, {0,0} };
    NXRect		*rectPtr = NULL;
    unsigned int	len;

    [pb readType:NXFilenamePboardType data:&pbData length:&len];

    if (aSize != NULL) {
    	rectPtr = &aRect;
	aRect.size = *aSize;
    }

    [self initFrame:rectPtr image:copiedImage data:pbData andLength:len+1
	  useSize:aSize != NULL useTile:showTile];

    return self;
}


- free
{
    free(data);
    [image free];
    [titleCell free];
    [tempImage free];
    return [super free];
}


- (NXCoord) cellHeight
{
    NXSize	cellSize;
    [titleCell calcCellSize:&cellSize];
    return cellSize.height;
}


- getImagePoint:(NXPoint *) imageLoc 
 andTilePoint:(NXPoint *)tileLoc 
 andHilitePoint:(NXPoint *) hiliteLoc
{
    NXCoord	cellHeight;
    NXPoint	baseSpot;
    NXSize	hiliteSize, imageSize, tileSize;

    cellHeight = [self cellHeight];

    [appTile getSize:&tileSize];
    [image getSize:&imageSize];
    [hilite getSize:&hiliteSize];

    /* Two ways to calculate where everybody goes.  If we are going to use
       the background tile, then find out where that goes, and center the
       image on that, shifted vertically so that the cell can fit
       underneath.							   */
    if (useTile) {
      baseSpot.x = (bounds.size.width - tileSize.width) / 2;
      baseSpot.y = (bounds.size.height - tileSize.height) / 2;
      if (tileLoc) {
	tileLoc->x = baseSpot.x;	tileLoc->y = baseSpot.y;
      }
	
      /* The image then needs to be put in the upper center of the
	 background tile, with enough space underneath to fit the cell.  */
      if (imageLoc) {
	imageLoc->x = baseSpot.x + (tileSize.width - imageSize.width) / 2;
	imageLoc->y = baseSpot.y + cellHeight + 
	  ((tileSize.height - 	
	    (imageSize.height + cellHeight + IMAGE_INSET)) / 2);
      }

      if (hiliteLoc) {
	hiliteLoc->x = (bounds.size.width - hiliteSize.width) / 2;
	hiliteLoc->y = (bounds.size.height - hiliteSize.height) / 2;
      }
    }
    else {

      baseSpot.x = (bounds.size.width - imageSize.width) / 2;
      baseSpot.y = cellHeight + 
	(bounds.size.height - imageSize.height - cellHeight) / 2;

      if (imageLoc) {
	imageLoc->x = baseSpot.x;
	imageLoc->y = baseSpot.y;
      }

      if (hiliteLoc) {
	hiliteLoc->x = baseSpot.x - ((hiliteSize.width - imageSize.width) / 2);
	hiliteLoc->y = baseSpot.y - ((hiliteSize.height -
				      imageSize.height) / 2);
      }
    }

    return self;
}


- (void) setImageSize
{
  NXSize	imageSize;

  [image setScalable:YES];
  [hilite setScalable:YES];
  [appTile setScalable:YES];

  imageSize = bounds.size;
    
  /* If we are using the background tile, then we need to make sure that
     the image size is slightly less than the real size, or it could
     completely obscure a smaller tile.				   */
  if (useTile)
    {
      [appTile scaleToFitInside:imageSize max:tileMax];
      [hilite scaleToFitInside:imageSize max:hiliteMax];
      imageSize.width -= IMAGE_INSET;
      imageSize.height -= (IMAGE_INSET + [self cellHeight]);
      [image scaleToFitInside:imageSize max:imageMax];
    }
  else
    {
      imageSize.height -= [self cellHeight];
      [hilite scaleToFitInside:imageSize max:hiliteMax];
      [image scaleToFitInside:imageSize max:imageMax];
    }
}


- sizeTo:(NXCoord) width :(NXCoord) height
{
    [super sizeTo:width :height];
    [self setImageSize];
    [self display];
    return self;
}


- drawSelf:(const NXRect *) rects :(int) rectCount
{
  NXPoint	tilePoint, imagePoint, hilitePoint;
  NXRect	cellRect, tileRect;
  NXSize	tileSize, tempSize;

  if (tempImage == NULL)
    tempImage = [[NXImage alloc] initSize:&bounds.size];
  else {
    [tempImage getSize:&tempSize];
    if ((tempSize.width != bounds.size.width) ||
	(tempSize.height != bounds.size.height)) {
      [tempImage free];
      tempImage = [[NXImage alloc] initSize:&bounds.size];
    }
  }

  [self getImagePoint:&imagePoint andTilePoint:&tilePoint 
	andHilitePoint:&hilitePoint];

  NXSetRect(&cellRect, 0, hilitePoint.y - [self cellHeight],
	    bounds.size.width, [self cellHeight]);

  if (useTile) {
    [appTile getSize:&tileSize];
    NXSetRect(&tileRect, tilePoint.x, tilePoint.y, tileSize.width,
	      tileSize.height);
    NXSetRect(&cellRect, 0, tilePoint.y + IMAGE_INSET,
	      tileSize.width, [self cellHeight]);
  }

  /* Tweak the color of the text to make sure that it is visible.	   */
  if (NXBrightnessComponent([superview backgroundColor]) < NX_DKGRAY + 0.01)
    [titleCell setTextGray:[self isGhost] ? NX_LTGRAY : NX_WHITE];
  else
    [titleCell setTextGray:[self isGhost] ? NX_DKGRAY : NX_BLACK];

  /* Are we a ghost?  If so, create a transparent version of ourselves.  */
  if ([self isGhost]) {

    /* Create the composite image used for ghosting.			   */
    if (useTile) {
      if ([tempImage lockFocus]) {
	PSsetalpha(0);
	PSsetgray(1);
	NXRectFill(&bounds);
	[appTile composite:NX_SOVER toPoint:&tilePoint];
	[image composite:NX_SOVER toPoint:&imagePoint];
	[tempImage unlockFocus];
      }
      [tempImage dissolve:0.666 toPoint:&bounds.origin];
    }
    else {
      if ([tempImage lockFocus]) {
	PSsetalpha(0);
	PSsetgray(0);
	NXRectFill(&bounds);
	[image dissolve:0.666 toPoint:&imagePoint];
	[tempImage unlockFocus];
      }
      [tempImage composite:NX_SOVER toPoint:&bounds.origin];
    }
  }
  else {

    /* Are we highlighting ourselves?  If we are using background tiles, then
       we have to create another temporary image, set it's background to
       transparent, then draw a white, non-transparent rectangle on it, and
       then draw in the ghost.						   */
    if ([self state]) {

      if (useTile) {
	if ([tempImage lockFocus]) {
	  PSsetalpha(1);
	  PSsetgray(0);
	  NXRectFill(&bounds);
	  [appTile composite:NX_DOUT toPoint:&tilePoint];
	  [tempImage unlockFocus];
	}
	[appTile composite:NX_SOVER toPoint:&tilePoint];
	[image composite:NX_SOVER toPoint:&imagePoint];
	[titleCell drawInside:&cellRect inView:self];
	[tempImage dissolve:0.666 toPoint:&bounds.origin];
      }
      else {
	[hilite composite:NX_SOVER toPoint:&hilitePoint];
	[image composite:NX_SOVER toPoint:&imagePoint];
	[titleCell drawInside:&cellRect inView:self];
      }
    }
    else {
      NXRect	aRect;

      aRect = bounds;
      [self convertRect:&aRect toView:superview];
      [superview lockFocus];
      [superview drawSelf:&aRect :1];
      [superview unlockFocus];
      if (useTile) {
	[appTile composite:NX_SOVER toPoint:&tilePoint];
      }
      [image composite:NX_SOVER toPoint:&imagePoint];
      [titleCell drawInside:&cellRect inView:self];
    }


  }

  return self;
}


- image
{
    return image;
}

- (BOOL) useTile
{
  return useTile;
}

- getData:(void **) aPtr andLength:(unsigned int *) aLength
{
    *aPtr = data;
    *aLength = length;
    return self;
}


- setGhost:(BOOL) newGhost
{
    ghost = newGhost;
    return self;
}


- (BOOL) isGhost
{
    return ghost;
}


- (int) state
{
    return selected;
}


- setState:(int) flag
{
    if (flag ^ selected) {
	selected = flag ? YES : NO;
    	[self display];
    }
    return self;
}

@end
