/*
  Copyright (C) 2000-2005 SKYRIX Software AG

  This file is part of SOPE.

  SOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  SOPE 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with SOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

#include "WETableCalcMatrix.h"
#include "common.h"

typedef struct {
  NSMutableArray *items;
} MatrixEntry;

typedef struct {
  unsigned x;
  unsigned y;
} MatrixCoord;

typedef struct {
  id *objects;
} MatrixThread;

typedef enum { WERow, WEColumn } WEOrientation;

static NSNull *null = nil;

@implementation WETableCalcMatrixSpan

+ (id)spanWithObject:(id)_obj range:(NSRange *)_range {
  id span;
  span = [[WETableCalcMatrixSpan alloc] initWithObject:_obj range:_range];
  return [span autorelease];
}
- (id)initWithObject:(id)_obj range:(NSRange *)_range {
  self->object = [_obj retain];
  self->range  = *_range;
  return self;
}
- (void)dealloc {
  [self->object release];
  [super dealloc];
}

/* accessors */

- (id)object {
  return self->object;
}
- (NSRange)range {
  return self->range;
}

/* calculations */

- (BOOL)startsAtIndex:(unsigned)_idx {
  return (_idx == self->range.location) ? YES : NO;
}
- (BOOL)occupiesIndex:(unsigned)_idx {
  if (_idx < self->range.location)
    return NO;
  if (_idx >= (self->range.location + self->range.length))
    return NO;
  return YES;
}
- (unsigned)size {
  return self->range.length;
}

/* description */

- (NSString *)description {
  return [NSString stringWithFormat:
                     @"<0x%08X[%@]: object=0x%08X start=%d len=%d>",
                     self, NSStringFromClass([self class]),
                     [self object],
                     self->range.location,
                     self->range.length];
}

@end /* WETableCalcMatrixSpan */

@interface WETableCalcMatrixStripe : NSObject
{
@private
  unsigned           size;
  MatrixThread       *threads;
  short              threadCount;
  WETableCalcMatrix *matrix;     /* non-retained */
  id                 delegate;    /* non-retained */
}

- (unsigned)threadCount;
- (NSArray *)threads;
- (NSArray *)threadSpans;

/* modification */

- (void)addObject:(id)_obj atPositions:(unsigned *)_pos count:(unsigned)_c;

@end

@implementation WETableCalcMatrixStripe

+ (void)initialize {
  if (null == nil) null = [[NSNull null] retain];
}

- (id)initWithSize:(unsigned)_h
  matrix:(WETableCalcMatrix *)_matrix
  delegate:(id)_delegate
{
  self->size   = _h;
  self->matrix = _matrix;

  if ([_delegate respondsToSelector:
                   @selector(tableCalcMatrix:spanForObject:range:)])
    self->delegate = _delegate;
  
  return self;
}
- (void)dealloc {
  if (self->threads) {
    unsigned i;
    
    for (i = 0; i < (unsigned)self->threadCount; i++) {
      if (self->threads[i].objects) {
        unsigned j;

        for (j = 0; j < self->size; j++)
          [self->threads[i].objects[j] release];
        free(self->threads[i].objects);
      }
    }
    free(self->threads);
  }
  [super dealloc];
}

/* accessors */

- (unsigned)threadCount {
  return self->threadCount;
}

