//
//  Lynkeos
//  $Id$
//
//  Created by Jean-Etienne LAMIAUD on Wed Sep 24 2003.
//  Copyright (c) 2003-2025. Jean-Etienne LAMIAUD
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//

#include <math.h>

#include "geometry.h"
#include "ProcessStackManager.h"
#include "MyImageView.h"

#define K_MAX_ZOOM 4.0
#define K_TILE_SIZE 256

static const double MM_LN_10 = -M_LN10;

#if !GNUSTEP
@interface ImageViewTile : NSObject
{
@public
   CGImageRef tileImage;
   CGRect tileRect;
}
@end

@interface MyImageView(Tile)
- (void) applyTileAt:(CGPoint)at;
@end

@interface ImageViewTileOperation : NSOperation
{
@private
   NSRect _requestedReq;
   MyImageView *_view;
}

- (id) initWithRect:(NSRect)rect inView:(NSObject *)view;
@end

@implementation ImageViewTileOperation

- (id) initWithRect: (NSRect)rect inView: (MyImageView *)view
{
   if ((self = [super init]) != nil)
   {
      _requestedReq = rect;
      _view = view;
   }

   return self;
}

- (void) main
{
   if (!self.cancelled)
      [_view applyTileAt:_requestedReq.origin];
}
@end
#endif

/*!
 * @abstract Zoom management part of MyImageView.
 */
@interface MyImageView(Zoom)

/*!
 * @method applyZoom:from:
 * @abstract Set the zoom factor to the required value.
 * @param newZoom The zoom factor to apply
 * @param sender The object asking for zoom
 */
- (void) applyZoom :(double)newZoom from:(id)sender withRedraw:(BOOL)redraw;

/*!
 * @method stepZoom:
 * @abstract Step the zoom factor to the nearest half integer power of two.
 * @param step The step to apply to the log2 of the zoom factor.
 */
- (void) stepZoom :(double)step;
@end

#if !GNUSTEP
@implementation MyImageView(Tile)
- (void) applyTileAt:(CGPoint)at
{
   if (_drawnTile == NULL)
      return;

   uint8_t *tileDrawn = &_drawnTile[(int)(at.y / K_TILE_SIZE)*_tilePerLine + (int)(at.x / K_TILE_SIZE)];
   if (__sync_bool_compare_and_swap(tileDrawn, Blank, Drawing))
   {
      // It is up to us to draw it
      CGContextRef ctx = NULL;

      if (_imageLayer != NULL)
         ctx = CGLayerGetContext(_imageLayer);

      if (ctx != NULL)
      {
         // Retrieve the image (converting the coordinates to bitmap reference)
         CGRect r = {at,{K_TILE_SIZE, K_TILE_SIZE}};
         if (r.origin.x < 0)
         {
            r.size.width += r.origin.x;
            r.origin.x = 0;
         }
         if ((r.origin.x + r.size.width) > _itemSize.width)
            r.size.width = _itemSize.width - r.origin.x;
         if (r.origin.y < 0)
         {
            r.size.height += r.origin.y;
            r.origin.y = 0;
         }
         if ((r.origin.y + r.size.height) > _itemSize.height)
            r.size.height = _itemSize.height - r.origin.y;
         CGRect itemRect = r;
         itemRect.origin.y = [_item imageSize].height - r.origin.y - r.size.height;
         CGImageRef img = [_item getImageTileInRect:itemRect];

         CGContextDrawImage(ctx, r, img);

         CGImageRelease(img);
      }
      *tileDrawn = Available;
   }
   else
   {
      // Wait for tile availibitity, if needed
      while (*tileDrawn != Available)
         sched_yield();
   }
}
@end
#endif

@implementation MyImageView(Zoom)

