/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "got/game/shot_movement.h"
#include "got/events.h"
#include "got/game/move.h"
#include "got/game/move_patterns.h"
#include "got/game/object.h"
#include "got/vars.h"

namespace Got {

int shotMovementNone(Actor *actor);
int shotMovementOne(Actor *actor);
int shotMovementTwo(Actor *actor);
int shotMovementThree(Actor *actor);
int shotMovementFour(Actor *actor);
int shotMovementFive(Actor *actor);
int shotMovementSix(Actor *actor);
int shotMovementSeven(Actor *actor);
int shotMovementEight(Actor *actor);
int shotMovementNine(Actor *actor);
int shotMovementTen(Actor *actor);
int shotMovementEleven(Actor *actor);
int shotMovementTwelve(Actor *actor);
int shotMovementThirteen(Actor *actor);

int (*const shotMovementFunc[])(Actor *actor) = {
	shotMovementNone,
	shotMovementOne,
	shotMovementTwo,
	shotMovementThree,
	shotMovementFour,
	shotMovementFive,
	shotMovementSix,
	shotMovementSeven,
	shotMovementEight,
	shotMovementNine,
	shotMovementTen,
	shotMovementEleven,
	shotMovementTwelve,
	shotMovementThirteen
};

void next_shot_frame(Actor *actor) {
	if (actor->_directions == 4 && actor->_framesPerDirection == 1) {
		actor->_nextFrame = actor->_lastDir;
		actor->_dir = 0;
	} else {
		const int fcount = actor->_frameCount - 1;

		if (fcount <= 0) {
			actor->_nextFrame++;
			if (actor->_nextFrame > 3)
				actor->_nextFrame = 0;

			actor->_frameCount = actor->_frameSpeed;
		} else
			actor->_frameCount = fcount;
	}
}

// Boss - snake
int shotMovementNone(Actor *actor) {
	actor->_temp3--;
	if (!actor->_temp3) {
		actorDestroyed(actor);
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
	}

	next_shot_frame(actor);
	if (actor->_directions == 1)
		return 0;

	return actor->_lastDir;
}

int shotMovementOne(Actor *actor) {
	int x1 = 0, y1 = 0;

	const int d = actor->_lastDir;

	switch (d) {
	case 0:
		x1 = actor->_x;
		y1 = actor->_y - 2;
		break;
		
	case 1:
		x1 = actor->_x;
		y1 = actor->_y + 2;
		break;
		
	case 2:
		x1 = actor->_x - 2;
		y1 = actor->_y;
		break;
		
	case 3:
		x1 = actor->_x + 2;
		y1 = actor->_y;
		break;
		
	default:
		break;
	}

	if (!checkMove3(x1, y1, actor)) {
		actorDestroyed(actor);
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
	} else {
		next_shot_frame(actor);
		actor->_lastDir = d;
	}

	if (actor->_directions == 1)
		return 0;
	if (actor->_directions == 4 && actor->_framesPerDirection == 1)
		return 0;

	return d;
}

int shotMovementTwo(Actor *actor) {
	int x1 = 0, y1 = 0;

	const int d = actor->_lastDir;

	switch (d) {
	case 0:
		x1 = actor->_x;
		y1 = actor->_y - 2;
		break;
		
	case 1:
		x1 = actor->_x;
		y1 = actor->_y + 2;
		break;
		
	case 2:
		x1 = actor->_x - 2;
		y1 = actor->_y;
		break;
		
	case 3:
		x1 = actor->_x + 2;
		y1 = actor->_y;
		break;
		
	default:
		break;
	}
	if (!checkMove4(x1, y1, actor)) {
		actorDestroyed(actor);
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
	} else {
		next_shot_frame(actor);
		actor->_lastDir = d;
	}
	if (actor->_directions == 1)
		return 0;
	if (actor->_directions == 4 && actor->_framesPerDirection == 1)
		return 0;
	return d;
}

// Serpent fire
int shotMovementThree(Actor *actor) {
	const int d = actor->_lastDir;

	actor->_x -= 2;
	actor->_temp3--;
	actor->_temp4--;

	if (overlap(_G(thorX1) - 1, _G(thorY1) - 1, _G(thorX2) + 1, _G(thorY2) + 1,
				actor->_x, actor->_y, actor->_x + 15, actor->_y + 15)) {
		actor->_moveType = 0;
		actor->_speed = 6;
		thorDamaged(actor);
		actor->_x += 2;
	}
	if (!actor->_temp4) {
		actor->_temp4 = actor->_temp5;
		actor->_speed++;
		if (actor->_speed > 6)
			actor->_moveType = 0;
	}
	if (!actor->_temp3) {
		actorDestroyed(actor);
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
	} else {
		next_shot_frame(actor);
		actor->_lastDir = d;
	}
	if (actor->_directions == 1)
		return 0;
	return d;
}

// Wraith balls
int shotMovementFour(Actor *actor) {
	if (actor->_temp1) {
		actor->_temp1--;
		if (!actor->_temp1) {
			actorDestroyed(actor);
			_G(appleDropCounter++);
			if (_G(appleDropCounter) == 4) {
				if (dropObject(actor, 5))
					_G(appleDropCounter) = 0;
				else
					_G(appleDropCounter) = 3;
			} else
				dropObject(actor, 3);
			return 0;
		}
	}
	if (overlap(_G(thor)->_x - 1, _G(thor)->_y - 1, _G(thorX2) + 1, _G(thorY2) + 1,
				actor->_x, actor->_y, actor->_x + 15, actor->_y + 15)) {
		thorDamaged(actor);
		actorDestroyed(actor);
		return 0;
	}

	int x1 = actor->_x;
	int y1 = actor->_y;
	int yd = 0;
	int xd = 0;
	int d = actor->_lastDir;

	if ((x1 > (_G(thorX1)) + 1))
		xd = -2;
	else if ((x1 < (_G(thorX1)) - 1))
		xd = 2;

	if (actor->_actorNum == 1) {
		if (y1 < (_G(thorY1) - 6))
			yd = 2;
		else if (y1 > (_G(thorY1) - 6))
			yd = -2;
	} else {
		if (y1 < (_G(thorRealY1) - 1))
			yd = 2;
		else if (y1 > (_G(thorRealY1) + 1))
			yd = -2;
	}

	if (xd && yd) {
		if (xd == -2 && ABS(yd) == 2)
			d = 2;
		else if (xd == 2 && ABS(yd) == 2)
			d = 3;
		
		x1 += xd;
		y1 += yd;
		if (checkMove3(x1, y1, actor)) {
			nextFrame(actor);
			actor->_lastDir = d;
			if (actor->_directions == 1)
				return 0;
			return d;
		}
	} else if (xd == 0 && yd == 2)
		d = 1;
	else if (xd == 0 && yd == -2)
		d = 0;
	else if (xd == 2 && yd == 0)
		d = 3;
	else if (xd == -2 && yd == 0)
		d = 2;

	x1 = actor->_x;
	y1 = actor->_y;
	actor->_toggle ^= 1;

	if (actor->_toggle) {
		if (xd) {
			x1 += xd;
			if (checkMove3(x1, y1, actor)) {
				if (xd > 0)
					d = 3;
				else
					d = 2;
				nextFrame(actor);
				actor->_lastDir = d;
				if (actor->_directions == 1)
					return 0;
				return d;
			}
			x1 = actor->_x;
		}
		if (yd) {
			y1 += yd;
			if (checkMove3(x1, y1, actor)) {
				if (yd > 0)
					d = 1;
				else
					d = 0;
				nextFrame(actor);
				actor->_lastDir = d;
				if (actor->_directions == 1)
					return 0;
				return d;
			}
		}
	} else {
		if (yd) {
			y1 += yd;
			if (checkMove3(x1, y1, actor)) {
				if (yd > 0)
					d = 1;
				else
					d = 0;
				nextFrame(actor);
				actor->_lastDir = d;
				if (actor->_directions == 1)
					return 0;
				return d;
			}
			y1 = actor->_y;
		}
		if (xd) {
			x1 += xd;
			if (checkMove3(x1, y1, actor)) {
				if (xd > 0)
					d = 3;
				else
					d = 2;
				nextFrame(actor);
				actor->_lastDir = d;
				if (actor->_directions == 1)
					return 0;
				return d;
			}
		}
	}
	checkMove3(actor->_x, actor->_y, actor);
	nextFrame(actor);
	actor->_lastDir = d;
	if (actor->_directions == 1)
		return 0;
	return d;
}

// No move, frame cycle
int shotMovementFive(Actor *actor) {
	next_shot_frame(actor);
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Wraith spots
int shotMovementSix(Actor *actor) {
	actor->_temp1--;
	if (!actor->_temp1) {
		actorDestroyed(actor);
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
	} else
		next_shot_frame(actor);
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

#define YV actor->_i1
#define YC actor->_i2
#define BC actor->_i3
#define YD actor->_i4
#define XD actor->_i5
#define XC actor->_i6
#define IV 100
#define IC 50

// Skull drop
int shotMovementSeven(Actor *actor) {
	if (actor->_temp3) {
		actor->_temp3--;
		goto done;
	}
	if (overlap(actor->_x, actor->_y, actor->_x + actor->_sizeX, actor->_y + actor->_sizeY,
				_G(thor)->_x, _G(thor)->_y + 4, _G(thor)->_x + 15, _G(thor)->_y + 15))
		thorDamaged(actor);

	actor->_temp2++;
	if (actor->_temp2 > 2) {
		if (actor->_temp4)
			actor->_temp4--;
		actor->_temp2 = 0;
	}
	actor->_temp3 = actor->_temp4;

	actor->_y += 2;
	if (actor->_y > 160 - 36) {
		actor->_x += (4 - g_events->getRandomNumber(8));
		actor->_moveType = 8;
		YV = IV;
		YC = 0;
		BC = IC;
		YD = 0;
		XC = 3;
		if (actor->_x < 150)
			XD = 1;
		else
			XD = 0;
	}

done:
	next_shot_frame(actor);
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Skull bounce
int shotMovementEight(Actor *actor) {
	int x = actor->_x;
	int y = actor->_y;

	XC--;
	if (!XC) {
		XC = 3;
		if (XD)
			x += 2;
		else
			x -= 2;
	}
	YC += YV;
	if (YC > (IV - 1)) {
		if (!YD) {
			YV -= 8;
			YC -= IV;
			y -= 2;
		} else {
			YV += 8;
			YC -= IV;
			y += 2;
		}
	}
	if (YV < 0) {
		YV = 0;
		BC = 1;
	}
	if (YV > IV) {
		YV = IV;
		BC = 1;
	}
	BC--;
	if (!BC) {
		BC = IC;
		if (YD)
			YV = IV;
		YD ^= 1;
	}
	if (y > 164)
		y = 164;
	//   8       311
	if (x < 1 || x > (319 - actor->_sizeX)) {
		if (!actor->_dead)
			if (_G(actor[actor->_creator])._currNumShots)
				_G(actor[actor->_creator])._currNumShots--;
		actorDestroyed(actor);
	}
	if (overlap(actor->_x, actor->_y, actor->_x + actor->_sizeX, actor->_y + actor->_sizeY,
				_G(thor)->_x, _G(thor)->_y + 4, _G(thor)->_x + 15, _G(thor)->_y + 15))
		thorDamaged(actor);
	actor->_x = x;
	actor->_y = y;

	//done:
	next_shot_frame(actor);
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Skull explode
int shotMovementNine(Actor *actor) {
	actor->_nextFrame++;
	if (actor->_nextFrame == 3) {
		_G(actor[actor->_creator])._currNumShots--;
		actorDestroyed(actor);
		return 0;
	}
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Skull - stalagtites
int shotMovementTen(Actor *actor) {
	bool check = false;
	actor->_y += 2;

	if (overlap(actor->_x, actor->_y, actor->_x + actor->_sizeX, actor->_y + actor->_sizeY,
				_G(thor)->_x, _G(thor)->_y + 4, _G(thor)->_x + 15, _G(thor)->_y + 15)) {
		thorDamaged(actor);
		check = true;
	}
	if (actor->_y > 160 || check) {
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
		actorDestroyed(actor);
	}

	return 0;
}

#undef YD
#undef XD
#define YA actor->_i1
#define XA actor->_i2
#define YD actor->_i3
#define XD actor->_i4
#define DIR actor->_i5
#define CNT actor->_i6

void calc_angle(const int x1, const int y1, const int x2, const int y2, Actor *actor) {
	if (x1 < x2) {
		XA = -2;
		XD = x2 - x1;
	} else if (x1 > x2) {
		XA = 2;
		XD = x1 - x2;
	} else {
		XA = 0;
		XD = 0;
	}

	if (y1 < y2) {
		YA = -2;
		YD = y2 - y1;
	} else if (y1 > y2) {
		YA = 2;
		YD = y1 - y2;
	} else {
		YA = 0;
		YD = 0;
	}

	if (YD >= XD)
		DIR = 1;
	else
		DIR = 0;
	CNT = 0;
}

// Angle throw
int shotMovementEleven(Actor *actor) {
	int x1 = actor->_x;
	int y1 = actor->_y;

	if (!actor->_temp1) {
		calc_angle(_G(thorX1), _G(thorRealY1), x1, y1, actor);
		actor->_temp1 = 1;
	}

	if (DIR) {
		y1 += YA;
		CNT += XD;
		if (CNT >= YD) {
			x1 += XA;
			CNT -= YD;
		}
	} else {
		x1 += XA;
		CNT += YD;
		if (CNT >= XD) {
			y1 += YA;
			CNT -= XD;
		}
	}

	if (!checkMove3(x1, y1, actor)) {
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
		actorDestroyed(actor);
	} else
		nextFrame(actor);

	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Angle throw Loki
int shotMovementTwelve(Actor *actor) {
	int x1 = actor->_x;
	int y1 = actor->_y;

	if (!actor->_temp5) {
		calc_angle(_G(thorX1), _G(thorRealY1), x1, y1, actor);
		actor->_temp5 = 1;
	}

	if (DIR) {
		y1 += YA;
		CNT += XD;
		if (CNT >= YD) {
			x1 += XA;
			CNT -= YD;
		}
	} else {
		x1 += XA;
		CNT += YD;
		if (CNT >= XD) {
			y1 += YA;
			CNT -= XD;
		}
	}

	if (x1 < 16 || x1 > 287 || y1 < 16 || y1 > 159) {
		calc_angle(g_events->getRandomNumber(319),
				   g_events->getRandomNumber(191), x1, y1, actor);
		actor->_moveType = 13;
		actor->_temp4 = 240;
		actor->_nextFrame = 2;
	} else {
		if (overlap(x1 + 2, y1 + 2, x1 + 14, y1 + 14, _G(thorX1), _G(thorY1), _G(thorX2), _G(thorY2))) {
			thorDamaged(actor);
		}
		actor->_x = x1;
		actor->_y = y1;
	}

	const int fcount = actor->_frameCount - 1;
	if (fcount <= 0) {
		actor->_nextFrame++;
		if (actor->_nextFrame > 1)
			actor->_nextFrame = 0;
		actor->_frameCount = actor->_frameSpeed;
	} else
		actor->_frameCount = fcount;
	
	if (actor->_directions == 1)
		return 0;
	return actor->_lastDir;
}

// Angle throw Loki-2
int shotMovementThirteen(Actor *actor) {
	int x1 = actor->_x;
	int y1 = actor->_y;

	if (!actor->_temp5) {
		calc_angle(_G(thorX1), _G(thorRealY1), x1, y1, actor);
		actor->_temp5 = 1;
	}

	if (DIR) {
		y1 += YA;
		CNT += XD;
		if (CNT >= YD) {
			x1 += XA;
			CNT -= YD;
		}
	} else {
		x1 += XA;
		CNT += YD;
		if (CNT >= XD) {
			y1 += YA;
			CNT -= XD;
		}
	}

	if (actor->_temp4)
		actor->_temp4--;

	if (!actor->_temp4) {
		if (_G(actor[actor->_creator])._currNumShots)
			_G(actor[actor->_creator])._currNumShots--;
		actorDestroyed(actor);
		_G(appleDropCounter++);
		if (_G(appleDropCounter) > 4) {
			if (dropObject(actor, 5))
				_G(appleDropCounter) = 0;
			else
				_G(appleDropCounter) = 4;
		} else
			dropObject(actor, 4);
		return 0;
	}

	if (x1 < 16 || x1 > 287 || y1 < 16 || y1 > 159) {
		if (x1 < 16 || x1 > 287)
			XA = 0 - XA;
		else
			YA = 0 - YA;
	} else {
		if (overlap(x1 + 4, y1 + 4, x1 + 12, y1 + 12, _G(thorX1), _G(thorY1), _G(thorX2), _G(thorY2))) {
			thorDamaged(actor);
		}
		actor->_x = x1;
		actor->_y = y1;
	}

	const int fcount = actor->_frameCount - 1;
	if (fcount <= 0) {
		actor->_nextFrame++;
		if (actor->_nextFrame > 3)
			actor->_nextFrame = 2;
		actor->_frameCount = actor->_frameSpeed;
	} else
		actor->_frameCount = fcount;
	
	if (actor->_directions == 1)
		return 0;

	return actor->_lastDir;
}

} // namespace Got
