/*
 *
 * levelloadjj2.cpp
 *
 * 28th June 2010: Created levelloadjj2.cpp from parts of levelload.cpp
 * 29th June 2010: Renamed levelloadjj2.cpp to jj2levelload.cpp
 *
 * Part of the OpenJazz project
 *
 *
 * Copyright (c) 2005-2010 Alister Thomson
 *
 * OpenJazz is distributed under the terms of
 * the GNU General Public License, version 2.0
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/*
 * Deals with the loading of JJ2 level data.
 *
 */


#include "jj2event/jj2event.h"
#include "jj2level.h"

#include "game/game.h"
#include "io/file.h"
#include "io/gfx/font.h"
#include "io/gfx/sprite.h"
#include "io/gfx/video.h"
#include "io/sound.h"
#include "menu/menu.h"
#include "player/jj2levelplayer.h"
#include "loop.h"
#include "util.h"

#include <string.h>


#define SKEY 254 /* Sprite colour key */


void JJ2Level::loadSprite (unsigned char* parameters, unsigned char* compressedPixels, Sprite* sprite, Sprite* flippedSprite) {

	unsigned char* pixels;
	int width, height;
	int srcPos, dstPos, rle;
	int x, y;

	// Load dimensions
	width = createShort(parameters);
	height = createShort(parameters + 2);

	if ((width == 0) || (height == 0)) {

		sprite->clearPixels();

		return;

	}


	// Decompress pixels

	pixels = new unsigned char[width * height];
	memset(pixels, 0, width * height);

	srcPos = createInt(parameters + 16);
	dstPos = 0;

	while (dstPos < width * height) {

		rle = compressedPixels[srcPos];
		srcPos++;

		if (rle > 128) {

			rle &= 127;

			if (dstPos + rle < width * height)
				memcpy(pixels + dstPos, compressedPixels + srcPos, rle);

			srcPos += rle;
			dstPos += rle;

		} else if (rle == 128) {

			rle = dstPos % width;

			if (rle) dstPos = dstPos + width - rle;

		} else {

			dstPos += rle;

		}

	}


	// Set sprite data
	sprite->xOffset = createShort(parameters + 8);
	sprite->yOffset = createShort(parameters + 10);
	sprite->setPixels(pixels, width, height, 0);

	// Flip sprite
	for (y = 0; y < height; y++) {

		for (x = 0; x < (width >> 1); x++) {

			rle = pixels[(y * width) + x];
			pixels[(y * width) + x] = pixels[(y * width) + (width - 1) - x];
			pixels[(y * width) + (width - 1) - x] = rle;

		}

	}

	// Set flipped sprite data
	flippedSprite->xOffset = -createShort(parameters + 8) - width;
	flippedSprite->yOffset = createShort(parameters + 10);
	flippedSprite->setPixels(pixels, width, height, 0);

	delete[] pixels;

	return;

}


int JJ2Level::loadSprites () {

	File* file;
	unsigned char* aBuffer;
	unsigned char* bBuffer;
	unsigned char* cBuffer;
	int* setOffsets;
	int aCLength, bCLength, cCLength, dCLength;
	int aLength, bLength, cLength, dLength;
	int setAnims, sprites, animSprites;
	int set, anim, sprite, setSprite;

	// Thanks to Neobeo for working out the .j2a format


	try {

		file = new File(F_ANIMS_J2A, false);

	} catch (int e) {

		return e;

	}

	file->seek(24, true);

	nAnimSets = file->loadInt();

	setOffsets = new int[nAnimSets];

	for (set = 0; set < nAnimSets; set++) setOffsets[set] = file->loadInt();


	// Count number of sprites

	sprites = 0;

	for (set = 0; set < nAnimSets; set++) {

		file->seek(setOffsets[set] + 6, true);

		sprites += file->loadShort();

	}

	spriteSet = new Sprite[sprites];
	flippedSpriteSet = new Sprite[sprites];
	animSets = new Anim *[nAnimSets];
	flippedAnimSets = new Anim *[nAnimSets];


	// Load animations and sprites

	sprites = 0;

	for (set = 0; set < nAnimSets; set++) {

		file->seek(setOffsets[set] + 4, true);

		setAnims = file->loadChar();

		if (setAnims) {

			animSets[set] = new Anim[setAnims];
			flippedAnimSets[set] = new Anim[setAnims];

		} else {

			animSets[set] = NULL;
			flippedAnimSets[set] = NULL;

		}

		file->seek(7, false);

		aCLength = file->loadInt();
		aLength = file->loadInt();
		bCLength = file->loadInt();
		bLength = file->loadInt();
		cCLength = file->loadInt();
		cLength = file->loadInt();
		dCLength = file->loadInt();
		dLength = file->loadInt();

		aBuffer = file->loadLZ(aCLength, aLength);
		bBuffer = file->loadLZ(bCLength, bLength);
		cBuffer = file->loadLZ(cCLength, cLength);

		setSprite = 0;

		for (anim = 0; anim < setAnims; anim++) {

			animSprites = createShort(aBuffer + (anim * 8));

			// Fonts are loaded separately
			if (animSprites == 224) animSprites = 1;

			animSets[set][anim].setData(animSprites, 0, 0, 0, 0, 0, 0);
			flippedAnimSets[set][anim].setData(animSprites, 0, 0, 0, 0, 0, 0);

			for (sprite = 0; sprite < animSprites; sprite++) {

				loadSprite(bBuffer + (setSprite * 24), cBuffer, spriteSet + sprites, flippedSpriteSet + sprites);

				animSets[set][anim].setFrame(sprite, false);
				animSets[set][anim].setFrameData(spriteSet + sprites, 0, 0);
				flippedAnimSets[set][anim].setFrame(sprite, false);
				flippedAnimSets[set][anim].setFrameData(flippedSpriteSet + sprites, 0, 0);

				setSprite++;
				sprites++;

			}

		}

		delete[] cBuffer;
		delete[] bBuffer;
		delete[] aBuffer;

	}


	delete[] setOffsets;

	delete file;


	return E_NONE;

}