- (void) applyZoom :(double)newZoom from:(id)sender withRedraw:(BOOL)redraw
{
   NSRect newFrame, newBounds = _canvasRect;
   NSRect visible = [self visibleRect];
   NSRect enclosing = [[self superview] bounds];

   // Adjust the enclosing bounds to the intended zoom
   enclosing.origin.x /= newZoom;
   enclosing.origin.y /= newZoom;
   enclosing.size.width /= newZoom;
   enclosing.size.height /= newZoom;

   // Set the bounds to the largest between image rect and enclosing NSClipView bounds
   if (newBounds.origin.x > enclosing.origin.x)
   {
      newBounds.size.width += newBounds.origin.x - enclosing.origin.x;
      newBounds.origin.x = enclosing.origin.x;
   }
   if ((newBounds.origin.x + newBounds.size.width) < (enclosing.origin.x + enclosing.size.width))
      newBounds.size.width  = enclosing.origin.x + enclosing.size.width - newBounds.origin.x;
   if (newBounds.origin.y > enclosing.origin.y)
   {
      newBounds.size.height += newBounds.origin.y - enclosing.origin.y;
      newBounds.origin.y = enclosing.origin.y;
   }
   if ((newBounds.origin.y + newBounds.size.height) < (enclosing.origin.y + enclosing.size.height))
      newBounds.size.height  = enclosing.origin.y + enclosing.size.height - newBounds.origin.y;

   // Apply zoom
   if ( newBounds.size.width != 0 )
   {
      newFrame.size.width = newBounds.size.width * newZoom;
      newFrame.origin.x = newBounds.origin.x * newZoom;
   }
   else
   {	// NSView don't like null sizes
      newFrame.size.width = 1;
      newFrame.origin.x = 0;
      newBounds.size.width = 1;
      newBounds.origin.x = 0;
   }

   if ( newBounds.size.height != 0 )
   {
      newFrame.size.height = newBounds.size.height * newZoom;
      newFrame.origin.y = newBounds.origin.y * newZoom;
   }
   else
   {	// NSView don't like null sizes
      newFrame.size.height = 1;
      newFrame.origin.y = 0;
      newBounds.size.height = 1;
      newBounds.origin.y = 0;
   }

   [self setFrameSize:newFrame.size];
   [self setFrameOrigin:newFrame.origin];
   [self setBoundsSize:newBounds.size];
   [self setBoundsOrigin:newBounds.origin];

   if ( newZoom != _zoom )
   {
      // Adjust scroll to keep center still
      if ( _canvasRect.size.width != 0 && _canvasRect.size.height != 0 )
      {
         visible.origin.x += (1 - _zoom/newZoom)*visible.size.width/2;
         if ( visible.origin.x < 0 )
            visible.origin.x = 0;
         visible.origin.y += (1 - _zoom/newZoom)*visible.size.height/2;
         if ( visible.origin.y < 0 )
            visible.origin.y = 0;
         visible.size.width *= _zoom/newZoom;
         visible.size.height *= _zoom/newZoom;

         [self scrollRectToVisible:visible];
      }

      _zoom = newZoom;
      if ( sender != _zoomField )
         [_zoomField setDoubleValue:_zoom*100.0];
      if ( sender != _zoomSlider )
         [_zoomSlider setDoubleValue:log(_zoom)/log(K_MAX_ZOOM)];

      [[NSNotificationCenter defaultCenter] postNotificationName: LynkeosImageViewZoomDidChangeNotification
                                                          object: self];
   }

   if (redraw)
      // Make me redraw
      [self setNeedsDisplay:YES];
}

- (void) stepZoom :(double)step
{
   double z = log(_zoom)/log(2);

   // Get the wanted half integer value
   if ( step < 0.0 && z > 0.0 )
      z += 0.49999999;
   else if ( step > 0.0 && z < 0.0 )
      z -= 0.49999999;

   z = (double)((int)((z+step)*2))/2.0;
   z = exp(log(2)*z);

   [self applyZoom: z from:nil withRedraw:YES];
}
@end

@implementation MyImageView

// Initializations and allocation stuff
- (id)initWithFrame:(NSRect)frameRect
{
   LynkeosIntegerRect nowhere = { {0,0}, {0,0} };

   if ( (self = [super initWithFrame:frameRect]) == nil )
      return( self );

   _selectionDelegate = nil;
   _item = nil;
   _itemSequenceNumber = 0;
   _itemSize = LynkeosMakeIntegerSize(0, 0);
   _imageTransform = nil;
#if GNUSTEP
   _imageRep = nil;
#else
   _tilesQueue = [[NSOperationQueue alloc] init];
   _layerContext = NULL;
   _imageLayer = NULL;
   _drawnTile = NULL;
   _tilePerLine = 0;
#endif

   _zoom = 1.0;

   _selectionMode = NoSelection;
   _savedSelectionMode = NoSelection;
   _freezeCounter = 0;
   _selectionOrigin = nowhere.origin;
   _lastPoint = nowhere.origin;
   _selection = [[NSMutableArray array] retain];
   _inProgressSelection = nowhere;
   _currentSelectionIndex = NSNotFound;
   _previousSelectionIndex = NSNotFound;
   _autoscrollTimer = nil;

   [self initCursors];

#if !GNUSTEP
   self.canDrawConcurrently = YES;
#endif

    return self;
}