- (NSArray *)threadAtIndex:(unsigned)_idx {
  id       *objs;
  unsigned i;
  NSArray  *result;
  
  objs = calloc(self->size, sizeof(id));
  NSAssert(objs, @"could not allocate memory ..");
  NSAssert(self->size > 0, @"invalid size ..");
  
  for (i = 0; i < self->size; i++) {
    objs[i] = self->threads[_idx].objects[i];
    
    if (objs[i] == nil)
      objs[i] = null;
  }
  result = [NSArray arrayWithObjects:objs count:self->size];
  free(objs);
  return result;
}
- (NSArray *)spansAtIndex:(unsigned)_idx {
  id       *spans;
  unsigned i, spanCount;
  NSArray  *result;
  
  spans = calloc(self->size, sizeof(id));
  
  for (i = 0, spanCount = 0; i < self->size; ) {
    WETableCalcMatrixSpan *span;
    id      obj;
    NSRange r;
    
    span = nil;
    obj  = self->threads[_idx].objects[i];

    if ((i + 1) == self->size) {
      /* last entry */
      r.location = i;
      r.length   = 1;
      i++;
    }
    else {
      /* look ahead for similiar entries */
      unsigned j;
      
      r.location = i;
      r.length   = 0;

      for (j = i + 1; j < self->size; j++) {
        id nextObj;
        
        nextObj = self->threads[_idx].objects[j];
        if (nextObj != obj)
          break;
      }
      r.length = (j - i);

      /* continue at end of this object */
      i = j;
    }

    span = nil;
    if (self->delegate) {
      span = [self->delegate tableCalcMatrix:self->matrix
                             spanForObject:obj
                             range:r];
    }
    if (span == nil) {
      span = [[WETableCalcMatrixSpan alloc] initWithObject:obj range:&r];
      AUTORELEASE(span);
    }

    spans[spanCount] = span;
    spanCount++;
  }
  result = [NSArray arrayWithObjects:spans count:spanCount];
  free(spans);
  return result;
}

- (NSArray *)threads {
  if (self->threadCount == 0)
    return nil;
  if (self->threadCount == 1)
    return [NSArray arrayWithObject:[self threadAtIndex:0]];
  
  {
    id       threadArrays[self->threadCount];
    unsigned i;

    for (i = 0; i < (unsigned)self->threadCount; i++)
      threadArrays[i] = [self threadAtIndex:i];
    
    return [NSArray arrayWithObjects:threadArrays count:self->threadCount];
  }
}
- (NSArray *)threadSpans {
  if (self->threadCount == 0)
    return nil;
  else if (self->threadCount == 1)
    return [NSArray arrayWithObject:[self spansAtIndex:0]];
  else {
    NSArray  *threadArrays[self->threadCount];
    unsigned i;
    
    for (i = 0; i < (unsigned)self->threadCount; i++) {
      threadArrays[i] = [self spansAtIndex:i];
    }
    
    return [NSArray arrayWithObjects:threadArrays count:self->threadCount];
  }
}

/* addition */

- (unsigned)threadForPositions:(unsigned *)_pos count:(unsigned)_c {
  unsigned i;
  void *tmp;
  
  if (self->threadCount == 0) {
    self->threads  = calloc(1, sizeof(MatrixThread));
    self->threads[0].objects = calloc(self->size, sizeof(id));
    self->threadCount = 1;
    return 0;
  }
  
  /* check each column */
  for (i = 0; i < (unsigned)self->threadCount; i++) {
    unsigned       j;
    MatrixThread *column;
    BOOL           ok;
    
    column = &(self->threads[i]);
    
    /* check each required position in column */
    for (j = 0, ok = YES; j < _c; j++) {
      unsigned requiredPos;
      
      requiredPos = _pos[j];
      NSAssert(requiredPos < self->size, @"index to high ..");
      
      if (column->objects[requiredPos] != nil) {
        /* position already assigned */
        ok = NO;
        break;
      }
    }
    if (ok) {
      /* all required position available, return column */
      return i;
    }
    /* check next column */
  }

  /* all available threads are full, make new one .. */
  tmp = self->threads;
  self->threads = calloc(self->threadCount + 1, sizeof(MatrixThread));
  memcpy(self->threads, tmp, self->threadCount * sizeof(MatrixThread));
  self->threads[self->threadCount].objects = calloc(self->size, sizeof(id));
  self->threadCount++;
  return (self->threadCount - 1);
}

- (void)addObject:(id)_obj atPositions:(unsigned *)_pos count:(unsigned)_c {
  unsigned thread;
  unsigned i;

  if (_c == 0) return;
  
  /* find row */
  thread = [self threadForPositions:_pos count:_c];
  NSAssert(thread < (unsigned)self->threadCount, @"invalid idx");
  
  /* place object */
  for (i = 0; i < _c; i++) {
    unsigned requiredIdx;
    
    requiredIdx = _pos[i];
    
#if DEBUG
    NSAssert(requiredIdx < self->size, @"index to high ..");
    NSAssert3(self->threads[thread].objects[requiredIdx] == nil,
              @"index %i is already marked (by=0x%08X, my=0x%08X) !",
              requiredIdx, self->threads[thread].objects[requiredIdx], _obj);
#endif
    
    self->threads[thread].objects[requiredIdx] = RETAIN(_obj);
  }
}