int JJ2Level::loadTiles (char* fileName) {

	File* file;
	unsigned char* aBuffer;
	unsigned char* bBuffer;
	unsigned char* dBuffer;
	unsigned char* tileBuffer;
	int aCLength, bCLength, cCLength, dCLength;
	int aLength, bLength, cLength, dLength;
	int count, x, y;
	int maxTiles;
	int tiles;

	// Thanks to Neobeo for working out the most of the .j2t format


	try {

		file = new File(fileName, false);

	} catch (int e) {

		return e;

	}

	// Skip to version indicator
	file->seek(220, true);

	maxTiles = file->loadShort();

	if (maxTiles == 0x201) maxTiles = 4096;
	else maxTiles = 1024;


	// Skip to compressed block lengths
	file->seek(8, false);
	aCLength = file->loadInt();
	aLength = file->loadInt();
	bCLength = file->loadInt();
	bLength = file->loadInt();
	cCLength = file->loadInt();
	cLength = file->loadInt();
	dCLength = file->loadInt();
	dLength = file->loadInt();

	aBuffer = file->loadLZ(aCLength, aLength);
	bBuffer = file->loadLZ(bCLength, bLength);
	file->seek(cCLength, false); // Don't need this block
	dBuffer = file->loadLZ(dCLength, dLength);

	delete file;


	// Load the palette
	for (count = 0; count < 256; count++) {

		palette[count].r = aBuffer[count << 2];
		palette[count].g = aBuffer[(count << 2) + 1];
		palette[count].b = aBuffer[(count << 2) + 2];

	}


	// Load tiles

	tiles = createShort(aBuffer + 1024);
	tileBuffer = new unsigned char[tiles << 10];

	for (count = 0; count < tiles; count++) {

		memcpy(tileBuffer + (count << 10), bBuffer + createInt(aBuffer + 1028 + (maxTiles << 1) + (count << 2)), 1024);

	}

	tileSet = createSurface(tileBuffer, TTOI(1), TTOI(tiles));
	SDL_SetColorKey(tileSet, SDL_SRCCOLORKEY, 0);

	// Flip tiles
	for (count = 0; count < TTOI(tiles); count++) {

		for (x = 0; x < 16; x++) {

			y = tileBuffer[(count * 32) + x];
			tileBuffer[(count * 32) + x] = tileBuffer[(count * 32) + 31 - x];
			tileBuffer[(count * 32) + 31 - x] = y;

		}

	}

	flippedTileSet = createSurface(tileBuffer, TTOI(1), TTOI(tiles));
	SDL_SetColorKey(flippedTileSet, SDL_SRCCOLORKEY, 0);

	delete[] tileBuffer;


	// Load mask

	mask = new char[tiles << 10];

	// Unpack bits
	for (count = 0; count < tiles; count++) {

		for (y = 0; y < 32; y++) {

			for (x = 0; x < 32; x++)
				mask[(count << 10) + (y << 5) + x] = (dBuffer[createInt(aBuffer + 1028 + (maxTiles * 18) + (count << 2)) + (y << 2) + (x >> 3)] >> (x & 7)) & 1;

		}

	}

	flippedMask = new char[tiles << 10];

	// Unpack bits
	for (count = 0; count < tiles; count++) {

		for (y = 0; y < 32; y++) {

			for (x = 0; x < 32; x++)
				flippedMask[(count << 10) + (y << 5) + x] = (dBuffer[createInt(aBuffer + 1028 + (maxTiles * 22) + (count << 2)) + (y << 2) + (x >> 3)] >> (x & 7)) & 1;

		}

	}

	delete[] dBuffer;
	delete[] bBuffer;
	delete[] aBuffer;


	/* Uncomment the code below if you want to see the mask instead of the tile
	graphics during gameplay */

	/*if (SDL_MUSTLOCK(tileSet)) SDL_LockSurface(tileSet);
	if (SDL_MUSTLOCK(flippedTileSet)) SDL_LockSurface(flippedTileSet);

	for (count = 0; count < tiles; count++) {

		for (y = 0; y < 32; y++) {

			for (x = 0; x < 32; x++) {

				if (mask[(count << 10) + (y << 5) + x] == 1)
					((char *)(tileSet->pixels))[(count << 10) + (y << 5) + x] = 43;

				if (flippedMask[(count << 10) + (y << 5) + x] == 1)
					((char *)(flippedTileSet->pixels))[(count << 10) + (y << 5) + x] = 88;

			}

		}

	}

	if (SDL_MUSTLOCK(tileSet)) SDL_UnlockSurface(tileSet);
	if (SDL_MUSTLOCK(flippedTileSet)) SDL_UnlockSurface(flippedTileSet);*/


	return tiles | (maxTiles << 16);

}