- (void) dealloc
{
#if GNUSTEP
   if ( _imageRep != nil )
      [_imageRep release];
#else
   if (_layerContext != NULL)
      CGContextRelease(_layerContext);
   if ( _imageLayer != NULL )
      CGLayerRelease(_imageLayer);
   if (_drawnTile != NULL)
   {
      uint8_t *ary = _drawnTile;
      _drawnTile = NULL;
      free(ary);
   }
#endif
   if ( _item != nil )
      [_item release];
   if ( _imageTransform != nil )
      [_imageTransform release];

   [_selection release];
   [_crossCursor release];
   [_leftCursor release];
   [_rightCursor release];
   [_topCursor release];
   [_bottomCursor release];
   [_topLeftCursor release];
   [_topRightCursor release];
   [_bottomLeftCursor release];
   [_bottomRightCursor release];
   [_insideCursor release];

   [super dealloc];
}

- (void) awakeFromNib
{
   [_blackText setEnabled:NO];
   [_whiteText setEnabled:NO];
   [_blackWhiteSlider setEnabled:NO];
   [_gammaText setEnabled:NO];
   [_gammaSlider setEnabled:NO];
   [_blackText setStringValue:@""];
   [_whiteText setStringValue:@""];
   [_gammaText setStringValue:@""];   
}

// Image and zoom
- (IBAction)doZoom:(id)sender
{
   double z;

   if ( sender == _zoomSlider )
      z = exp(log(K_MAX_ZOOM)*[sender doubleValue]);
   else
      z = [sender doubleValue]/100.0;

   [self applyZoom: z from:sender withRedraw:YES];
}

- (IBAction)moreZoom:(id)sender
{
   if ( _zoom < K_MAX_ZOOM )
      [self stepZoom:0.5];
}

- (IBAction)lessZoom:(id)sender
{
   if ( _zoom > 1.0/K_MAX_ZOOM )
      [self stepZoom:-0.5];	// To be improved in stepZoom
}

- (IBAction) blackWhiteChange :(id)sender
{
   // Reconcile slider and text fields
   double black = 0, white = 255;
   if ( sender == _blackWhiteSlider )
   {
      black = [sender doubleLoValue];
      [_blackText setDoubleValue:black];
      white = [sender doubleHiValue];
      [_whiteText setDoubleValue:white];
   }
   else if ( sender == _blackText )
   {
      black = [sender doubleValue];
      white = [_blackWhiteSlider doubleHiValue];
      if ( black > white )
      {
         black = white;
         [sender setDoubleValue:black];
      }
      if ( black < [_blackWhiteSlider minValue] )
         [_blackWhiteSlider setMinValue:black];
      [_blackWhiteSlider setDoubleLoValue:black];
   }
   else if ( sender == _whiteText )
   {
      black = [_blackWhiteSlider doubleLoValue];
      white = [sender doubleValue];
      if ( white < black )
      {
         white = black;
         [sender setDoubleValue:white];
      }
      if ( white > [_blackWhiteSlider maxValue] )
         [_blackWhiteSlider setMaxValue:white];
      [_blackWhiteSlider setDoubleHiValue:white];
   }
   else
      NSAssert(NO,@"Unknown control in blackWhiteChange");

   // Set the levels in the item
   [_item setBlackLevel:black whiteLevel:white gamma:[_gammaText doubleValue]];

   // Refresh the image
   [self updateImage];
}

- (IBAction) gammaChange :(id)sender
{
   double gammaCorrect = 1.0;

   // Reconcile controls
   if ( sender == _gammaSlider )
   {
      gammaCorrect = exp(MM_LN_10*[sender doubleValue]);
      [_gammaText setDoubleValue:gammaCorrect];
   }
   else if ( sender == _gammaText )
   {
      gammaCorrect = [sender doubleValue];
      [_gammaSlider setDoubleValue:log(gammaCorrect)/MM_LN_10];
   }
   else
      NSAssert( NO, @"Unknown gamma control" );

   // Set the levels in the item
   [_item setBlackLevel:[_blackText doubleValue]
             whiteLevel:[_whiteText doubleValue]
                  gamma:gammaCorrect];

   // Refresh the image
   [self updateImage];
}

- (void) displayItem:(id <LynkeosProcessableItem>)item
{
   [self displayItem:item withTransform:[NSAffineTransform transform]];
}