@end

@interface WETableCalcMatrixPositionArray : NSObject
{ /* mutable array of matrix coordinates */
@private
  unsigned    count;
  MatrixCoord *positions;
}

- (void)addPosition:(unsigned)_x:(unsigned)_y;
- (void)checkForDuplicates;

/* narrow set to row or column */
- (unsigned *)indicesInColumn:(unsigned)_x count:(unsigned *)_count;
- (unsigned *)indicesInRow:(unsigned)_y    count:(unsigned *)_count;

@end

@implementation WETableCalcMatrixPositionArray

- (void)dealloc {
  if (self->positions) free(self->positions);
  [super dealloc];
}

- (void)checkForDuplicates {
  unsigned j;
  
  for (j = 0; j < self->count; j++) {
    unsigned i;

    for (i = 0; i < j; i++) {
      NSAssert4(!((self->positions[j].x) == self->positions[i].x &&
                 (self->positions[j].y) == self->positions[i].y),
                @"duplicate coordinate at %d and %d: %d/%d !",
                j, i, self->positions[j].x, self->positions[j].y);
    }
  }
}

- (void)addPosition:(unsigned)_x:(unsigned)_y {
  if (self->positions == NULL) {
    self->positions = calloc(1, sizeof(MatrixCoord));
    self->positions[0].x = _x;
    self->positions[0].y = _y;
    self->count = 1;
  }
  else {
    unsigned oldCount = self->count;
    void *tmp;
    
    tmp = self->positions;
    self->positions = calloc(oldCount + 1, sizeof(MatrixCoord));
    memcpy(self->positions, tmp, (oldCount * sizeof(MatrixCoord)));
    
    self->positions[oldCount].x = _x;
    self->positions[oldCount].y = _y;
    self->count++;
  }
}

- (unsigned *)indicesIn:(WEOrientation)o index:(unsigned)_idx
  count:(unsigned *)_count
{
  unsigned i, rowCount;
  unsigned *pos, *p;
  
  /* first count */
  for (i = 0, rowCount = 0; i < self->count; i++) {
    unsigned j;
    
    j = (o == WEColumn) ? self->positions[i].x : self->positions[i].y;
    if (j == _idx)
      rowCount++;
  }
  if (rowCount == 0)
    return NULL;
  
  /* then copy */
  pos = calloc(rowCount, sizeof(unsigned));
  *_count = rowCount;
  
  for (i = 0, p = pos; i < self->count; i++) {
    unsigned j;
    
    j = (o == WEColumn) ? self->positions[i].x : self->positions[i].y;
    
    if (j == _idx) {
      *p = (o == WEColumn)
        ? self->positions[i].y
        : self->positions[i].x;
      p++;
    }
  }
  return pos;
}

- (unsigned *)indicesInRow:(unsigned)_y count:(unsigned *)_count {
  return [self indicesIn:WERow index:_y count:_count];
}
- (unsigned *)indicesInColumn:(unsigned)_x count:(unsigned *)_count {
  return [self indicesIn:WEColumn index:_x count:_count];
}

- (NSString *)description {
  return [NSString stringWithFormat:@"<%08X[%@]: count=%d>",
                     self, NSStringFromClass([self class]),
                     self->count];
}

@end /* WETableCalcMatrixPositionArray */

@implementation WETableCalcMatrix

+ (int)version {
  return 0;
}

static inline MatrixEntry *entryAt(WETableCalcMatrix *self, unsigned x, 
				   unsigned y) {
  return self->matrix +
         (x * self->height * sizeof(MatrixEntry)) +
         (y * sizeof(MatrixEntry));
}

