/* Gridlock
Copyright (c) 2002-2003 by Brian Nenninger. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#import "Game.h"

static NSArray *gEmptyArray = nil;

static NSArray* emptyArray() {
  if (!gEmptyArray) {
    gEmptyArray = [[NSArray alloc] init];
  }
  return gEmptyArray;
}

@implementation Game

idAccessor(grid, setGrid)


-(Game *)copy {
  Game *newGame = nil;
  if ((newGame = [[[self class] alloc] init])) {
    newGame->grid = [grid copy];
    newGame->playerNumber = playerNumber;
    [newGame setConfigurationInfo:[self configurationInfo]];
    newGame->moveCount = moveCount;
  }
  return newGame;
}

-(void)copyValuesToGame:(Game *)newGame {
  [grid copyValuesToGrid:newGame->grid];
  newGame->playerNumber = playerNumber;
  [newGame setConfigurationInfo:[self configurationInfo]];
  newGame->moveCount = moveCount;
}

-(void)dealloc {
  [grid release];
  [futureGrid release];
  [configurationInfo release];
  [super dealloc];
}

-(void)reset {
  playerNumber = 1;
  moveCount = 0;
}

-(void)createGridFromConfiguration {
  [self setGrid:[DCHypergrid gridWithRows:[[[self configurationInfo] objectForKey:@"rows"] intValue]
                                  columns:[[[self configurationInfo] objectForKey:@"cols"] intValue]]];
  [self setGridValuesFromArray:[[self configurationInfo] objectForKey:@"positions"]];  
}

// number of rows and columns are cached to avoid method calls
-(int)numberOfRows {
  if (_nrows>0) return _nrows;
  return (_nrows=[grid numberOfRows]);
}

-(int)numberOfColumns {
  if (_ncols>0) return _ncols;
  return (_ncols=[grid numberOfColumns]);
}

-(int)moveCount {
  return moveCount;
}

-(int)valueAtPosition:(DCHypergridPosition *)pos {
  return [grid valueAtPosition:pos];
}
-(int)valueAtRow:(int)r column:(int)c {
  return [grid valueAtRow:r column:c];
}

-(void)setValue:(int)value atPosition:(DCHypergridPosition *)pos {
  [grid setValue:value atPosition:pos];
}
-(void)setValue:(int)value atRow:(int)r column:(int)c {
  [grid setValue:value atRow:r column:c];
}

-(BOOL)isPositionValid:(DCHypergridPosition *)pos {
  int row = [pos row];
  int col = [pos column];
  return (row>=0 && col>=0 && row<[self numberOfRows] && col<[self numberOfColumns]);
}
-(BOOL)isValidRow:(int)row column:(int)col {
  return (row>=0 && col>=0 && row<[self numberOfRows] && col<[self numberOfColumns]);
}

-(void)resetFutureGrid {
  if ([self futureGrid]==nil) {
    [self setFutureGrid:[[[self grid] copy] autorelease]];
  }
  else {
    [[self grid] copyValuesToGrid:[self futureGrid]];
  }
}

-(void)updateFromPreparedMove {
  if ([self futureGrid]) {
    [[self futureGrid] copyValuesToGrid:[self grid]];
  }
  else {
    NSEnumerator *cenum;
    DCHypergridPosition *pos;
    cenum = [[self positionsOfAllChangingCells] objectEnumerator];
    while ((pos=[cenum nextObject])) {
      //NSLog(@"updating: %d %d %d", [pos row], [pos column], [self futureValueAtPosition:pos]);
      [self setValue:[self futureValueAtPosition:pos] atPosition:pos];
    }
  }
}

-(int)futureValueAtPosition:(DCHypergridPosition *)pos {
  return [[self futureGrid] valueAtPosition:pos];
}

-(NSArray *)positionsOfAllChangingCells {
  NSMutableArray *array = [NSMutableArray array];
  NSEnumerator *pe = [[self grid] positionEnumerator];
  id pos;
  DCHypergrid *grid1 = [self grid];
  DCHypergrid *grid2 = [self futureGrid];
  while ((pos=[pe nextObject])) {
    if ([grid1 valueAtPosition:pos]!=[grid2 valueAtPosition:pos]) {
      [array addObject:pos];
    }
  }
  return array;
}

-(BOOL)makeMoveSequence:(NSArray *)positions {
  if (![self prepareMoveSequence:positions]) return NO;
  else {
    [self updateFromPreparedMove];
    [self didMakeMove:positions];
    return YES;    
  }
}

-(void)didMakeMove:(NSArray *)positions {
  ++moveCount;
  [self incrementPlayerNumber];
}

-(BOOL)preparePass {
  return [self prepareMoveSequence:emptyArray()];
}

-(BOOL)pass {
  return [self makeMoveSequence:emptyArray()];
}

-(BOOL)isPassValid {
  return [self isCompleteMoveSequenceValid:emptyArray()];
}

-(NSMutableArray *)allValidMoveSequences {
  [NSException raise:@"SubclassMustImplementMethod" format:@"Class %d must implement -allValidMoveSequences", NSStringFromClass([self class])];
  return nil;
}

-(NSArray *)inferredValidMoveSequences {
  NSMutableArray *validSequences = [NSMutableArray array];
  NSMutableArray *partialSequences = [NSMutableArray array];

  // partialSequences is a stack holding in-progress move arrays
  [partialSequences addObject:[NSMutableArray array]];

  while ([partialSequences count]>0) {
    NSMutableArray *sequence = [partialSequences lastObject];
    NSEnumerator *posEnum = [[self grid] positionEnumerator];
    id position;
    [partialSequences removeLastObject];
    // add each cell to the partial sequence
    while((position=[posEnum nextObject])) {
      NSMutableArray *possibleSequence = [NSMutableArray arrayWithArray:sequence];
      [possibleSequence addObject:position];
      // If this forms a complete sequence, add to the return array.
      // If it forms a valid sequence, push it back on the stack.
      if ([self isCompleteMoveSequenceValid:possibleSequence]) {
        [validSequences addObject:possibleSequence];
      }
      if ([self isPartialMoveSequenceValid:possibleSequence]) {
        [partialSequences addObject:possibleSequence];
      }
    }
  }
  return validSequences;
}

-(BOOL)isPartialMoveSequenceValid:(NSArray *)positions {
  return ([positions count]>0 && [[self allValidMoveSequences] arrayWithPrefix_:positions]!=nil);
}

-(BOOL)isCompleteMoveSequenceValid:(NSArray *)positions {
  if (!positions) positions = [NSArray array];
  return [[self allValidMoveSequences] containsObject:positions];
}

-(int)currentPlayerNumber {
  return playerNumber;
}
-(void)setCurrentPlayerNumber:(int)value {
  playerNumber = value;
}

-(void)incrementPlayerNumber {
  [self setCurrentPlayerNumber:[self nextPlayerNumber]];
}

-(int)playerNumberMovingAfterPlayer:(int)pnum {
  return (pnum==[self numberOfPlayers]) ? 1 : pnum+1;
}

-(int)nextPlayerNumber {
  return [self playerNumberMovingAfterPlayer:[self currentPlayerNumber]];
}

-(int)scoreForPlayer:(int)pnum {
  return [grid numberOfCellsWithValue:pnum];
}

-(BOOL)showScores {
  return NO;
}

-(int)winningPlayer {
  int pcount = [self numberOfPlayers];
  int i;
  int highscore = [self scoreForPlayer:1];
  int winner = 1;
  BOOL tie = NO;
  for(i=2; i<=pcount; i++) {
    int score = [self scoreForPlayer:i];
    if (score>highscore) {
      highscore = score;
      winner = i;
      tie = NO;
    }
    else if (score==highscore) tie = YES;
  }
  return (tie) ? 0 : winner;
}

-(NSArray *)positionsOfWinningPieces {
  return nil;
}

-(int)numberOfPlayers {
  return 2;
}

-(void)setGridValuesFromArray:(NSArray *)array {
  NSEnumerator *ae = [array objectEnumerator];
  id pv;
  while ((pv=[ae nextObject])) {
    [[self grid] setValue:[[pv objectAtIndex:2] intValue]
                    atRow:[[pv objectAtIndex:0] intValue]
                   column:[[pv objectAtIndex:1] intValue]];
  }
}

-(void)fillFirstRows:(int)numRows {
  int r, c;
  for(c=0; c<[self numberOfColumns]; c++) {
    for(r=0; r<numRows; r++) {
      // player 1 has bottom row(s), player 2 top
      [self setValue:1 atRow:r column:c];
      [self setValue:2 atRow:[self numberOfRows]-1-r column:c];
    }
  }  
}

-(void)fillRandomEmptyCellsWithValue:(int)value count:(int)count {
  int n=0;
  while (n<count) {
    id pos = [self randomPosition];
    if ([self valueAtPosition:pos]==0) {
      [self setValue:value atPosition:pos];
      n++;
    }
  }  
}

-(DCHypergridPosition *)randomPosition {
  int rnd1 = random();
  int rnd2 = random();
  int r = rnd1 % [self numberOfRows];
  int c = rnd2 % [self numberOfColumns];
  return [DCHypergridPosition positionWithRow:r column:c];
}

-(NSString *)descriptionForMove:(NSArray *)move {
  int len = [move count];
  if (len==0) return @"()";
  else {
    NSMutableString *desc = [NSMutableString string];
    int i;
    for(i=0; i<len; i++) {
      int row = [[move objectAtIndex:i] row];
      int col = [[move objectAtIndex:i] column];
      [desc appendFormat:@"%c%d", 'a'+col, row+1];
      if (i+1<len) [desc appendString:@"-"];
    }
    return desc;
  }
}

-(id)propertyList {
  NSMutableDictionary *dict = [NSMutableDictionary dictionary];
  [dict setObject:[[NSNumber numberWithInt:[self currentPlayerNumber]] stringValue] forKey:@"playerNumber"];
  [dict setObject:[[NSNumber numberWithInt:[self moveCount]]stringValue] forKey:@"moveCount"];
  [dict setObject:[[self grid] propertyList] forKey:@"grid"];
  return dict;
}

-(void)updateFromPropertyList:(id)plist {
  playerNumber = [[plist objectForKey:@"playerNumber"] intValue];
  moveCount = [[plist objectForKey:@"moveCount"] intValue];
  [self setGrid:[[[DCHypergrid alloc] initFromPropertyList:[plist objectForKey:@"grid"]] autorelease]];
}

-(void)setFutureGrid:(DCHypergrid *)value
{
  ASSIGN_ID(futureGrid, value)
}

-(DCHypergrid *)futureGrid
{
  return futureGrid;
}

-(BOOL)prepareMoveSequence:(NSArray *)positions
{
  [self subclassResponsibility:_cmd];
  return NO;
}

-(void)setConfigurationInfo:(NSDictionary *)value
{
  ASSIGN_ID(configurationInfo, value)
}

-(NSDictionary *)configurationInfo
{
  return configurationInfo;
}

-(BOOL)isGameOver
{
  [self subclassResponsibility:_cmd];
  return NO;
}
@end
