#import "Maze.h"
#include <stdlib.h>
#include "Random.h"

#import <OpenGLES/ES1/gl.h>

@implementation Maze

#define CELL_SIZE 5.0f
#define HALF_CELL_SIZE (CELL_SIZE * 0.5f)
#define CELL_COLOR_LUMINANCE_BIAS 1.0f

- (id) init {
  self = [super init];
  if (self == nil) {
    return nil;
  }
  
  sizeX = 0;
  sizeY = 0;
  sizeZ = 0;
  cells = NULL;
  
  return self;
}

- (void) dealloc {
  if (cells != NULL) {
    free(cells);
  }
  [super dealloc];
}

- (void) generateNewMazeWithSizeX: (int) x sizeY: (int) y sizeZ: (int) z {
  int * indexArray;
  int cellIndex, cellIndex2;
  int swapIndex;
  int arraySize;
  int tempIndex;
  int wallIndex;
  int numberOfPartitions;
  int oldPartitionID, newPartitionID;
  BOOL * wallToKnockDown1, * wallToKnockDown2;
  
  if (cells != NULL) {
    free(cells);
  }
  
  sizeX = x;
  sizeY = y;
  sizeZ = z;
  arraySize = (sizeX * sizeY * sizeZ);
  cells = (MazeCell *) malloc(sizeof(MazeCell) * arraySize);
  
  indexArray = (int *) malloc(sizeof(int) * arraySize);
  for (cellIndex = 0; cellIndex < arraySize; cellIndex++) {
    swapIndex = (uirand() % arraySize);
    tempIndex = indexArray[cellIndex];
    indexArray[cellIndex] = indexArray[swapIndex];
    indexArray[swapIndex] = tempIndex;
  }
  
  free(indexArray);
  
  for (cellIndex = 0; cellIndex < arraySize; cellIndex++) {
    cells[cellIndex].leftWall = YES;
    cells[cellIndex].rightWall = YES;
    cells[cellIndex].bottomWall = YES;
    cells[cellIndex].topWall = YES;
    cells[cellIndex].backWall = YES;
    cells[cellIndex].frontWall = YES;
    cells[cellIndex].red = ufrand(1.0f);
    cells[cellIndex].green = ufrand(1.0f);
    cells[cellIndex].blue = ufrand(1.0f);
    if ((cells[cellIndex].red + cells[cellIndex].green + cells[cellIndex].blue) < CELL_COLOR_LUMINANCE_BIAS) {
      float maximum;
      
      maximum = cells[cellIndex].red;
      if (cells[cellIndex].green > maximum) maximum = cells[cellIndex].green;
      if (cells[cellIndex].blue > maximum) maximum = cells[cellIndex].blue;
      if (maximum == 0.0f) {
        cells[cellIndex].red = 1.0f;
        cells[cellIndex].green = 1.0f;
        cells[cellIndex].blue = 1.0f;
      } else {
        cells[cellIndex].red /= maximum;
        cells[cellIndex].green /= maximum;
        cells[cellIndex].blue /= maximum;
      }
    }
    cells[cellIndex].partitionID = cellIndex;
  }
  
  numberOfPartitions = arraySize;
  while (numberOfPartitions > 1) {
    cellIndex = (uirand() % arraySize);
    wallIndex = (uirand() % 6);
    if ((cellIndex % sizeX) == 0 && wallIndex == 0) wallIndex = 1;
    if ((cellIndex % sizeX) == (sizeX - 1) && wallIndex == 1) wallIndex = 0;
    if (((cellIndex / sizeX) % sizeY) == 0 && wallIndex == 2) wallIndex = 3;
    if (((cellIndex / sizeX) % sizeY) == (sizeY - 1) && wallIndex == 3) wallIndex = 2;
    if ((((cellIndex / sizeY) / sizeX) % sizeZ) == 0 && wallIndex == 4) wallIndex = 5;
    if ((((cellIndex / sizeY) / sizeX) % sizeZ) == (sizeZ - 1) && wallIndex == 5) wallIndex = 4;
    
    switch (wallIndex) {
      case 0:
        cellIndex2 = (cellIndex - 1);
        wallToKnockDown1 = &cells[cellIndex].leftWall;
        wallToKnockDown2 = &cells[cellIndex2].rightWall;
        break;
      case 1:
        cellIndex2 = (cellIndex + 1);
        wallToKnockDown1 = &cells[cellIndex].rightWall;
        wallToKnockDown2 = &cells[cellIndex2].leftWall;
        break;
      case 2:
        cellIndex2 = (cellIndex - sizeX);
        wallToKnockDown1 = &cells[cellIndex].bottomWall;
        wallToKnockDown2 = &cells[cellIndex2].topWall;
        break;
      case 3:
        cellIndex2 = (cellIndex + sizeX);
        wallToKnockDown1 = &cells[cellIndex].topWall;
        wallToKnockDown2 = &cells[cellIndex2].bottomWall;
        break;
      case 4:
        cellIndex2 = (cellIndex - (sizeX * sizeY));
        wallToKnockDown1 = &cells[cellIndex].backWall;
        wallToKnockDown2 = &cells[cellIndex2].frontWall;
        break;
      case 5:
        cellIndex2 = (cellIndex + (sizeX * sizeY));
        wallToKnockDown1 = &cells[cellIndex].frontWall;
        wallToKnockDown2 = &cells[cellIndex2].backWall;
        break;
      default:
        continue;
    }
    
    if (cells[cellIndex2].partitionID != cells[cellIndex].partitionID) {
      *wallToKnockDown1 = NO;
      *wallToKnockDown2 = NO;
      
      newPartitionID = cells[cellIndex].partitionID;
      oldPartitionID = cells[cellIndex2].partitionID;
      
      for (cellIndex = 0; cellIndex < arraySize; cellIndex++) {
        if (cells[cellIndex].partitionID == oldPartitionID) {
          cells[cellIndex].partitionID = newPartitionID;
        }
      }
      numberOfPartitions--;
    }
  }
  
  switch (uirand() % 6) {
    case 0:
      lastCellIndex = (((uirand() % sizeY) * sizeX) + ((uirand() % sizeZ) * (sizeX * sizeY)));
      firstCellIndex = (((uirand() % sizeY) * sizeX) + ((uirand() % sizeZ) * (sizeX * sizeY)) + (sizeX - 1));
      cells[lastCellIndex].leftWall = NO;
      break;
    case 1:
      lastCellIndex = (((uirand() % sizeY) * sizeX) + ((uirand() % sizeZ) * (sizeX * sizeY)) + (sizeX - 1));
      firstCellIndex = (((uirand() % sizeY) * sizeX) + ((uirand() % sizeZ) * (sizeX * sizeY)));
      cells[lastCellIndex].rightWall = NO;
      break;
    case 2:
      lastCellIndex = (((uirand() % sizeZ) * (sizeX * sizeY)) + (uirand() % sizeX));
      firstCellIndex = (((uirand() % sizeZ) * (sizeX * sizeY)) + ((sizeY - 1) * sizeX) + (uirand() % sizeX));
      cells[lastCellIndex].bottomWall = NO;
      break;
    case 3:
      lastCellIndex = (((uirand() % sizeZ) * (sizeX * sizeY)) + ((sizeY - 1) * sizeX) + (uirand() % sizeX));
      firstCellIndex = (((uirand() % sizeZ) * (sizeX * sizeY)) + (uirand() % sizeX));
      cells[lastCellIndex].topWall = NO;
      break;
    case 4:
      lastCellIndex = (((uirand() % sizeY) * sizeX) + (uirand() % sizeX));
      firstCellIndex = ((((sizeZ - 1) * (sizeX * sizeY)) + (uirand() % sizeY) * sizeX) + (uirand() % sizeX));
      cells[lastCellIndex].backWall = NO;
      break;
    case 5:
      lastCellIndex = ((((sizeZ - 1) * (sizeX * sizeY)) + (uirand() % sizeY) * sizeX) + (uirand() % sizeX));
      firstCellIndex = (((uirand() % sizeY) * sizeX) + (uirand() % sizeX));
      cells[lastCellIndex].frontWall = NO;
      break;
  }
}

