/* -*- linux-c -*-
 * Filename: shape.cpp
 * Revision: 19981004
 */

#include <fstream.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

#include "./include/shape.h"
#include "./include/wireframe.h"
#include "./include/errmsg.h"

#include "./include/config.h"

// Translations to get from the primary orientation to
// a specific orientation
int absolute_trans[24] = {
	0, Y_ROT, 2*Y_ROT, 3*Y_ROT,
	X_ROT, X_ROT + Y_ROT, X_ROT + 2*Y_ROT, X_ROT + 3*Y_ROT,
	2*X_ROT, 2*X_ROT + Y_ROT, 2*X_ROT + 2*Y_ROT, 2*X_ROT + 3*Y_ROT,
	3*X_ROT, 3*X_ROT + Y_ROT, 3*X_ROT + 2*Y_ROT, 3*X_ROT + 3*Y_ROT,
	Z_ROT, Z_ROT + Y_ROT, Z_ROT + 2*Y_ROT, Z_ROT + 3*Y_ROT,
	3*Z_ROT, 3*Z_ROT + Y_ROT, 3*Z_ROT + 2*Y_ROT, 3*Z_ROT + 3*Y_ROT
};

// Translations to get to next orientation
int next_trans[24] = {
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + X_ROT,
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + X_ROT,
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + X_ROT,
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + X_ROT + Z_ROT,
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + 2*Z_ROT,
	Y_ROT, Y_ROT, Y_ROT, Y_ROT + Z_ROT
};

// Constructor
shape::shape ()
{
#ifdef _DEBUG
	fprintf (stderr, "shape::shape() 1\n");
#endif

	original = (ROW *)calloc (1, FRAME_SIZE);
	if (!original) {
		fprintf (stderr, FAIL_MEM_ALLOC);
		return;
	}

	width = height = depth = 0;
	shape_x = shape_y = shape_z = 0;
	orientation = 0;
	used = -1;
}

// Construct setting the shape
shape::shape (struct wire_frame *master)
{
#ifdef _DEBUG
	fprintf (stderr, "shape::shape() 2\n");
#endif

	original = (ROW *)calloc (1, FRAME_SIZE);
	if (!original) {
		fprintf (stderr, FAIL_MEM_ALLOC);
		return;
	}

	set_shape (master);

	// Get shape measurements
	width = get_width ();
	height = get_height ();
	depth = get_depth ();

	// Initialise other member variables
	shape_x = shape_y = shape_z = 0;
	orientation = 0;
	used = -1;
}

// Destructor
shape::~shape ()
{
	if (original) free (original);
}

// Set the shape after construction
void shape::set_shape (struct wire_frame *master)
{
#ifdef _DEBUG
	fprintf (stderr, "shape::set_shape()");
#endif

	// Set the name and data of this shape
	set_name (master->wf_description);
	memcpy (cube_data, master->wf_row, FRAME_SIZE);

	// Ensure location
	tidy_up ();
	
	// Store the tidied shape as the master
	memcpy (original, cube_data, FRAME_SIZE);

	// Clear the temp data
	clear ();
}

// Set the shape back to the original
void shape::reset (void)
{
	memcpy (cube_data, original, FRAME_SIZE);
}

// Shift the shape left in its frame
void shape::shift_left (int times)
{
	int y, z;

	// Make sure we don't accidentally clip the left of our shape
	if (width + shape_x + times > CONT_WIDTH) return;

	for (y=0; y<CONT_HEIGHT; y++) {
		for (z=0; z<CONT_DEPTH; z++) {
			cube_data[y*CONT_DEPTH + z] = cube_data[y*CONT_DEPTH + z] << times;
		}
	}

	shape_x += times;
}

// Shift the shape right in its frame
void shape::shift_right (int times)
{
	int y, z;

	// Don't clip the right of our shape
	if (times > shape_x) return;

	for (y=0; y<CONT_HEIGHT; y++) {
		for (z=0; z<CONT_DEPTH; z++) {
			cube_data[y*CONT_DEPTH + z] = cube_data[y*CONT_DEPTH + z] >> times;
		}
	}

	shape_x -= times;
}

