/*
 *	This is the full SuperLemming initialization and skill code, including a few projectile subroutines currently written as if only for SuperLem
 *	SuperLem code execution branches based on SuperLem state, including takeoff, flying, and landing routines
 *	There are multiple stages to the landing process, as it includes everything from when the lemming reaches the mouse to when the lemming becomes a walker
 */

/*
 *	MOUSE_X and MOUSE_Y constants assumed to represent mouse coordinates
 *	SAR macro defined for arithmetical shift right operation
 */
#define MOUSE_X 	insert reference for mouse x position
#define MOUSE_Y		insert reference for mouse y position
#define SAR(x)		(( (x) >= 0 || ((x) & 1) == 0 ) ? (x) / 2 : ((x) / 2) - 1)

class Lemming;

struct super_lemming {
	int state;
	int x_pos;					// 0x00
	int y_pos;					// 0x02
	int x_motion_difference;	// 0x04
	int y_motion_difference;	// 0x06
	int x_mouse_difference;		// 0x08
	int y_mouse_difference;		// 0x0A
	int x_direction;			// 0x0C
	int y_direction;			// 0x0E
	int x_corrected;			// 0x10
	int y_corrected;			// 0x12
	int dynamic_counter;		// 0x14
	int speed = 7;				// 0x16
	int collision_test_offset;	// 0x18
};

enum state{
	ZERO,
	TAKEOFF,
	FLYING_HORIZONTAL,
	FLYING_VERTICAL,
	LANDING_1,
	LANDING_2,
	LANDING_3,
	LANDING_4,
	LANDING_5,
	LANDING_6	
};

int precollision_x, precollision_y;

/*
 *	This subroutine is called to change lemming state to superlemming
 */
void Initialize_SuperLem( Lemming* lemming )
{
	super_lemming* super_lem;

	// Superlem struct pointer is stored in lemming x velocity variable
	lemming->dX = (int)super_lem;
	// movw   $0x5,0x1e(%di)
	super_lem->state = TAKEOFF;
	// Lemming coordinates stored for collision testing when lemming leaves the ground and starts flying
	super_lem->x_corrected = lemming->x_pos;
	super_lem->y_corrected = lemming->y_pos;

	// movw   $0x29,-0x62b7		-		Unknown variable altered
	// THIS LINE would set lemming state variable to 0x25 for superlem
	// movb   $0x1,0x71c9		-		Unknown variable altered, this variable seems to oscillate between 3 and 1 through normal gameplay
}

void Super_Lemming( Lemming* lemming )
{
	// SuperLem struct data pointer is stored in lemming x velocity variable
	// This is likely more efficient than passing it in as a separate parameter to this function, so I have retained this behavior
	// A finished implementation might want to store it in a dedicated variable
	super_lemming* super_lem = (super_lemming*)lemming->dX;
	
	// These two instructions alter unknown variables in the lemming data structure, possibly graphics-related
	// mov 0xfff7, si[0x1a]
	// mov 0xfff0, si[0x1c]
	
	switch( super_lem->state ) {
		case ZERO:
			// SuperLem state 0 makes a jump directly to a return
			break;
		case TAKEOFF:
			Take_Off(lemming, super_lem);
			break;
		// Flying in either direction has the same entry point in the code
		case FLYING_HORIZONTAL:
		case FLYING_VERTICAL:
			Flying(lemming, super_lem);
			break;
		// Landing sequence is split into multiple parts
		case LANDING_1:
			Landing_1(lemming, super_lem);
			break;
		case LANDING_2:
			Landing_2(lemming, super_lem);
			break;
		case LANDING_3:
			Landing_3(lemming, super_lem);
			break;
		case LANDING_4:
			Landing_4(lemming, super_lem);
			break;
		case LANDING_5:
			Landing_5(lemming, super_lem);
			break;
		case LANDING_6:
			Landing_6(lemming, super_lem);
			break;
	}

}