- (void) displayItem:(id <LynkeosProcessableItem>)item
       withTransform:(NSAffineTransform*)transform
{
   NSAffineTransform *newTransform
      = [[[NSAffineTransform alloc] initWithTransform:transform] autorelease];
   if ( item != nil )
   {
      // Get the align transform if any
      id <LynkeosViewAlignResult> res = (id <LynkeosViewAlignResult>)
         [item getProcessingParameterWithRef:LynkeosAlignResultRef
                               forProcessing:LynkeosAlignRef];
      if ( res != nil )
         [newTransform prependTransform:[res alignTransform]];
   }

   // Avoid useless updates
   if ( item == _item &&
       ( item == nil ||
        ( [item getSequenceNumber] == _itemSequenceNumber &&
          [_imageTransform isEqual:newTransform] ) ) )
      return;

   // Save the parameters
   if ( _imageTransform != nil )
      [_imageTransform release];
   _imageTransform = newTransform;
   if ( _imageTransform != nil )
      [_imageTransform retain];

   if ( _item != item )
   {
      if ( _item != nil )
         [_item release];
      _itemSequenceNumber = 0;
      _item = item;
      if ( _item != nil )
         [_item retain];
   }
   if ( _item != nil )
   {
      _itemSequenceNumber = [item getSequenceNumber];
   }

   // Display that new image
   [self updateImage];
}