- (id)initWithSize:(unsigned)_width:(unsigned)_height {
  if (_width == 0 || _height == 0) {
    [self logWithFormat:@"ERROR: specified invalid matrix dimensions: %ix%i",
            _width, _height];
    [self release];
    return nil;
  }
  
  NSAssert(_width > 0 && _height > 0, @"invalid args ..");
  self->width  = _width;
  self->height = _height;
  self->matrix = (void *)calloc(_width * _height, sizeof(MatrixEntry));
  memset(self->matrix, 0, _width * _height * sizeof(MatrixEntry));

  self->objToPos = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
                                    NSObjectMapValueCallBacks,
                                    64);
  
  return self;
}
- (void)dealloc {
  [self removeAllObjects];

  if (self->objToPos)
    NSFreeMapTable(self->objToPos);
  
  if (self->matrix)
    free(self->matrix);
  
  [super dealloc];
}

/* accessors */

- (unsigned)width {
  return self->width;
}
- (unsigned)height {
  return self->height;
}

- (void)setDelegate:(id)_delegate {
  self->delegate = _delegate;
  
  self->columnCheck =
    [_delegate respondsToSelector:
                 @selector(tableCalcMatrix:shouldProcessColumn:forObject:)];
  self->rowCheck =
    [_delegate respondsToSelector:
                 @selector(tableCalcMatrix:shouldProcessRow:forObject:)];
}
- (id)delegate {
  return self->delegate;
}

/* clearing the structure */

- (void)removeAllObjects {
  unsigned y, x;
  
  if (self->objToPos) {
    NSResetMapTable(self->objToPos);
    self->objToPos = NULL;
  }
  
  if (self->matrix == NULL)
    return;
  
  for (y = 0; y < self->height; y++) {
    for (x = 0; x < self->width; x++) {
      MatrixEntry *e;
      
      e = entryAt(self, x, y);
      
      if (e->items == nil) 
	continue;
      
      [e->items release]; e->items = nil;
    }
  }
}

/* queries */

- (BOOL)object:(id)_obj possibleInRow:(unsigned)_y {
  /* optimization method, can always return 'YES' */
  if (self->rowCheck) {
    return [self->delegate tableCalcMatrix:self
                           shouldProcessRow:_y
                           forObject:_obj];
  }
  return YES;
}

- (BOOL)object:(id)_obj possibleInColumn:(unsigned)_x {
  /* optimization method, can always return 'YES' */
  if (self->columnCheck) {
    return [self->delegate tableCalcMatrix:self
                           shouldProcessColumn:_x
                           forObject:_obj];
  }
  return YES;
}

- (BOOL)object:(id)_obj matchesCellAt:(unsigned)_x:(unsigned)_y {
  return [self->delegate tableCalcMatrix:self
                         shouldPlaceObject:_obj
                         atPosition:_x:_y];
}

/* adding object to structure */

- (void)addObject:(id)_obj toCellAt:(unsigned)_x:(unsigned)_y {
  WETableCalcMatrixPositionArray *positions;
  MatrixEntry *e;
  
  if ((positions = NSMapGet(self->objToPos, _obj)) == nil) {
    positions = [[WETableCalcMatrixPositionArray alloc] init];
    NSMapInsert(self->objToPos, _obj, positions);
    RELEASE(positions);
  }
  
  [positions checkForDuplicates];
  
  e = entryAt(self, _x, _y);
  
  if (e->items == nil)
    e->items = [[NSMutableArray alloc] init];
  
  [e->items addObject:_obj];

  [positions checkForDuplicates];
  [positions addPosition:_x:_y];
  [positions checkForDuplicates];
}

/* placing objects */

- (void)placeObject:(id)_object {
  unsigned y, x;

  if (NSMapGet(self->objToPos, _object)) {
    //NSLog(@"already placed object %@ !", _object);
    return;
  }
  
  if (self->rowCheck) {
    for (y = 0; y < self->height; y++) {
      if (![self object:_object possibleInRow:y])
        continue;

      for (x = 0; x < self->width; x++) {
        if ([self object:_object matchesCellAt:x:y]) {
          /* add to cell x:y */
          [self addObject:_object toCellAt:x:y];
        }
      }
    }
  }
  else {
    for (x = 0; x < self->width; x++) {
      if (![self object:_object possibleInColumn:x])
        continue;
    
      for (y = 0; y < self->height; y++) {
        if ([self object:_object matchesCellAt:x:y]) {
          /* add to cell x:y */
          [self addObject:_object toCellAt:x:y];
        }
      }
    }
  }
}