- (int) cellIndexForGridPositionX: (int) x Y: (int) y Z: (int) z {
  return ((z * sizeX * sizeY) + (y * sizeX) + x);
}

- (IntVector) gridPositionForCellIndex: (int) cellIndex {
  IntVector position;
  
  position.x = (cellIndex % sizeX);
  position.y = ((cellIndex / sizeX) % sizeY);
  position.z = (((cellIndex / sizeY) / sizeX) % sizeZ);
  
  return position;
}

- (Vector) spatialPositionForCellIndex: (int) cellIndex {
  Vector position;
  
  position.x = ((CELL_SIZE * (cellIndex % sizeX)) + HALF_CELL_SIZE);
  position.y = ((CELL_SIZE * ((cellIndex / sizeX) % sizeY)) + HALF_CELL_SIZE);
  position.z = ((CELL_SIZE * (((cellIndex / sizeY) / sizeX) % sizeZ)) + HALF_CELL_SIZE);
  
  return position;
}

- (int) firstCellIndex {
  return firstCellIndex;
}

- (int) lastCellIndex {
  return lastCellIndex;
}

- (MazeCell *) cellAtIndex: (int) cellIndex {
  return &cells[cellIndex];
}

- (void) draw {
  int cellIndexX, cellIndexY, cellIndexZ, cellIndex;
  Vector position;
  GLfloat vertices[12];
  GLfloat colors[16];
  
  glEnable(GL_POLYGON_OFFSET_FILL);
  glPolygonOffset(1.0f, 1.0f);
  
	glVertexPointer(3, GL_FLOAT, 0, vertices);
	glEnableClientState(GL_VERTEX_ARRAY);
	glColorPointer(4, GL_FLOAT, 0, colors);
	glEnableClientState(GL_COLOR_ARRAY);
	
  for (cellIndexZ = 0; cellIndexZ < sizeZ; cellIndexZ++) {
    for (cellIndexY = 0; cellIndexY < sizeY; cellIndexY++) {
      for (cellIndexX = 0; cellIndexX < sizeX; cellIndexX++) {
        cellIndex = ((cellIndexZ * sizeX * sizeY) + (cellIndexY * sizeX) + cellIndexX);
        
        position.x = ((CELL_SIZE * (cellIndex % sizeX)) + HALF_CELL_SIZE);
        position.y = ((CELL_SIZE * ((cellIndex / sizeX) % sizeY)) + HALF_CELL_SIZE);
        position.z = ((CELL_SIZE * (((cellIndex / sizeY) / sizeX) % sizeZ)) + HALF_CELL_SIZE);
        
        colors[0] = colors[4] = colors[8] = colors[12] = cells[cellIndex].red;
        colors[1] = colors[5] = colors[9] = colors[13] = cells[cellIndex].green;
        colors[2] = colors[6] = colors[10] = colors[14] = cells[cellIndex].blue;
        colors[3] = colors[7] = colors[11] = colors[15] = 1.0f;
        
        if (cells[cellIndex].leftWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x - HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
        if (cells[cellIndex].rightWall) {
          vertices[0] =  position.x + HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
        if (cells[cellIndex].bottomWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y - HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
        if (cells[cellIndex].topWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y + HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
        if (cells[cellIndex].backWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z - HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
        if (cells[cellIndex].frontWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z + HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        }
      }
    }
  }
  
  glPolygonOffset(0.0f, 0.0f);
  glDisable(GL_POLYGON_OFFSET_FILL);
  
  glLineWidth(5.0);
  
	colors[0] = colors[4] = colors[8] = colors[12] = 0.0f;
	colors[1] = colors[5] = colors[9] = colors[13] = 0.0f;
	colors[2] = colors[6] = colors[10] = colors[14] = 0.0f;
	colors[3] = colors[7] = colors[11] = colors[15] = 1.0f;
	
  for (cellIndexZ = 0; cellIndexZ < sizeZ; cellIndexZ++) {
    for (cellIndexY = 0; cellIndexY < sizeY; cellIndexY++) {
      for (cellIndexX = 0; cellIndexX < sizeX; cellIndexX++) {
        cellIndex = ((cellIndexZ * sizeX * sizeY) + (cellIndexY * sizeX) + cellIndexX);
        
        position.x = ((CELL_SIZE * (cellIndex % sizeX)) + HALF_CELL_SIZE);
        position.y = ((CELL_SIZE * ((cellIndex / sizeX) % sizeY)) + HALF_CELL_SIZE);
        position.z = ((CELL_SIZE * (((cellIndex / sizeY) / sizeX) % sizeZ)) + HALF_CELL_SIZE);
        
        if (cells[cellIndex].leftWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x - HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
        if (cells[cellIndex].rightWall) {
          vertices[0] =  position.x + HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
        if (cells[cellIndex].bottomWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y - HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
        if (cells[cellIndex].topWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y + HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
        if (cells[cellIndex].backWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z - HALF_CELL_SIZE;
          vertices[3] =  position.x + HALF_CELL_SIZE;
          vertices[4] =  position.y - HALF_CELL_SIZE;
          vertices[5] =  position.z - HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z - HALF_CELL_SIZE;
          vertices[9] =  position.x - HALF_CELL_SIZE;
          vertices[10] = position.y + HALF_CELL_SIZE;
          vertices[11] = position.z - HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
        if (cells[cellIndex].frontWall) {
          vertices[0] =  position.x - HALF_CELL_SIZE;
          vertices[1] =  position.y - HALF_CELL_SIZE;
          vertices[2] =  position.z + HALF_CELL_SIZE;
          vertices[3] =  position.x - HALF_CELL_SIZE;
          vertices[4] =  position.y + HALF_CELL_SIZE;
          vertices[5] =  position.z + HALF_CELL_SIZE;
          vertices[6] =  position.x + HALF_CELL_SIZE;
          vertices[7] =  position.y + HALF_CELL_SIZE;
          vertices[8] =  position.z + HALF_CELL_SIZE;
          vertices[9] =  position.x + HALF_CELL_SIZE;
          vertices[10] = position.y - HALF_CELL_SIZE;
          vertices[11] = position.z + HALF_CELL_SIZE;
          glDrawArrays(GL_LINE_LOOP, 0, 4);
        }
      }
    }
  }
}

@end