void Take_Off( Lemming* lemming, super_lemming* super_lem )
{
	// First checks to see lemming is still standing on the ground
	if( Is_Terrain( lemming->x, lemming->y ) ) {
		if( lemming->counter >= 19 ) {
			// Lemming has finished taking off
			lemming->y_pos -= 7;
			Flying(lemming, super_lem);
		}
		else {
			lemming->counter++;
		}
	}
	else {
		// Terrain was removed from beneath the lemming while taking off, make the lemming a faller
		Change_Lemming_State(0x40);
	}
	
}

void Flying( Lemming* lemming, super_lemming* super_lem )
{
	// Assuming MOUSE_X and MOUSE_Y are global variables here
	int x_mouse_difference = MOUSE_X - lemming->x_pos;
	int y_mouse_difference = MOUSE_Y - lemming->y_pos;
	
	// Store differences and position in superlem data
	super_lem->x_mouse_difference = x_mouse_difference;
	super_lem->y_mouse_difference = y_mouse_difference;
	super_lem->x_pos = lemming->x_pos;
	super_lem->y_pos = lemming->y_pos;
	
	int x_direction, y_direction;
	
	// Set directions based on difference between mouse and lem coordinates
	// Left = -1, Right = 1 	Up = -1, Down = 1
	x_direction = x_mouse_difference < 0 ? -1 : 1;
	y_direction = y_mouse_difference < 0 ? -1 : 1;
	
	x_mouse_difference = Abs(x_mouse_difference);
	y_mouse_difference = Abs(y_mouse_difference);
	
	// First check that lemming is at least 8 pixels away from mouse in either direction
	if( x_mouse_difference >= 8 || y_mouse_difference >= 8 ) {
		super_lem->x_direction = x_direction;
		super_lem->y_direction = y_direction;
		
		// Greates difference dictates secondary direction adjustment counter, "dynamic_counter"
		int greatest_difference = x_mouse_difference > y_mouse_difference ? x_mouse_difference : y_mouse_difference;
		super_lem->dynamic_counter = greatest_difference / 2;
		
		// Find general projectile direction, also used as array index for collision testing
		// This function is also used for other projectiles, should be generalized eventually
		int test_index = Find_Projectile_Direction( super_lem->x_mouse_difference, super_lem->y_mouse_difference );
		super_lem->collision_test_offset = test_index;

		// Same index is used to set lemming counter, determines how lemming moves once he reaches the mouse
		// Also likely used for graphics
		lemming->counter = test_index + 20;
		
		super_lem->x_motion_difference = super_lem->x_pos;
		super_lem->y_motion_difference = super_lem->y_pos;
		
		Move_Projectile(super_lem);

		// SuperLem x and y position were modified, can now subtract from previous coordinates to find differences
		super_lem->x_motion_difference -= super_lem->x_pos;
		super_lem->y_motion_difference -= super_lem->y_pos;

		// Save previous corrected coordinates in variables because they are about to be changed
		// These variables are pointers because collision check subroutine returns collision coordinates in registers
		// This is not utilized in this function but is used elsewhere
		int *prev_x_corrected, *prev_y_corrected;
		*prev_x_corrected = super_lem->x_corrected;
		*prev_y_corrected = super_lem->y_corrected;

		// Get new corrected coordinates in superlem struct to be used for collision testing
		Correct_Coordinates( lemming, super_lem );

		*prev_y_corrected--;

		bool collided = Collision_Check( super_lem, prev_x_corrected, prev_y_corrected, super_lem->x_corrected, super_lem->y_corrected );

		if( collided ) {
			Handle_Collision( lemming, super_lem );
		}
		else {
			lemming->x_pos = super_lem->x_pos;
			lemming->y_pos = super_lem->y_pos;

			// Stores two constants to unknown lemming variables, possibly to do with graphics
			// movw   $0xfff9,0x1a(%si)
			// movw   $0xfff8,0x1c(%si)
		}
	}
	else {
		// Lemming got too close to the mouse
		
		// First check if the lemming had just taken off when he reached the mouse
		if( super_lem->state == TAKEOFF ) {
			lemming->y_pos += 7;
			super_lem->state = LANDING_6;
			lemming->counter = 12;
			
			// Call into SuperLem state 9 (last landing sequence)
			Landing_6(lemming, super_lem);
		}
		else {
			// The lemming was flying when he reached the mouse, prepare for landing
			super_lem->x_mouse_difference = -super_lem->x_motion_difference;
			if( super_lem->x_mouse_difference > 0 ) {
				lemming->direction = 1;
			}
			else {
				lemming->direction = -1;
			}
			
			super_lem->y_motion_difference *= -1;
			super_lem->state = LANDING_1;
			
			// Unsure if all operations before this call are actually necessary
			Landing_1(lemming, super_lem);
		}
	}
}