- (void)placeObjects:(NSArray *)_objects {
  unsigned oc, i;
  
  if ((oc = [_objects count]) == 0)
    return;
  
  for (i = 0; i < oc; i++)
    [self placeObject:[_objects objectAtIndex:i]];
}

/* formatting */

- (NSArray *)objectsInColumn:(unsigned)_x {
  unsigned     y;
  NSMutableSet *set;
  NSArray      *result;

  set = [[NSMutableSet alloc] init];
  
  for (y = 0; y < self->height; y++) {
    MatrixEntry *e;
    
    e = entryAt(self, _x, y);
    if (e->items)
      [set addObjectsFromArray:e->items];
  }
  result = [set allObjects];
  RELEASE(set);
  return result;
}
- (NSArray *)objectsInRow:(unsigned)_y {
  unsigned     x;
  NSMutableSet *set;
  NSArray      *result;

  set = [[NSMutableSet alloc] init];
  
  for (x = 0; x < self->width; x++) {
    MatrixEntry *e;
    
    e = entryAt(self, x, _y);
    if (e->items)
      [set addObjectsFromArray:e->items];
  }
  result = [set allObjects];
  RELEASE(set);
  return result;
}

- (NSArray *)spansOfColumn:(unsigned)_x {
  WETableCalcMatrixStripe *stripe;
  NSEnumerator  *objects;
  id            object;
  
  stripe = [[WETableCalcMatrixStripe alloc]
                                     initWithSize:self->height
                                     matrix:self
                                     delegate:self->delegate];
  stripe = [stripe autorelease];
  
  objects = [[self objectsInColumn:_x] objectEnumerator];
  while ((object = [objects nextObject]) != nil) {
    WETableCalcMatrixPositionArray *pos;
    unsigned *indices;
    unsigned idxCount;
    
    pos = NSMapGet(self->objToPos, object);
    [pos checkForDuplicates];
    
    indices = [pos indicesInColumn:_x count:&idxCount];
    NSAssert(indices, @"available in column, but no indices ?");

    [stripe addObject:object atPositions:indices count:idxCount];
  }
  
  return [stripe threadSpans];
}
- (NSArray *)spansOfRow:(unsigned)_y {
  WETableCalcMatrixStripe *stripe;
  NSEnumerator  *objects;
  id            object;
  
  stripe = [[WETableCalcMatrixStripe alloc]
                                     initWithSize:self->width
                                     matrix:self
                                     delegate:self->delegate];
  stripe = [stripe autorelease];
  
  objects = [[self objectsInRow:_y] objectEnumerator];
  while ((object = [objects nextObject])) {
    WETableCalcMatrixPositionArray *pos;
    unsigned *indices;
    unsigned idxCount;
    
    pos = NSMapGet(self->objToPos, object);
    [pos checkForDuplicates];
    
    indices = [pos indicesInRow:_y count:&idxCount];
    NSAssert(indices, @"available in column, but no indices ?");
    
    [stripe addObject:object atPositions:indices count:idxCount];
  }
  
  return [stripe threadSpans];
}

- (NSArray *)columnSpans {
  id       objs[self->width];
  unsigned i;
  
  for (i = 0; i < self->width; i++) {
    objs[i] = [self spansOfColumn:i];
    if (objs[i] == nil) objs[i] = [NSArray array];
  }
  return [NSArray arrayWithObjects:objs count:self->width];
}
- (NSArray *)rowSpans {
  id       objs[self->height];
  unsigned i;
  
  for (i = 0; i < self->height; i++) {
    objs[i] = [self spansOfRow:i];
    if (objs[i] == nil) objs[i] = [NSArray array];
  }
  return [NSArray arrayWithObjects:objs count:self->height];
}

/* counting */

- (unsigned)widthOfColumn:(unsigned)_x {
  unsigned y;
  unsigned count;
  
  for (y = 0, count = 0; y < self->height; y++) {
    MatrixEntry *e;

    e = entryAt(self, _x, y);
    if ([e->items count] > count)
      count = [e->items count];
  }
  return count;
}

- (unsigned)countOfColumn:(unsigned)_x {
  return [[self objectsInColumn:_x] count];
}

@end /* WETableCalcMatrix */
