/*
 *
 * levelload.cpp
 *
 * 22nd July 2008: Created levelload.c from parts of level.c
 * 3rd February 2009: Renamed levelload.c to levelload.cpp
 * 18th July 2009: Created demolevel.cpp from parts of level.cpp and
 *                 levelload.cpp
 * 19th July 2009: Added parts of levelload.cpp to level.cpp
 * 28th June 2010: Created levelloadjj2.cpp from parts of levelload.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 level data.
 *
 */


#include "bullet.h"
#include "event/event.h"
#include "level.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/levelplayer.h"
#include "loop.h"
#include "util.h"

#include <string.h>


#define SKEY 254 /* Sprite colour key */


void Level::loadSprite (File* file, Sprite* sprite) {

	unsigned char* pixels;
	int pos, maskOffset;
	int width, height;

	// Load dimensions
	width = file->loadShort() << 2;
	height = file->loadShort();

	file->seek(2, false);

	maskOffset = file->loadShort();

	pos = file->loadShort() << 2;

	// Sprites can be either masked or not masked.
	if (maskOffset) {

		// Masked

		height++;

		// Skip to mask
		file->seek(maskOffset, false);

		// Find the end of the data
		pos += file->tell() + ((width >> 2) * height);

		// Read scrambled, masked pixel data
		pixels = file->loadPixels(width * height, SKEY);
		sprite->setPixels(pixels, width, height, SKEY);

		delete[] pixels;

		file->seek(pos, true);

	} else if (width) {

		// Not masked

		// Read scrambled pixel data
		pixels = file->loadPixels(width * height);
		sprite->setPixels(pixels, width, height, SKEY);

		delete[] pixels;

	}


	return;

}


int Level::loadSprites (char * fileName) {

	File* mainFile = NULL;
	File* specFile = NULL;
	int count;


	// Open fileName
	try {

		specFile = new File(fileName, false);

	} catch (int e) {

		return e;

	}


	// This function loads all the sprites, not fust those in fileName
	try {

		mainFile = new File(F_MAINCHAR, false);

	} catch (int e) {

		delete specFile;

		return e;

	}


	sprites = specFile->loadShort();

	// Include space in the sprite set for the blank sprite at the end
	spriteSet = new Sprite[sprites + 1];

	// Read horizontal offsets
	for (count = 0; count < sprites; count++)
		spriteSet[count].xOffset = specFile->loadChar() << 2;

	// Read vertical offsets
	for (count = 0; count < sprites; count++)
		spriteSet[count].yOffset = specFile->loadChar();


	// Skip to where the sprites start in mainchar.000
	mainFile->seek(2, true);


	// Loop through all the sprites to be loaded
	for (count = 0; count < sprites; count++) {

		if (specFile->loadChar() == 0xFF) {

			// Go to the next sprite/file indicator
			specFile->seek(1, false);

			if (mainFile->loadChar() == 0xFF) {

				// Go to the next sprite/file indicator
				mainFile->seek(1, false);

				/* Both fileName and mainchar.000 have file indicators, so
				create a blank sprite */
				spriteSet[count].clearPixels();

				continue;

			} else {

				// Return to the start of the sprite
				mainFile->seek(-1, false);

				// Load the individual sprite data
				loadSprite(mainFile, spriteSet + count);

			}

		} else {

			// Return to the start of the sprite
			specFile->seek(-1, false);

			// Go to the main file's next sprite/file indicator
			mainFile->seek(2, false);

			// Load the individual sprite data
			loadSprite(specFile, spriteSet + count);

		}


		// Check if the next sprite exists
		// If not, create blank sprites for the remainder
		if (specFile->tell() >= specFile->getSize()) {

			for (count++; count < sprites; count++) {

				spriteSet[count].clearPixels();

			}

		}

	}

	delete mainFile;
	delete specFile;


	// Include a blank sprite at the end
	spriteSet[sprites].clearPixels();
	spriteSet[sprites].xOffset = 0;
	spriteSet[sprites].yOffset = 0;

	return E_NONE;

}