void Landing_1( Lemming* lemming, super_lemming* super_lem )
{
	int counter = lemming->counter;
	// Save lemming counter for array indexing before it gets altered
	int adjustment_index = counter;
	
	if( counter == 44 ) {
		super_lem->state = LANDING_2;
		lemming->counter = 51;
		lemming->y_pos += 8;
		Landing_2( lemming, super_lem );
	}
	else if( counter > 44 || counter < 28 ) {
		counter--;
		if( counter < 20 ) {
			counter = 51;
		}
	}
	else {
		counter++;
	}
	lemming->counter = counter;

	// Change index some more to index into array
	adjustment_index = (adjustment_index - 20) * 2;

	// Hardcoded array determines lemming coordinate changes by frame of "pulling up" sequence
	// This is why lemming sometimes lands quickly or slowly
	int coordinate_changes[] = {
		8,   0,   8,   2,   7,   3,   7,   4, 
		6,   6,   4,   7,   3,   7,   2,   8, 
		0,   8,  -2,   8,  -3,   7,  -4,   7, 
		-6,  6,  -7,   4,  -7,   3,  -8,   2, 
		-8,  0,  -7,  -2,  -6,  -3,  -5,  -5, 
		-4, -6,  -3,  -6,  -2,  -6,  -1,  -6, 
		0,  -6,   1,  -6,   2,  -6,   3,  -6, 
		4,  -5,   5,  -4,   6,  -3,   7,  -2, 
		3,   2,   1,   1,   1,   0,   0,  -1, 
		-1, -1,  -2,  -3 
	};

	int x_change = coordinate_changes[adjustment_index];
	int y_change = coordinate_changes[adjustment_index + 1];

	super_lem->y_mouse_difference = y_change;
	lemming->x_pos += x_change;
	lemming->y_pos += y_change;

	// At this point the procedure jumps to a portion of the flying code which has been copied below

	// Previous coordiantes are stored as pointers because collision check subroutine returns collision coordinates in registers
	// This is not utilized in this function but is used elsewhere
	int *prev_x_corrected, *prev_y_corrected;
	*prev_x_corrected = super_lem->x_corrected;
	*prev_y_corrected = super_lem->y_corrected;

	Correct_Coordinates( lemming, super_lem );

	*prev_y_corrected--;

	bool collided = Collision_Check( super_lem, prev_x_corrected, prev_y_corrected, super_lem->x_corrected, super_lem->y_corrected );

	if( collided ) {
		Handle_Collision( lemming, super_lem );
	}
	else {
		lemming->x_pos = super_lem->x_pos;
		lemming->y_pos = super_lem->y_pos;
	}
}

void Landing_2( Lemming* lemming, super_lemming* super_lem ) {
	lemming->counter++;
	int counter = lemming->counter;

	int array[] = { 3,  2,  1,  1,  1,  0, 0, -1, -1, -1, -2, -3 };

	if( counter < 64 ) {
		counter = (counter - 52);
		lemming->y_pos -= array[counter];

		// NOTE the following is clearly a bug, causing the superlem to crash while starting the landing sequence if there is terrain to the left
		if( Is_Terrain( lemming->x_pos - 9, lemming->y_pos ) ) {
			Handle_Collision( lemming, super_lem );
		}
	}
	else {
		super_lem->state = LANDING_3;
		lemming->counter = 63;
		Landing_3( lemming, super_lem );
	}
}