void JJ2Level::createEvent (int x, int y, unsigned char* data) {

	unsigned char type;
	int properties;

	type = *data;
	properties = (data[1] >> 4) + (data[2] << 4) + (data[3] << 12);

	if ((type < 33) || ((type >= 206) && (type <= 208)) || (type == 230) || (type == 240) || (type == 245)) {

		mods[y][x].type = type;
		mods[y][x].properties = properties;

		return;

	}

	mods[y][x].type = 0;

	if (type < 41) {

		events = new AmmoJJ2Event(events, x, y, type);

	} else if (type < 44) {

		events = new OtherJJ2Event(events, x, y, type, properties);

	} else if (type < 46) {

		events = new CoinGemJJ2Event(events, x, y, type);

	} else if (type < 63) {

		events = new OtherJJ2Event(events, x, y, type, properties);

	} else if (type < 67) {

		events = new CoinGemJJ2Event(events, x, y, type);

	} else if (type < 72) {

		events = new OtherJJ2Event(events, x, y, type, properties);

	} else if (type < 74) {

		events = new FoodJJ2Event(events, x, y, type);

	} else if (type == 80) {

		events = new FoodJJ2Event(events, x, y, type);

	} else if (type < 141) {

		events = new OtherJJ2Event(events, x, y, type, properties);

	} else if (type < 148) {

		events = new FoodJJ2Event(events, x, y, type);

	} else if (type < 154) {

		events = new OtherJJ2Event(events, x, y, type, properties);

	} else if (type < 183) {

		events = new FoodJJ2Event(events, x, y, type);

	} else {

		events = new OtherJJ2Event(events, x, y, type, properties);

	}

	return;

}