int Level::loadTiles (char* fileName) {

	File* file;
	unsigned char* buffer;
	int rle, pos, index, count, fileSize;
	int tiles;


	try {

		file = new File(fileName, false);

	} catch (int e) {

		return e;

	}


	// Load the palette
	file->loadPalette(palette);


	// Load the background palette
	file->loadPalette(skyPalette);


	// Skip the second, identical, background palette
	file->skipRLE();


	// Load the tile pixel indices

	tiles = 240; // Never more than 240 tiles

	buffer = new unsigned char[tiles << 10];

	file->seek(4, false);

	pos = 0;
	fileSize = file->getSize();

	// Read the RLE pixels
	// file::loadRLE() cannot be used, for reasons that will become clear
	while ((pos < (tiles << 10)) && (file->tell() < fileSize)) {

		rle = file->loadChar();

		if (rle & 128) {

			index = file->loadChar();

			for (count = 0; count < (rle & 127); count++) buffer[pos++] = index;

		} else if (rle) {

			for (count = 0; count < rle; count++)
				buffer[pos++] = file->loadChar();

		} else { // This happens at the end of each tile

			// 0 pixels means 1 pixel, apparently
			buffer[pos++] = file->loadChar();

			file->seek(2, false); /* I assume this is the length of the next
				tile block */

			if (pos == (60 << 10)) file->seek(2, false);
			if (pos == (120 << 10)) file->seek(2, false);
			if (pos == (180 << 10)) file->seek(2, false);

		}

	}

	delete file;

	// Work out how many tiles were actually loaded
	// Should be a multiple of 60
	tiles = pos >> 10;

	tileSet = createSurface(buffer, TTOI(1), TTOI(tiles));
	SDL_SetColorKey(tileSet, SDL_SRCCOLORKEY, TKEY);

	delete[] buffer;

	return tiles;

}


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

	File *file;
	unsigned char *buffer;
	const char *ext;
	char *string;
	int tiles;
	int count, x, y, type;
	unsigned char startX, startY;


	try {

		font = new Font(false);

	} catch (int e) {

		throw e;

	}


	difficulty = diff;


	// Show loading screen

	// Open planet.### file

	if (!strcmp(fileName, LEVEL_FILE)) {

		// Using the downloaded level file

		string = createString("DOWNLOADED");

	} else {

		// Load the planet's name from the planet.### file

		string = createFileName(F_PLANET, fileName + strlen(fileName) - 3);

		try {

			file = new File(string, false);

		} catch (int e) {

			delete[] string;
			delete font;

			return e;

		}

		delete[] string;

		file->seek(2, true);
		string = file->loadString();

		delete file;

	}

	switch (fileName[5]) {

		case '0':

			ext = " LEVEL ONE";

			break;

		case '1':

			ext = " LEVEL TWO";

			break;

		case '2':

			string[0] = 0;
			ext = "SECRET LEVEL";

			break;

		default:

			ext = " LEVEL";

			break;

	}

	video.setPalette(menuPalette);

	clearScreen(0);

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

	delete[] string;

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



	// Open level file

	try {

		file = new File(fileName, false);

	} catch (int e) {

		delete font;

		return e;

	}


	// Load the blocks.### extension

	// Skip past all level data
	file->seek(39, true);
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->skipRLE();
	file->seek(598, false);
	file->skipRLE();
	file->seek(4, false);
	file->skipRLE();
	file->skipRLE();
	file->seek(25, false);
	file->skipRLE();
	file->seek(3, false);

	// Load the level number
	levelNum = file->loadChar() ^ 210;

	// Load the world number
	worldNum = file->loadChar() ^ 4;


	// Load tile set from appropriate blocks.###

	// Load tile set extension
	file->seek(8, false);
	ext = file->loadString();

	if (!strcmp(ext, "999")) {

		// Use the level file's extension instead
		delete[] ext;
		ext = createString(fileName + strlen(fileName) - 3);

	}

	// Allocate space for file names
	string = createFileName(F_BLOCKS, ext);

	delete[] ext;

	tiles = loadTiles(string);

	delete[] string;

	if (tiles < 0) {

		delete file;
		delete font;

		return tiles;

	}


	// Load sprite set from corresponding Sprites.###

	string = createFileName(F_SPRITES, worldNum);

	count = loadSprites(string);

	delete[] string;

	if (count < 0) {

		delete file;
		delete font;

		return count;

	}


	// Skip to tile and event reference data
	file->seek(39, true);

	// Load tile and event references

	buffer = file->loadRLE(LW * LH * 2);

	// Create grid from data
	for (x = 0; x < LW; x++) {

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

			grid[y][x].tile = buffer[(y + (x * LH)) << 1];
			grid[y][x].bg = buffer[((y + (x * LH)) << 1) + 1] >> 7;
			grid[y][x].event = buffer[((y + (x * LH)) << 1) + 1] & 127;
			grid[y][x].hits = 0;
			grid[y][x].time = 0;

		}

	}

	delete[] buffer;

	// A mysterious block of mystery
	file->skipRLE();


	// Load mask data

	buffer = file->loadRLE(tiles * 8);

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

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

			for (x = 0; x < 8; x++)
				mask[count][(y << 3) + x] = (buffer[(count << 3) + y] >> x) & 1;

		}

	}

	delete[] buffer;

	/* 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);

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

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

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

				if (mask[count][((y >> 2) << 3) + (x >> 2)] == 1)
					((char *)(tileSet->pixels))
						[(count * 1024) + (y * 32) + x] = 88;

			}

		}

	}

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


	// Load special event path

	buffer = file->loadRLE(PATHS << 9);

	for (type = 0; type < PATHS; type++) {

		path[type].length = buffer[type << 9] + (buffer[(type << 9) + 1] << 8);
		path[type].node = 0;
		if (path[type].length < 1) path[type].length = 1;
		path[type].x = new short int[path[type].length];
		path[type].y = new short int[path[type].length];

		for (count = 0; count < path[type].length; count++) {

			path[type].x[count] = ((signed char *)buffer)[(type << 9) + (count << 1) + 3] << 2;
			path[type].y[count] = ((signed char *)buffer)[(type << 9) + (count << 1) + 2];

		}

	}

	delete[] buffer;


	// Load event set

	buffer = file->loadRLE(EVENTS * ELENGTH);

	// Fill event set with data
	for (count = 0; count < EVENTS; count++) {

		memcpy(eventSet[count], buffer + (count * ELENGTH), ELENGTH);
		eventSet[count][E_MOVEMENTSP]++;

	}

	delete[] buffer;


	// Process grid

	enemies = items = 0;

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

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

			type = grid[y][x].event;

			if (type) {

				// Eliminate event references for events of too high a difficulty
				if (eventSet[type][E_DIFFICULTY] > difficulty) grid[y][x].event = 0;

				// If the event hurts and can be killed, it is an enemy
				// Anything else that scores is an item
				if ((eventSet[type][E_MODIFIER] == 0) && eventSet[type][E_HITSTOKILL]) enemies++;
				else if (eventSet[type][E_ADDEDSCORE]) items++;

			}

		}

	}


	// Yet more doubtless essential data
	file->skipRLE();


	// Load animation set

	buffer = file->loadRLE(ANIMS << 6);

	// Create animation set based on that data
	for (count = 0; count < ANIMS; count++) {

		animSet[count].setData(buffer[(count << 6) + 6],
			buffer[count << 6], buffer[(count << 6) + 1],
			buffer[(count << 6) + 3], buffer[(count << 6) + 4],
			buffer[(count << 6) + 2], buffer[(count << 6) + 5]);

		for (y = 0; y < buffer[(count << 6) + 6]; y++) {

			// Get frame
			x = buffer[(count << 6) + 7 + y];
			if (x > sprites) x = sprites;

			// Assign sprite and vertical offset
			animSet[count].setFrame(y, true);
			animSet[count].setFrameData(spriteSet + x,
				buffer[(count << 6) + 26 + y], buffer[(count << 6) + 45 + y]);

		}

	}

	delete[] buffer;


	// At general data

	// There's a a whole load of unknown data around here

	// Like another one of those pesky RLE blocks
	file->skipRLE();

	// And 217 bytes of DOOM
	file->seek(217, false);


	// Load sound map

	x = file->tell();

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

		file->seek(x + (count * 9), true);

		string = file->loadString();

		soundMap[count] = -1;

		// Search for matching sound

		for (y = 0; (y < nSounds) && (soundMap[count] == -1); y++) {

			if (!strcmp(string, sounds[y].name)) soundMap[count] = y;

		}

		delete[] string;

	}

	file->seek(x + 288, true);

	// Music file
	musicFile = file->loadString();

	// 26 bytes of undiscovered usefulness, less the music file name
	file->seek(x + 314, true);

	// End of episode cutscene
	sceneFile = file->loadString();

	// 52 bytes of undiscovered usefulness, less the cutscene file name
	file->seek(x + 366, true);


	// The players' initial coordinates
	startX = file->loadShort();
	startY = file->loadShort() + 1;


	// Next level
	x = file->loadChar();
	y = file->loadChar();
	setNext(x, y);


	// Thanks to Doubble Dutch for the water level bytes
	file->seek(4, false);
	waterLevelTarget = ITOF(file->loadShort());
	waterLevel = waterLevelTarget - F8;
	waterLevelSpeed = 0;


	// Thanks to Feline and the JCS94 team for the next bits:

	file->seek(3, false);

	// Now at "Section 15"


	// Load player's animation set references

	buffer = file->loadRLE(PANIMS * 2);
	string = new char[PANIMS + 3];

	for (x = 0; x < PANIMS; x++) string[x + 3] = buffer[x << 1];

	delete[] buffer;

	if (gameMode) {

		string[0] = MTL_P_ANIMS;
		string[1] = MT_P_ANIMS;
		string[2] = 0;
		game->send((unsigned char *)string);

	}

	// Set the players' initial values
	if (game) {

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

		for (count = 0; count < nPlayers; count++) game->resetPlayer(players + count, LT_LEVEL, string + 3);

	} else {

		localPlayer->reset(LT_LEVEL, string + 3, startX, startY);

	}

	delete[] string;


	// Load miscellaneous animations
	miscAnims[0] = file->loadChar();
	miscAnims[1] = file->loadChar();
	miscAnims[2] = file->loadChar();
	miscAnims[3] = file->loadChar();


	// Load bullet set
	buffer = file->loadRLE(BULLETS * BLENGTH);

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

		memcpy(bulletSet[count], buffer + (count * BLENGTH), BLENGTH);

		// Make sure bullets go in the right direction

		if (bulletSet[count][B_XSPEED] > 0)
			bulletSet[count][B_XSPEED] = -bulletSet[count][B_XSPEED];

		if (bulletSet[count][B_XSPEED | 1] < 0)
			bulletSet[count][B_XSPEED | 1] = -bulletSet[count][B_XSPEED | 1];

		if (bulletSet[count][B_XSPEED | 2] > 0)
			bulletSet[count][B_XSPEED | 2] = -bulletSet[count][B_XSPEED | 2];

		if (bulletSet[count][B_XSPEED | 3] < 0)
			bulletSet[count][B_XSPEED | 3] = -bulletSet[count][B_XSPEED | 3];

	}

	delete[] buffer;


	// Now at "Section 18." More skippability.
	file->skipRLE();


	// Now at "Section 19," THE MAGIC SECTION

	// First byte is the background palette effect type
	type = file->loadChar();

	sky = false;

	switch (type) {

		case 2:

			sky = true;

			// Sky background effect
			paletteEffects = new SkyPaletteEffect(156, 100, FH, skyPalette, NULL);

			break;

		case 8:

			// Parallaxing background effect
			paletteEffects = new P2DPaletteEffect(128, 64, FE, NULL);

			break;

		case 9:

			// Diagonal stripes "parallaxing" background effect
			paletteEffects = new P1DPaletteEffect(128, 32, FH, NULL);

			break;

		case 11:

			// The deeper below water, the darker it gets
			paletteEffects = new WaterPaletteEffect(TTOF(32), NULL);

			break;

		default:

			// No effect
			paletteEffects = NULL;

			break;

	}

	// Palette animations
	// These are applied to every level without a conflicting background effect
	// As a result, there are a few levels with things animated that shouldn't
	// be

	// In Diamondus: The red/yellow palette animation
	paletteEffects = new RotatePaletteEffect(112, 4, F32, paletteEffects);

	// In Diamondus: The waterfall palette animation
	paletteEffects = new RotatePaletteEffect(116, 8, F16, paletteEffects);

	// The following were discoverd by Unknown/Violet

	paletteEffects = new RotatePaletteEffect(124, 3, F16, paletteEffects);

	if ((type != PE_1D) && (type != PE_2D))
		paletteEffects = new RotatePaletteEffect(132, 8, F16, paletteEffects);

	if ((type != PE_SKY) && (type != PE_2D))
		paletteEffects = new RotatePaletteEffect(160, 32, -F16, paletteEffects);

	if (type != PE_SKY) {

		paletteEffects = new RotatePaletteEffect(192, 32, -F32, paletteEffects);
		paletteEffects = new RotatePaletteEffect(224, 16, F16, paletteEffects);

	}

	// Level fade-in/white-in effect
	if (checkpoint) paletteEffects = new FadeInPaletteEffect(T_START, paletteEffects);
	else paletteEffects = new WhiteInPaletteEffect(T_START, paletteEffects);


	file->seek(1, false);

	skyOrb = file->loadChar(); /* A.k.a the sun, the moon, the brightest star,
		that red planet with blue veins... */


	// And that's us done!

	delete file;


	// Set the tick at which the level will end
	endTime = (5 - difficulty) * 2 * 60 * 1000;


	events = NULL;
	bullets = NULL;

	energyBar = 0;

	return E_NONE;

}