// Shift the shape upwards in its frame
void shape::shift_up (int times)
{
	int y, z, i;

	// Don't clip the top of the shape
	if (height + shape_y + times > CONT_HEIGHT) return;

	// Starting at the front-top, moving down then
	// back, copy the row below to the current row.
	// Clear the bottom row each time
	for (i=0; i<times; i++) {
		for (z=0; z<CONT_DEPTH; z++) {
			for (y=CONT_HEIGHT - 1; y>0; y--) {
				cube_data[y*CONT_DEPTH + z] = cube_data[ (y-1)*CONT_DEPTH + z];
			}
			cube_data[z] = 0;
		}
	}

	shape_y += times;
}

// Shift the shape downwards in its frame
void shape::shift_down (int times)
{
	int y, z, i;

	// Don't clip the bottom of the shape
	if (times > shape_y) return;

	// Starting at the front-bottom, moving up then
	// back, copy the row above to the current row.
	// Clear the top row each time
	for (i=0; i<times; i++) {
		for (z=0; z<CONT_DEPTH; z++) {
			for (y=0; y<CONT_HEIGHT - 1; y++) {
				cube_data[y*CONT_DEPTH + z] = cube_data[ (y+1)*CONT_DEPTH + z];
			}
			cube_data[(CONT_HEIGHT-1)*CONT_DEPTH + z] = 0;
		}
	}

	shape_y -= times;
}

// Shift the shape backwards in the frame
void shape::shift_back (int times)
{
	int y, z, i;

	// Ensure we don't clip the back of the shape
	if (depth + shape_z + times > CONT_DEPTH) return;

	// Starting from the bottom-back moving forwards
	// then up, copy the next row forwards to the
	// current row. Clear the front row each time
	for (i=0; i<times; i++) {
		for (y=0; y<CONT_HEIGHT; y++) {
			for (z=CONT_DEPTH - 1; z>0; z--) {
				cube_data[y*CONT_DEPTH + z] = cube_data[y*CONT_DEPTH + z - 1];
			}
			cube_data[y*CONT_DEPTH] = 0;
		}
	}

	shape_z += times;
}

// Shift the shape forwards in the frame
void shape::shift_forward (int times)
{
	int y, z, i;

	// Ensure the front of the shape isn't clipped
	if (times > shape_z) return;

	// Starting from the bottom-front moving back then
	// up, copy the next row back to the current row.
	// Clear the back row each time.
	for (i=0; i<times; i++) {
		for (y=0; y<CONT_HEIGHT; y++) {
			for (z=0; z<CONT_DEPTH - 1; z++) {
				cube_data[y*CONT_DEPTH + z] = cube_data[y*CONT_DEPTH + z + 1];
			}
			cube_data[ (y+1)*CONT_DEPTH - 1] = 0;
		}
	}

	shape_z -= times;
}


// Rotate around the X axis (TERM: Roll forward)
void shape::rotate_x (int times)
{
	int y, z;
	int i, tmp;

	for (i=0; i<times; i++) {

		// Translate the z-coord to the y-cood
		for (y=0; y<CONT_HEIGHT; y++) {
			for (z=0; z<CONT_DEPTH; z++) {
				temp_cube[ (z+1)*CONT_DEPTH - y - 1] = cube_data[y*CONT_DEPTH + z];
			}
		}

		// Save the rotation
		update ();

		// Alter dimensions accordingly
		height ^= depth;
		depth ^= height;
		height ^= depth;

		// Alter shape location co-ords
		tmp = shape_z;
		shape_z = CONT_DEPTH - shape_y - depth;
		shape_y = tmp;
	}
}

// Rotate around the Y axis (TERM: Spin)
void shape::rotate_y (int times)
{
	int x, y, z;
	int i, tmp;

	for (i=0; i<times; i++) {
		// Clear the temp data since we are ORing with
		// the existing data
		clear ();

		// Translate the z-coord to the x-cood
		for (z=0; z<CONT_DEPTH; z++) {
			for (y=0; y<CONT_HEIGHT; y++) {
				for (x=0; x<CONT_WIDTH; x++) {
					if ( (1 << x) & cube_data[y*CONT_DEPTH + z]) {
						temp_cube[ (y+1)*CONT_DEPTH - x - 1] |=  (1 << z);
					}
				}
			}
		}

		// Refresh the actual cube data
		update ();

		// Swap dimensions
		depth ^= width;
		width ^= depth;
		depth ^= width;

		// Alter shape location co-ords
		tmp = shape_z;
		shape_z = CONT_DEPTH - shape_x - depth;
		shape_x = tmp;
	}
}