void Landing_3( Lemming* lemming, super_lemming* super_lem)
{
	int new_x = lemming->x_pos;
	int new_y = lemming->y_pos;
	
	// Pointers are actually used here because collision coordinates are needed, not precollision coordinates
	int *temp_x, *temp_y;
	*temp_x = new_x;
	*temp_y = new_y;

	new_y += 3;

	if( Collision_Check( super_lem, temp_x, temp_y, new_x, new_y ) ) {
		lemming->x_pos = *temp_x;
		lemming->y_pos = *temp_y;
		super_lem->state = LANDING_4;
		lemming->counter = 79;
		Landing_4( lemming, super_lem );
	}
	else {
		lemming->x_pos = new_x;
		lemming->y_pos = new_y;
		lemming->counter++;
		if( lemming->counter > 79 ) {
			lemming->counter = 64;
		}
	}
}

void Landing_4( Lemming* lemming, super_lemming* super_lem )
{
	lemming->counter++;
	if( lemming->counter >= 84 ) {
		super_lem->state = LANDING_5;
		lemming->counter = 11;
		Landing_5( lemming, super_lem );
	}
}

void Landing_5( Lemming* lemming, super_lemming* super_lem )
{
	lemming->counter++;
	if( lemming->counter >= 20 ) {
		super_lem->state = LANDING_6;
		lemming->counter = 12;
		Landing_6( lemming, super_lem );
	}
}

void Landing_6( Lemming* lemming, super_lemming* super_lem )
{
	lemming->counter--;
	if( lemming->counter < 0 ) {
		super_lem->state = 0;
		// Lemming has finally landed and completed all animations, set lemming to walker
		Change_Lemming_State(0x0);
	}
}


/*
 *	Special collision handling routine just for SuperLem
 */
void Handle_Collision( Lemming* lemming, super_lemming* super_lem )
{
	// Lemming coordinates are set to last coordinates in trajectory before the lemming collided
	lemming->x_pos = precollision_x;
	lemming->y_pos = precollision_y;

	super_lem->state = 0;

	// Direction defaults to right
	lemming->direction = 1;

	int temp_x = precollision_x;
	int temp_y = precollision_y;

	/*
	 *	A subroutine is called at this point that has not been completely investigated yet, though testing has shown it never has an effect on SuperLem behavior
	 */

	if( super_lem->y_mouse_difference < 0 ) {
		// Mouse was above the lemming/lemming was travelling upwards for this frame
		int counter = 9;
		// This loops check if there is terrain within 9 pixels directly below lemming collision
		// If there is, it sets the lemming on the terrain as an "ower" then returns
		while( counter > 0 ) {
			temp_y++;
			if( Is_Terrain(temp_x, temp_y) ) {
				lemming->x_pos = temp_x;
				lemming->y_pos = temp_y;
				super_lem->state = 0;

				// Set lemming state to Ower then leave
				Change_Lemming_State( 0x42 );
				return;
			}
			counter--;
		}
	}

	// No terrain was within 9 pixels below lemming or lemming was going downwards
	lemming->x_pos = temp_x;
	lemming->y_pos = temp_y;
	// If the lemming was going upwards, the new y coordinate will be 9 pixels below the collision spot
	// this effect is most visible when the lemming collides with a wall going horizontally
	// NOTE that if the lemming was going downwards when he collided with the ground, he will be set to a Flinger with coordinates 1 pixel above the ground

	// Prepare to initialize lemming as a flinger in the air
	int dX = super_lem->x_mouse_difference;
	int dY = super_lem->y_mouse_difference;
	if( dX < 0 ) {
		lemming->direction *= -1;
		SAR(dX);
		SAR(dX);
		SAR(dX);
	}
	else {
		dX *= -1;
		SAR(dX);
		SAR(dX);
		SAR(dX);
		dX *= -1;
		// This is the exact order of operations, might be able to be cleaned up
	}
	
	if( dY < 0 ) {
		SAR(dY);
		SAR(dY);
		SAR(dY);
	}
	else {
		dY *= -1;
		SAR(dY);
		SAR(dY);
		SAR(dY);
		dY *= -1;
	}

	Air_Physics_Initialization(lemming, dX, dY);
	Change_Lemming_State(0x41);		// Set lemming state to flinger
}

/**********************************************************************
 *	These methods are all projectile subroutines, included here
 *	specifically as superlem routines, should be generalized to all projectiles later
 **********************************************************************/