- (void) updateImage
{
   double vmin, vmax, black, white, gamma;
   BOOL validRange = NO, validLevels = NO;

   // Cleanup before else
   _canvasRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
#if !GNUSTEP
   [_tilesQueue cancelAllOperations];
   [_tilesQueue waitUntilAllOperationsAreFinished];
#warning tiles bookkeeping could be kept (but cleared) if image size does not change
   if (_drawnTile != NULL)
   {
      uint8_t *ary = _drawnTile;
      _drawnTile = NULL;
      free(ary);
   }
#warning layer could be kept if canvas size does not change
   if (_imageLayer != NULL)
   {
      CGLayerRelease(_imageLayer);
      _imageLayer = NULL;
   }
   if (_layerContext != NULL)
   {
      CGContextRelease(_layerContext);
      _layerContext = NULL;
   }
#else
   NSImage *image = nil;

   if ( _imageRep != nil )
   {
      [_imageRep release];
      _imageRep = nil;
   }
#endif

   // Prepare for the new image
   if ( _item != nil )
   {
      const LynkeosIntegerSize s = [_item imageSize];

      if (_itemSize.width != s.width || _itemSize.height != s.height)
         _itemSize = s;
#if !GNUSTEP
      _canvasRect = NSMakeRect(0.0, 0.0, (CGFloat)_itemSize.width, (CGFloat)_itemSize.height);

      if (_itemSize.width != 0 && _itemSize.height != 0)
      {
         // Create the layer which will be filled by the tiles
         _layerContext = CGBitmapContextCreate(NULL, _itemSize.width, _itemSize.height, 8, 0,
                                               CGColorSpaceCreateDeviceRGB(),
                                               (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
         _imageLayer = CGLayerCreateWithContext(_layerContext,
                                                NSMakeSize(_itemSize.width, _itemSize.height), NULL);
         _tilePerLine = (_itemSize.width+K_TILE_SIZE-1)/K_TILE_SIZE;
         _drawnTile = (uint8_t*)calloc(_tilePerLine * (_itemSize.height+K_TILE_SIZE-1)/K_TILE_SIZE,
                                       sizeof(uint8_t));

         // Adjust the view frame if the image size has changed
         CGFloat x, y;

         NSRect itemRect = _canvasRect;

         // Compute the rectangle wich encloses the transformed image (rotated, scaled)
         for ( y = 0.0; y <= itemRect.size.height; y +=  itemRect.size.height )
         {
            for ( x = 0.0; x <= itemRect.size.width; x+= itemRect.size.width )
            {
               NSPoint p = [_imageTransform transformPoint:NSMakePoint(x, y)];

               if ( p.x < _canvasRect.origin.x )
               {
                  _canvasRect.size.width += (_canvasRect.origin.x - p.x);
                  _canvasRect.origin.x = p.x;
               }
               else if ( p.x > _canvasRect.origin.x + _canvasRect.size.width )
                  _canvasRect.size.width = p.x - _canvasRect.origin.x;
               if ( p.y < _canvasRect.origin.y )
               {
                  _canvasRect.size.height += (_canvasRect.origin.y - p.y);
                  _canvasRect.origin.y = p.y;
               }
               else if ( p.y > _canvasRect.origin.y + _canvasRect.size.height )
                  _canvasRect.size.height = p.y - _canvasRect.origin.y;
            }
         }
      }
#else
      // Adjust the view frame if the image size has changed
      if (_itemSize.width != s.width || _itemSize.height != s.height)
         _itemSize = s;

      image = [_item getNSImage];
      if (image != nil)
      {
         NSRect r = {{0.0, 0.0}, {0.0, 0.0}};
         CGFloat x, y;

         r.size = [image size];
         _imageRep = [[image bestRepresentationForRect:r context:nil hints:nil] retain];

         _canvasRect = NSMakeRect(0.0, 0.0, (CGFloat)_itemSize.width, (CGFloat)_itemSize.height);

         // Compute the rectangle wich encloses the transformed image (rotated, scaled)
         for ( y = 0.0; y <= _canvasRect.size.height; y +=  _canvasRect.size.height )
         {
            for ( x = 0.0; x <= _canvasRect.size.width; x+= _canvasRect.size.width )
            {
               NSPoint p = [_imageTransform transformPoint:NSMakePoint(x, y)];

               if ( p.x < _canvasRect.origin.x )
               {
                  _canvasRect.size.width += (_canvasRect.origin.x - p.x);
                  _canvasRect.origin.x = p.x;
               }
               else if ( p.x > _canvasRect.origin.x + _canvasRect.size.width )
                  _canvasRect.size.width = p.x - _canvasRect.origin.x;
               if ( p.y < _canvasRect.origin.y )
               {
                  _canvasRect.size.height += (_canvasRect.origin.y - p.y);
                  _canvasRect.origin.y = p.y;
               }
               else if ( p.y > _canvasRect.origin.y + _canvasRect.size.height )
                  _canvasRect.size.height = p.y - _canvasRect.origin.y;
            }
         }
      }
#endif

      validRange = [_item isProcessed]
                   && [_item getMinLevel:&vmin maxLevel:&vmax];
      validLevels= [_item getBlackLevel:&black whiteLevel:&white gamma:&gamma];
   }

   [_blackText setEnabled:(validRange && validLevels)];
   [_whiteText setEnabled:(validRange && validLevels)];
   [_blackWhiteSlider setEnabled:(validRange && validLevels)];
   [_gammaText setEnabled:(validRange && validLevels)];
   [_gammaSlider setEnabled:(validRange && validLevels)];

   if ( validRange )
   {
      [_blackWhiteSlider setMinValue:fmin(vmin,black)];
      [_blackWhiteSlider setMaxValue:fmax(vmax,white)];
      [_blackWhiteSlider setDoubleLoValue:black];
      [_blackWhiteSlider setDoubleHiValue:white];
      [_gammaSlider setDoubleValue:log(gamma)/MM_LN_10];
   }

   if ( validLevels )
   {
      [_blackText setDoubleValue:black];
      [_whiteText setDoubleValue:white];
      [_gammaText setDoubleValue:gamma];
   }
   else
   {
      [_blackText setStringValue:@""];
      [_whiteText setStringValue:@""];
      [_gammaText setStringValue:@""];
   }

   [self applyZoom:_zoom from:nil withRedraw:YES];
#if GNUSTEP
   [[self window] invalidateCursorRectsForView:self];
#endif
}

- (void) resetBounds
{
   NSRect r = {{0.0, 0.0}, {1.0, 1.0}};
   [self setFrameSize:r.size];
   [self setFrameOrigin:r.origin];
   [self setBoundsSize:r.size];
   [self setBoundsOrigin:r.origin];
}

// Drawing
- (void)drawRect:(NSRect)rect
{
   NSGraphicsContext *g = [NSGraphicsContext currentContext];

#if !GNUSTEP
   if ( _imageLayer != NULL )
#else
   if ( _imageRep != nil )
#endif
   {
      if ([_item supportsTiling])
      {
         // Update needed tiles
         for (CGFloat y = 0; y < _itemSize.height; y += K_TILE_SIZE)
         {
            for (CGFloat x = 0; x < _itemSize.width; x += K_TILE_SIZE)
            {
               // Calculate the corners of all tiles around this point
               CGPoint corner[3][3];
               for (int dy = -1; dy <= 1; dy++)
               {
                  for (int dx = -1; dx <= 1; dx++)
                  {
                     corner[1+dx][1+dy] = [_imageTransform transformPoint: NSMakePoint(x + (CGFloat)dx*K_TILE_SIZE,
                                                                                       y + (CGFloat)dy*K_TILE_SIZE)];
                  }
               }
               // Iterate on these 4 tiles
               for (int dy = -1; dy <= 0; dy++)
               {
                  for (int dx = -1; dx <= 0; dx++)
                  {
                     if ((x + dx*K_TILE_SIZE) >= 0 && (y + dy*K_TILE_SIZE) >= 0 &&
                         ((x + dx*K_TILE_SIZE) + K_TILE_SIZE) < _itemSize.width && ((y+dy*K_TILE_SIZE) + K_TILE_SIZE) < _itemSize.height)
                     {
                        // Tile exists
                        BOOL intersects = NO;
                        for (int cy = 0; !intersects && cy <= 1; cy++)
                        {
                           for (int cx = 0; !intersects && cx <= 1; cx++)
                           {
                              if (pointIsInsideRect(corner[1+dx+cx][1+dy+cy], rect) )
                              {
                                 // Tile has at least one corner inside the rectangle
                                 intersects = YES;
                              }
                           }
                        }
                        if (!intersects)
                        {
                           // Check if a tile edge do intersect
                           intersects = segmentIntersectsRect(corner[1+dx][1+dy], corner[1+dx+1][1+dy], rect)
                                        || segmentIntersectsRect(corner[1+dx+1][1+dy], corner[1+dx+1][1+dy+1], rect)
                                        || segmentIntersectsRect(corner[1+dx+1][1+dy+1], corner[1+dx][1+dy+1], rect)
                                        || segmentIntersectsRect(corner[1+dx][1+dy+1], corner[1+dx][1+dy], rect);
                           
                        }
                        if (intersects)
                        {
                           // The tile with this bottom left corner is inside the draw rect
#if !GNUSTEP
                           [self applyTileAt:CGPointMake(x,y)];
#endif
                        }
                     }
                  }
               }
            }
         }
      }
#if !GNUSTEP
      else if (__sync_bool_compare_and_swap(&_drawnTile[0], Blank, Drawing))
      {
         CGRect r = {{0.0, 0.0}, {(CGFloat)_itemSize.width, (CGFloat)_itemSize.height}};
         CGContextRef ctx = CGLayerGetContext(_imageLayer);
         CGImageRef img = [_item getImageTileInRect:r];
         CGContextDrawImage(ctx, r, img);
         CGImageRelease(img);
         for (int j = 0; j < (_itemSize.height + K_TILE_SIZE - 1)/K_TILE_SIZE; j++)
            for (int i = 0; i < _tilePerLine; i++)
               _drawnTile[i+j*_tilePerLine] = Available;
      }
      else
      {
         while (_drawnTile[0] != Available)
            sched_yield();
      }
#endif
      NSRect r = NSMakeRect(0.0, 0.0, _itemSize.width, _itemSize.height);
      [g saveGraphicsState];
      if ( _imageTransform != nil )
         [_imageTransform concat];
#if !GNUSTEP
      CGContextDrawLayerInRect(g.CGContext, r, _imageLayer);
#else
      [_imageRep drawInRect:r];
#endif
      [g restoreGraphicsState];
   }

   if ( _draggingMode != SelNone || [_selection count] != 0 )
   {
      [g saveGraphicsState];
      [[NSColor orangeColor] set];

      NSEnumerator *selectionList = [_selection objectEnumerator];
      MyImageSelection *sel = nil;
      SelectionIndex_t i = NSNotFound;

      do
      {
         LynkeosIntegerRect r;

         if ( sel == nil )
            r = _inProgressSelection;
         else
            r = sel->_rect;

         if ( r.size.width != 0 && r.size.height != 0 )
         {
            if ( i == _currentSelectionIndex
                 && (_inProgressSelection.size.width != 0
                     || _inProgressSelection.size.height != 0) )
               // Do not display the active selection during modification
               continue;
            if ( sel == nil || i == _currentSelectionIndex )
            {
               [g saveGraphicsState];
               [[NSColor redColor] set];
            }
            [NSBezierPath strokeRect:NSRectFromIntegerRect(r)];
            if ( sel == nil || i == _currentSelectionIndex )
               [g restoreGraphicsState];
         }
         i = (i == NSNotFound ? 0 : i+1);
      } while ( (sel = [selectionList nextObject]) != nil );

      [g restoreGraphicsState];
   }

   // Give an opportunity to add drawings
   [[NSNotificationCenter defaultCenter] postNotificationName: LynkeosImageViewRedrawNotification
                                                       object: self
                                                     userInfo:
                                                        [NSDictionary dictionaryWithObject:[NSValue valueWithRect:rect]
                                                                                    forKey:LynkeosImageViewRedrawRect]];
}

- (NSSize) imageSize { return( _canvasRect.size ); }

- (double) getZoom { return ( _zoom ); }

- (void) setZoom:(double)zoom
{
   [self applyZoom:zoom from:nil withRedraw:YES];
}

- (void) getBlack:(double*)black white:(double*)white gamma:(double*)gamma
{
   *black = [_blackText doubleValue];
   *white = [_whiteText doubleValue];
   *gamma = [_gammaText doubleValue];
}

- (LynkeosIntegerRect) getSelection
{
   if ( _currentSelectionIndex < [_selection count])
      return( [self getSelectionAtIndex:_currentSelectionIndex] );
   else
      return( LynkeosMakeIntegerRect(0, 0, 0, 0) );
}

- (SelectionIndex_t) numberOfSelections
{ return( [_selection count] ); }

- (SelectionIndex_t) activeSelectionIndex
{ return( _currentSelectionIndex); }

- (LynkeosIntegerRect) getSelectionAtIndex:(SelectionIndex_t)index
{
   if ( [_selection count] > 0 )
   {
      MyImageSelection *sel = [_selection objectAtIndex:index];
      NSAssert( sel != nil, @"No selection in non nil selection list" );
      return( sel->_rect );
   }
   else
      return( LynkeosMakeIntegerRect(0, 0, 0, 0) );
}

- (unsigned int) getModifiers { return( _modifiers ); }

- (void) setSelectionMode:(SelectionMode_t)mode
{
   NSAssert( [_selection count] == 0,
             @"setSelectionMode called with active selections" );
   _selectionMode = mode;
}

- (void) freezeSelections:(BOOL)freeze
{
   if ( freeze )
   {
      if ( _freezeCounter == 0 )
      {
         _savedSelectionMode = _selectionMode;
         _selectionMode = NoSelection;
      }
      _freezeCounter++;
   }
   else
   {
      _freezeCounter--;
      if ( _freezeCounter == 0 )
      {
         _selectionMode = _savedSelectionMode;
         _savedSelectionMode = NoSelection;
      }
   }
}

- (void) setSelection :(LynkeosIntegerRect)selection
             resizable:(BOOL)resize
               movable:(BOOL)move
{
   SelectionIndex_t index;

   if ( _selectionMode == SingleSelection )
      index = 0;

   else if ( _currentSelectionIndex < [_selection count] )
      index = _currentSelectionIndex;

   else
      index = [_selection count];

   [self setSelection:selection atIndex:index resizable:resize movable:move];
}

- (void) setSelection :(LynkeosIntegerRect)selection
               atIndex:(SelectionIndex_t)index
             resizable:(BOOL)resize
               movable:(BOOL)move
{
   const SelectionIndex_t nSel = [_selection count];
   LynkeosIntegerRect selRect = selection;
   LynkeosIntegerRect oldRect = {{0,0},{0,0}};
   BOOL oldMovable = NO, oldResizable = NO;
   SelectionIndex_t oldIndex = index;

   if (_selectionMode == NoSelection)
   {
      NSLog(@"Trying to set a selection in NoSelection mode" );
      return;
   }
   NSAssert( index <= nSel,
             @"Trying to add a selection at an invalid index" );

   if ( index < nSel )
      [self getSelectionAtIndex:index
                           rect:&oldRect
                      resizable:&oldResizable movable:&oldMovable];

   // Correct the selection if needed
   if ( _selectionDelegate != nil )
   {
      if ( ![_selectionDelegate validateSelection:&selRect
                                          atIndex:index] )
      {
         selRect.origin.x = 0;
         selRect.origin.y = 0;
         selRect.size.width = 0;
         selRect.size.height = 0;
      }
   }

   // Do not allow selection to be outside the image, even partly
   if ( _canvasRect.size.width == 0 || _canvasRect.size.height == 0 )
   {
      selRect.origin.x = 0;
      selRect.origin.y = 0;
      selRect.size.width = 0;
      selRect.size.height = 0;
   }
   else
   {
      if ( selRect.size.width > _canvasRect.size.width)
         selRect.size.width  = _canvasRect.size.width;
      if ( selRect.origin.x < _canvasRect.origin.x )
         selRect.origin.x = _canvasRect.origin.x;
      else if ( selRect.origin.x + selRect.size.width
                 > _canvasRect.origin.x + _canvasRect.size.width )
         selRect.origin.x = _canvasRect.size.width - selRect.size.width;
      if ( selRect.size.height > _canvasRect.size.height)
         selRect.size.height  = _canvasRect.size.height;
      if ( selRect.origin.y < _canvasRect.origin.y )
         selRect.origin.y = _canvasRect.origin.y;
      else if ( selRect.origin.y + selRect.size.height
                 > _canvasRect.origin.y + _canvasRect.size.height )
         selRect.origin.y = _canvasRect.size.height - selRect.size.height;
   }

   // Check if something has changed
   if ( (index < nSel && index == _currentSelectionIndex
         && oldRect.origin.x == selection.origin.x
         && oldRect.origin.y == selection.origin.y
         && oldRect.size.width == selection.size.width
         && oldRect.size.height == selection.size.height
         && oldMovable == move && oldResizable == resize)
        || (nSel == 0 && (selection.size.width == 0
                          || selection.size.height == 0)) )
   {
      // Nothing to do
      return;
   }
   else if ( _isNotifying  )
   {
      NSLog(@"Attempt to modify the selection during a selection change notification");
      return;
   }

   // If the final selection size is zero, remove it
   if ( selRect.size.width == 0 || selRect.size.height == 0 )
   {
      if (index < [_selection count])
         [self removeSelectionAtIndex:index];
   }
   else
   {
      MyImageSelection *sel = [[[MyImageSelection alloc] initWithRect:selRect
                                                              movable:move
                                                            resizable:resize]
                                                                   autorelease];

      _modifiers = 0;

      if ( index < nSel )
      {
         if ( ![sel isEqual:[_selection objectAtIndex:index]] )
            [_selection replaceObjectAtIndex:index withObject:sel];
      }
      else
         [_selection addObject:sel];
      _currentSelectionIndex = index;


      [self setNeedsDisplay:YES];
      [[self window] invalidateCursorRectsForView:self];

      // If the selection was adjusted, notify it
      if (    index != oldIndex
           || selRect.origin.x    != oldRect.origin.x
           || selRect.origin.y    != oldRect.origin.y
           || selRect.size.width  != oldRect.size.width
           || selRect.size.height != oldRect.size.height )
      {
         _isNotifying = YES;
         [[NSNotificationCenter defaultCenter] postNotificationName:
                              LynkeosImageViewSelectionRectDidChangeNotification
                                                             object: self
                                                           userInfo:
          [NSDictionary dictionaryWithObjectsAndKeys:
              [NSNumber numberWithUnsignedInteger: _currentSelectionIndex],
              LynkeosImageViewSelectionRectIndex,
              nil]];
         _isNotifying = NO;
      }
   }
}

- (void) removeSelectionAtIndex:(SelectionIndex_t)index
{
   if (_selectionMode == NoSelection)
   {
      NSLog(@"Trying to remove a selection in NoSelection mode" );
      return;
   }
   NSAssert( index < [_selection count],
             @"Trying to remove a nonexistent selection" );

   [_selection removeObjectAtIndex:index];
   SelectionIndex_t newCount = [_selection count];
   if ( newCount != 0 && _currentSelectionIndex >= newCount )
      _currentSelectionIndex = newCount - 1;

   else if ( newCount == 0 )
      _currentSelectionIndex = NSNotFound;

   [self setNeedsDisplay:YES];
   [[self window] invalidateCursorRectsForView:self];

   _isNotifying = YES;
   [[NSNotificationCenter defaultCenter] postNotificationName:
                                 LynkeosImageViewSelectionWasDeletedNotification
                                                       object: self
                                                     userInfo:
                                 [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSValue valueWithRange:
                                                         NSMakeRange(index, 1)],
                                    LynkeosImageViewSelectionRange,
                                    nil]];
   _isNotifying = NO;
}

- (void) removeAllSelections
{
   const SelectionIndex_t nSel = [_selection count];

   if (nSel == 0)
      // Nothing to remove
      return;
   if (_selectionMode == NoSelection)
   {
      NSLog(@"Trying to remove selections in NoSelection mode" );
      return;
   }

   if ( nSel != 0 )
   {
      [_selection removeAllObjects];
      _currentSelectionIndex = NSNotFound;

      [self setNeedsDisplay:YES];
      [[self window] invalidateCursorRectsForView:self];

      _isNotifying = YES;
      [[NSNotificationCenter defaultCenter] postNotificationName:
                                 LynkeosImageViewSelectionWasDeletedNotification
                                                       object: self
                                                     userInfo:
                                 [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSValue valueWithRange:
                                                          NSMakeRange(1, nSel)],
                                  LynkeosImageViewSelectionRange,
                                  nil]];
      _isNotifying = NO;
   }
}


- (void) setSelectionDelegate:(id <LynkeosImageViewDelegate>)delegate
{
   _selectionDelegate = delegate;
}
@end