// Rotate around the Z axis (TERM: Bank)
void shape::rotate_z (int times)
{
	int x, y, z;
	int i, tmp;

	for (i=0; i<times; i++) {
		// Clear the temp data since we are ORing with
		// the existing data
		clear ();

		// Translate the x-coord to the y-coord
		for (z=0; z<CONT_DEPTH; z++) {
			for (y=0; y<CONT_HEIGHT; y++) {
				for (x=0; x<CONT_WIDTH; x++) {
					if ((1 << x) & cube_data[y*CONT_DEPTH + z]) {
						temp_cube[ (CONT_HEIGHT - 1 -x)*CONT_DEPTH +z] |= (1 << y);
					}
				}
			}
		}
    
		// Refresh the actual cube data
		update ();

		// Alter the dimensions
		height ^= width;
		width ^= height;
		height ^= width;

		// Alter shape location co-ords
		tmp = shape_y;
		shape_y = CONT_HEIGHT - shape_x - height;
		shape_x = tmp;
	}
}

void shape::orientate (int tgt_o)
{
	int xr, yr, zr;

	// Calculate the number of rotations on each axis
	// required to get to the specific orientation
	xr = (absolute_trans[orientation] >> X_BIT) & 3;
	yr = (absolute_trans[orientation] >> Y_BIT) & 3;
	zr = (absolute_trans[orientation] >> Z_BIT) & 3;
  
	// Get to the primary orientation
	reset ();
  
	// Re-initialise the the dimensions
	width = get_width ();
	height = get_height ();
	depth = get_depth ();
  
	// Rotate as necessary
	rotate_x (xr);
	rotate_z (zr);
	rotate_y (yr);
	
	// Put back at the front/bottom/right
	tidy_up ();

	// Store the current orientation
	orientation = tgt_o;
}


void shape::next (void)
{
	int xr, yr, zr;

	// This bit could confuse. Using the position in the
	// array of the current orientation it tells us how
	// to get to the next orientation

	// Calculate the number of rotations on each axis
	// required to get to the specific orientation
	xr = (next_trans[orientation] >> X_BIT) & 3;
	yr = (next_trans[orientation] >> Y_BIT) & 3;
	zr = (next_trans[orientation] >> Z_BIT) & 3;
  
	// Rotate as necessary
	rotate_y (yr);
	rotate_x (xr);
	rotate_z (zr);

	// Put back at the front/bottom/right
	tidy_up ();

	// Set the new orientation
	orientation = ++orientation % 24;
}

// Tidy up on all axis
void shape::tidy_up (void)
{
	tidy_x ();
	tidy_y ();
	tidy_z ();
}

// Move as far to the right as possible
void shape::tidy_x (void)
{
	int x, y, z;

	// Slide right no more than the frame width
	for (x=0; x<CONT_WIDTH; x++) {
		// Scan the right most columns of the frame
		for (y=0; y<CONT_HEIGHT; y++) {
			for (z=0; z<CONT_DEPTH; z++) {
				if (cube_data[y*CONT_DEPTH + z] & 1) return;
			}
		}
		shift_right (1);
	}
}

// Move as far down as possible
void shape::tidy_y (void)
{
	int y, z;

	// Slide down no more than the frame height
	for (y=0; y<CONT_HEIGHT; y++) {
		// Scan the bottom layer of the frame
		for (z=0; z<CONT_DEPTH; z++) {
			if (cube_data[z]) return;
		}
		shift_down (1);
	}
}

// Move as far forward as possible
void shape::tidy_z (void)
{
	int y, z;

	// Slide forward no more than the frame depth
	for (z=0; z<CONT_DEPTH; z++) {
		// Scan the front rows of the frame
		for (y=0; y<CONT_HEIGHT; y++) {
			if (cube_data[y*CONT_DEPTH]) return;
		}
		shift_forward (1);
	}
}