int Find_Projectile_Direction( int x_difference, int y_difference )
{	
	// Easier to write 3x SAR operations than write something to imitate actual behavior
	if( x_difference != 0 ) {
		SAR(x_difference);
		SAR(x_difference);
		SAR(x_difference);
		
		// Add 1 to the difference if positive and cap the magnitude to 8
		if( x_difference >= 0 && x_difference < 8 ) {
			x_difference++;
		}
		else if( x_difference < -8 ) {
			x_difference = -8;
		}
	}
	if( y_difference != 0 ) {
		SAR(y_difference);
		SAR(y_difference);
		SAR(y_difference);
		
		// Add 1 to the difference if positive and cap the magnitude to 8
		if( y_difference >= 0 && x_difference < 8 ) {
			y_difference++;
		}
		else if( y_difference < -8 ) {
			y_difference = -8;
		}
	}

	// Build counter to use to build index
	int counter = 0;
	
	if( x_difference < 0 ) {
		x_difference *= -1;
		counter += 2;
	}
	if( y_difference < 0 ) {
		y_difference *= -1;
		counter += 4;
	}
	
	// Begin developing index variable using the counter, as well as index for additional adjustment
	int index = 0;
	int adjust_index;
	
	// NOTE the first and third cases run into the second and fourth cases
	switch ( counter ) {
		case 4:
			index += 16;
		case 2:
			index += 8;
			adjust_index = x_difference;
			break;

		case 6:
			index += 16;
		case 0:
			adjust_index = y_difference;
			break;
	}
	// Index will now have a value depending on quadrant of change in x and y
	// Note that these quadrants are defined by relative position (start at top right and go counterclockwise), not signs of x and y
	// Quadrant 1	== 24
	// Quadrant 2	== 16
	// Quadrant 3	== 8
	// Quadrant 4	== 0
	
	adjust_index *= 8;
	adjust_index += x_difference + y_difference;
	
	// Array of constants to adjust the index based on x and y differences
	char adjust_array[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 4, 2, 1, 1, 0, 0,
				  0, 0, 8, 6, 4, 2, 2, 1, 1, 1, 1, 8, 7, 6, 4, 3,
				  2, 2, 1, 1, 8, 7, 6, 5, 4, 3, 2, 2, 2, 8, 8, 7,
				  6, 5, 4, 3, 2, 2, 8, 8, 7, 6, 6, 5, 4, 3, 2, 8,
				  8, 7, 7, 6, 6, 5, 4, 3, 8, 8, 7, 7, 6, 6, 6, 5,
				  4, 4, 0, 0, 0, 4, 0, 1, 0, 4, 0, 2, 0, 3, 0, 2,
				  0, 3, 0, 3, 0, 2, 0, 3, 0, 2, 0, 4, 0, 1, 0, 4,
				  0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
				  0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
				  0, 7, 0, 0, 0, 7, 0, 2, 0, 7, 0, 3, 0, 7, 0, 5,
				  0, 6, 0, 6, 0, 5, 0, 7, 0, 3, 0, 7, 0, 2, 0, 7,
				  0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
				  0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
				  0, 4, 0, 0, 0, 4, 0, 1, 0, 4, 0, 2, 0, 3, 0, 2,
				  0, 3, 0, 3, 0, 2, 0, 3, 0, 2, 0, 4, 0, 1, 0, 4,
				  0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
				  0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
				  0, 0};
	
	index += adjust_array[adjust_index];
	
	if( index >= 32 ) {
		index = 0;
	}
	
	return index;
}

/*
 *	This function should be modified to use general projectile struct instead
 *	of superlem struct once projectiles are analyzed
 */