int JJ2Level::load (char *fileName, unsigned char diff, bool checkpoint) {

	Anim* pAnims[2];
	File *file;
	char *string;
	unsigned char* aBuffer;
	unsigned char* bBuffer;
	unsigned char* cBuffer;
	unsigned char* dBuffer;
	int aCLength, bCLength, cCLength, dCLength;
	int aLength, bLength, cLength, dLength;
	int tiles;
	int count, x, y;
	unsigned char tileQuad[8];
	short int* quadRefs;
	int flags, width, pitch, height;
	unsigned char startX, startY;

	// Thanks to Neobeo for working out the most of the .j2l format


	try {

		font = new Font(false);

	} catch (int e) {

		throw e;

	}


	difficulty = diff;


	// Open JJ2 level file

	try {

		file = new File(fileName, false);

	} catch (int e) {

		delete font;

		return e;

	}

	// Load level name
	file->seek(188, true);

	string = (char *)file->loadBlock(32);


	// Show loading screen

	video.setPalette(menuPalette);

	video.clearScreen(0);

	x = (canvasW >> 1) - ((strlen(string) + 6) << 2);
	x = fontmn2->showString("LOADING ", x - 60, (canvasH >> 1) - 16);
	fontmn2->showString(string, x, (canvasH >> 1) - 16);

	delete[] string;

	if (::loop(NORMAL_LOOP) == E_QUIT) return E_QUIT;


	// Skip to compressed block lengths
	file->seek(230, true);
	aCLength = file->loadInt();
	aLength = file->loadInt();
	bCLength = file->loadInt();
	bLength = file->loadInt();
	cCLength = file->loadInt();
	cLength = file->loadInt();
	dCLength = file->loadInt();
	dLength = file->loadInt();

	aBuffer = file->loadLZ(aCLength, aLength);
	bBuffer = file->loadLZ(bCLength, bLength);
	cBuffer = file->loadLZ(cCLength, cLength);
	dBuffer = file->loadLZ(dCLength, dLength);

	delete file;

	// Load tile set from given file

	tiles = loadTiles((char *)aBuffer + 51);

	if (tiles < 0) {

		delete[] dBuffer;
		delete[] cBuffer;
		delete[] bBuffer;
		delete[] aBuffer;

		delete font;

		return tiles;

	}

	TSF = tiles >> 28;
	tiles = tiles & 0xFFFF;


	// Next level
	string = (char *)aBuffer + 115;

	if (fileExists(string)) nextLevel = createString(string);
	else nextLevel = createString(string, ".j2l");


	// Music file
	string = (char *)aBuffer + 179;

	if (fileExists(string)) musicFile = createString(string);
	else musicFile = createString(string, ".j2b");


	// Create layers

	quadRefs = (short int *)dBuffer;

	for (count = 0; count < LAYERS; count++) {

		flags = aBuffer[8403 + (count << 2)];
		width = createInt(aBuffer + 8403 + 48 + (count << 2));
		pitch = createInt(aBuffer + 8403 + 80 + (count << 2));
		height = createInt(aBuffer + 8403 + 112 + (count << 2));

		if (pitch & 3) pitch += 4;

		if (aBuffer[8403 + 40 + count]) {

			layers[count] = new JJ2Layer(width, height, flags);

			for (y = 0; y < height; y++) {

				for (x = 0; x < width; x++) {

					if ((x & 3) == 0) memcpy(tileQuad, cBuffer + (quadRefs[x >> 2] << 3), 8);

					layers[count]->setTile(x, y, createShort(tileQuad + ((x & 3) << 1)), TSF? -tiles: tiles);

				}

				quadRefs += pitch >> 2;

			}

		} else {

			// No tile data

			layers[count] = new JJ2Layer();

		}

	}

	layer = layers[3];
	width = layer->getWidth();
	height = layer->getHeight();


	// Load events
	startX = 1;
	startY = 1;

	mods = new JJ2Modifier *[height];
	*mods = new JJ2Modifier[width * height];

	events = NULL;

	for (y = 0; y < height; y++) {

		mods[y] = *mods + (y * width);

		for (x = 0; x < width; x++) {

			// Create event or assign modifier
			createEvent(x, y, bBuffer + (((y * width) + x) << 2));

			if (mods[y][x].type == 29) {

				// Jazz start pos

				startX = x;
				startY = y;

			}

		}

	}

	delete[] dBuffer;
	delete[] cBuffer;
	delete[] bBuffer;
	delete[] aBuffer;


	// Load anims from anims.j2a

	if (loadSprites() < 0) {

		delete file;

		if (events) delete events;
		delete[] *mods;
		delete[] mods;

		for (x = 0; x < LAYERS; x++) delete layers[x];

		delete[] flippedMask;
		delete[] mask;

		delete[] musicFile;
		delete[] nextLevel;

		SDL_FreeSurface(flippedTileSet);
		SDL_FreeSurface(tileSet);

		delete font;

		return count;

	}


	// Set initial water level
	waterLevelTarget = TTOF(layer->getHeight() + 1);
	waterLevel = waterLevelTarget - F8;
	waterLevelSpeed = 0;


	// Generate player's animation set references

	if (multiplayer) {

		string = new char[MTL_P_ANIMS + 1];

		string[0] = MTL_P_ANIMS + 1;
		string[1] = MT_P_ANIMS;
		string[2] = 0;
		string[3] = 54;

		game->send((unsigned char *)string);

		delete[] string;

	}

	// Set the players' initial values

	pAnims[0] = animSets[54];
	pAnims[1] = flippedAnimSets[54];

	if (game) {

		if (!checkpoint) game->setCheckpoint(startX, startY);

		for (count = 0; count < nPlayers; count++) game->resetPlayer(players + count, LT_JJ2LEVEL, pAnims);

	} else {

		localPlayer->reset(LT_JJ2LEVEL, pAnims, startX, startY);

	}


	// And that's us done!


	// Set the tick at which the level will end, though this is not used
	endTime = (5 - difficulty) * 2 * 60 * 1000;


	// Adjust panel fonts to use bonus level palette
	panelBigFont->mapPalette(0, 32, 64, 8);
	panelSmallFont->mapPalette(75, 5, 64, 8);


	return E_NONE;

}