// Measure the width of the shape
int shape::get_width (void)
{
	int x, y, z;
	int gauge;
  
	// Set the gauge to be as big as possible
	gauge = 1 << (CONT_WIDTH - 1);
  
	// Make the gauge smaller until it collides
	// with the shape
	for (x=CONT_WIDTH; x>0; x--) {
		for (y=0; y<CONT_HEIGHT; y++) {
			for (z=0; z<CONT_DEPTH; z++) {
				if (cube_data[y*CONT_DEPTH + z] & gauge) return x - shape_x;
			}
		}
		gauge >>= 1;
	}

	// No shape! Bad, very bad!
	return 0;
}

// Measure the height of the shape
int shape::get_height (void)
{
	int y, z;

	// Scan down from the top until it collides
	// with the shape
	for (y=CONT_HEIGHT; y>0; y--) {
		for (z=0; z<CONT_DEPTH; z++) {
			if (cube_data[(y-1)*CONT_DEPTH + z]) return y - shape_y;
		}
	}

	// No shape! Bad, very bad!
	return 0;
}

// Measure the depth of the shape
int shape::get_depth (void)
{
	int y, z;

	// Scan from the back until it collides
	// with the shape
	for (z=CONT_DEPTH; z>0; z--) {
		for (y=0; y<CONT_DEPTH; y++) {
			if (cube_data[y*CONT_DEPTH + z - 1]) return z - shape_z;
		}
	}

	// No shape! Bad, very bad!
	return 0;
}

// Print the status of the shape
void shape::status (void)
{
	int y, z;

	printf ("Shape: %s\n", frame_name);
	printf ("Orientation %d\n", orientation);
	printf ("X: %d\nZ: %d\n", shape_x, shape_z);
	printf ("Altitude: %d\n", shape_y);
	printf ("Used: %d\n\n    ", used);

	for (y=CONT_HEIGHT; y!=0; y--) {
		for (z=0; z<CONT_DEPTH; z++) {
			printf ("%02d  ", cube_data[ (y-1)*CONT_DEPTH + z]);
		}
		printf ("\n    ");
	}

	printf ("\n");
}

// Set this shape to a src shape
void shape::copy (shape *src)
{
	if (src) {
		shape_x = src->shape_x;
		shape_y = src->shape_y;
		shape_z = src->shape_z;
		width = src->width;
		height = src->height;
		depth = src->depth;
		orientation = src->orientation;
		used = -1;
		memcpy (cube_data, src->cube_data, FRAME_SIZE);
		memcpy (original, src->original, FRAME_SIZE);
		set_name (src->frame_name);
	} else {
		shape_x = shape_y = shape_z = 0;
		width = height = depth = 0;
		orientation = 0;
		used = -1;
		memset (cube_data, 0, FRAME_SIZE);
		memset (original, 0, FRAME_SIZE);
		if (frame_name) {
			free (frame_name);
			frame_name = NULL;
		}
	}
}

// Save the shape
void shape::save (fstream &fs)
{
/*
#ifdef _DEBUG
	cerr << "Used: " << used << "\n";
	cerr << "shape_x: " << shape_x << "\n";
	cerr << "shape_y: " << shape_y << "\n";
	cerr << "shape_z: " << shape_z << "\n";

	cerr << "height: " << height << "\n";
	cerr << "width: " << width << "\n";
	cerr << "depth: " << depth << "\n";
	
	cerr << "orientation: " << orientation << "\n";
#endif // _DEBUG
*/

	fs << used;
	fs << shape_x;
	fs << shape_y;
	fs << shape_z;

	fs << height;
	fs << width;
	fs << depth;
	
	fs << orientation;

	frame::save (fs);
}

// Load the shape
void shape::load (fstream &fs)
{
	fs >> used;
	fs >> shape_x;
	fs >> shape_y;
	fs >> shape_z;

        fs >> height;
	fs >> width;
	fs >> depth;
	
	fs >> orientation;

	frame::load (fs);
}