void Move_Projectile( super_lemming* super_lem )
{
	int x = super_lem->x_pos;
	int y = super_lem->y_pos;

	int x_difference = super_lem->x_mouse_difference;
	int y_difference = super_lem->y_mouse_difference;

	if( x_difference < 0 ) {
		x_difference *= -1;
	}
	if( y_difference < 0 ) {
		y_difference *= -1;
	}

	// Travel counter used to find when to adjust secondary direction
	int travel_counter = super_lem->dynamic_counter;
	// Speed variable is used to dictate number of steps
	int counter = super_lem->speed;

	// Mimicking structure of code here, could clean up
	if( super_lem->state != 2 ) {
		// SuperLem state is 3, primary direction is vertical
		while( counter >= 0 )
		{
			y += super_lem->y_direction;
			travel_counter -= x_difference;
			if( travel_counter < 0 ) {
				x += super_lem->x_direction;
				travel_counter += y_difference;
			}
			counter--;
		}
	}
	else {
		// SuperLem state is 2, primary direction is horizontal
		while( counter >= 0 ) {
			x += super_lem->x_direction;
			travel_counter -= y_difference;
			if( travel_counter < 0 ) {
				y += super_lem->y_direction;
				travel_counter += x_difference;
			}
			counter--;
		}
	}

	super_lem->dynamic_counter = travel_counter;

	/*
	 *	At this point the function performs a check that a SuperLem always passes,
	 *	so a large part of the function gets skipped
	 */

	// Save new coordinates in superlem data, not yet in lemming data
	// Collision checking has to be done first
	super_lem->x_pos = x;
	super_lem->y_pos = y;
}

/*
 *	This function finds "corrected" superlem coordinates that represent the collision point of the lemming
 *	This point likely corresponds to the lemming's head or body, I have not tested what part of the superlemming is at the lemming's actual coordinates
 */
void Correct_Coordinates( Lemming* lemming, super_lemming* super_lem ) 
{
	int adjustment_index = super_lem->collision_test_offset;

	// First cut index down to 3 LSBs (should only lose 1 bit max)
	adjustment_index = adjustment_index & 7;
	adjustment_index *= 2;

	// Uses hardcoded array to find adjustments
	int adjustment_array[] = {4, 0, 4, 1, 4, 2, 3, 2,
							   3, 3, 2, 3, 2, 4, 1, 4,
							   1, 0, 1, 0, 1, 0, 1, 1,
							   1, 1, 1, 1, 0, 1, 0, 1};

	int adjust_x = adjustment_array[adjustment_index];
	int adjust_y = adjustment_array[adjustment_index + 1];

	// Adjustment values are negated to match lemming's direction
	if( super_lem->collision_test_offset >= 8 ) {
		if( super_lem->collision_test_offset < 16 ) {
			// Lemming is going down and left
			int temp = adjust_x;
			adjust_x = adjust_y;
			adjust_y = temp;
			
			adjust_x *= -1;
		}
		else if( super_lem->collision_test_offset < 24 ) {
			// Lemming is going up and left
			adjust_x *= -1;
			adjust_y *= -1;
		}
		else {
			// Lemming is going up and right
			int temp = adjust_x;
			adjust_x = adjust_y;
			adjust_y = temp;

			adjust_y *= -1;
		}
	}

	// Old corrected coordinates were saved so we can store the new corrected coordinates in superlem struct
	super_lem->x_corrected = super_lem->x_pos + adjust_x;
	super_lem->y_corrected = super_lem->y_pos + adjust_y;

}

bool Collision_Check( super_lemming* super_lem, int* temp_x, int* temp_y, int new_x, int new_y )
{
	// Temp coordinates start as previous corrected coordinates
	bool collided = false;
	while ( !collided ) {
		// First we need the remaining difference in x and y to cover until new position is reached
		int x_difference = new_x - *temp_x;
		int y_difference = new_y - *temp_y;

		if( x_difference == 0 || y_difference == 0 ) {
			break;
		}
		
		// Store latest x and y without a collision
		precollision_x = *temp_x;
		precollision_y = *temp_y;

		if( *temp_x != new_x ) {
			if( *temp_x < new_x ) {
				*temp_x++;
			}
			else {
				*temp_x--;
			}
		}
		if( *temp_y != new_y ) {
			if( *temp_y < new_y ) {
				*temp_y++;
			}
			else {
				*temp_y--;
			}
		}
		// Terrain check is inlined in actual collision check subroutine
		collided = Is_Terrain( *temp_x, *temp_y );
	}

	return collided;
}
