/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/memstream.h"

#include "gob/gob.h"
#include "gob/map.h"
#include "gob/dataio.h"
#include "gob/goblin.h"
#include "gob/mult.h"
#include "gob/sound/sound.h"

namespace Gob {

Map_v1::Map_v1(GobEngine *vm) : Map(vm) {
}

Map_v1::~Map_v1() {
}

void Map_v1::init() {
	if (_passMap || _itemsMap)
		return;

	_passWidth = 26;
	_mapWidth  = 26;
	_mapHeight = 28;

	_passMap = new int8[_mapHeight * _mapWidth];
	memset(_passMap, 0, _mapHeight * _mapWidth * sizeof(int8));

	_itemsMap = new int16*[_mapHeight];
	 for (int i = 0; i < _mapHeight; i++) {
		_itemsMap[i] = new int16[_mapWidth];
		memset(_itemsMap[i], 0, _mapWidth * sizeof(int16));
	}

	_wayPointCount = 40;
	_wayPoints = new WayPoint[40];
	memset(_wayPoints, 0, sizeof(WayPoint));
}

void Map_v1::loadMapObjects(const char *avjFile) {
	char avoName[128];
	byte *dataBuf;
	int16 tmp;
	int32 flag;
	int16 gobDataCount;
	int16 objDataCount;
	uint32 gobsPos;
	uint32 objsPos;

	strcpy(avoName, _sourceFile);
	strcat(avoName, ".avo");

	int32 size;
	dataBuf = _vm->_dataIO->getFile(avoName, size);
	if (!dataBuf) {
		dataBuf = _vm->_dataIO->getFile(avjFile, size);
		_loadFromAvo = false;
	} else
		_loadFromAvo = true;

	Common::MemoryReadStream mapData(dataBuf, 4294967295U);

	init();

	if (_loadFromAvo) {
		mapData.read(_passMap, _mapHeight * _mapWidth);

		for (int y = 0; y < _mapHeight; y++)
			for (int x = 0; x < _mapWidth; x++)
				_itemsMap[y][x] = mapData.readSByte();

		for (int i = 0; i < 40; i++) {
			_wayPoints[i].x = mapData.readUint16LE();
			_wayPoints[i].y = mapData.readUint16LE();
		}

		for (int i = 0; i < 20; i++) {
			_itemPoses[i].x      = mapData.readByte();
			_itemPoses[i].y      = mapData.readByte();
			_itemPoses[i].orient = mapData.readByte();
		}
	}

	mapData.skip(32 + 76 + 4 + 20);

	for (int i = 0; i < 3; i++) {
		tmp = mapData.readUint16LE();
		mapData.skip(tmp * 14);
	}

	loadSounds(mapData);

	mapData.skip(4 + 24);

	gobDataCount = mapData.readUint16LE();
	objDataCount = mapData.readUint16LE();

	gobsPos = mapData.pos();
	Common::MemoryReadStream gobsData(dataBuf + gobsPos, 4294967295U);
	mapData.skip(gobDataCount * 8);

	objsPos = mapData.pos();
	Common::MemoryReadStream objsData(dataBuf + objsPos, 4294967295U);
	mapData.skip(objDataCount * 8);

	loadGoblins(mapData, gobsPos);
	loadObjects(mapData, objsPos);

	tmp = mapData.readUint16LE();
	for (int i = 0; i < tmp; i++) {
		mapData.skip(30);

		flag = mapData.readSint32LE();
		mapData.skip(56);

		if (flag != 0)
			mapData.skip(30);
	}

	mapData.skip(50);
	loadItemToObject(mapData);

	delete[] dataBuf;
}

void Map_v1::loadSounds(Common::SeekableReadStream &data) {
	int16 count;
	char buf[19];
	char sndNames[20][19];

	count = data.readUint16LE();

	for (int i = 0; i < count; i++) {
		data.read(buf, 14);
		buf[14] = 0;
		strcat(buf, ".SND");
		strcpy(sndNames[i], buf);
	}

	_vm->_sound->sampleLoad(&_vm->_goblin->_soundData[14], SOUND_SND, "diamant1.snd");

	for (int i = 0; i < count; i++) {
		if (!_vm->_dataIO->hasFile(sndNames[i]))
			continue;

		_vm->_sound->sampleLoad(&_vm->_goblin->_soundData[i], SOUND_SND, sndNames[i]);
	}
}

void Map_v1::loadGoblins(Common::SeekableReadStream &data, uint32 gobsPos) {
	Goblin::Gob_State *pState;
	uint32 tmpStateData[40 * 6];
	uint32 tmpPos;

	_vm->_goblin->_gobsCount = data.readUint16LE();
	for (int i = 0; i < _vm->_goblin->_gobsCount; i++) {
		int linesCount = (i == 3) ? 70 : 40;

		_vm->_goblin->_goblins[i] = new Goblin::Gob_Object;
		memset(_vm->_goblin->_goblins[i], 0, sizeof(Goblin::Gob_Object));

		tmpPos = data.pos();
		data.seek(gobsPos);
		_vm->_goblin->_goblins[i]->xPos = data.readUint16LE();
		_vm->_goblin->_goblins[i]->yPos = data.readUint16LE();
		_vm->_goblin->_goblins[i]->order = data.readUint16LE();
		_vm->_goblin->_goblins[i]->state = data.readUint16LE();
		gobsPos = data.pos();
		data.seek(tmpPos);

		_vm->_goblin->_goblins[i]->stateMach =
				new Goblin::Gob_StateLine[linesCount];
		for (int state = 0; state < linesCount; ++state)
			for (int col = 0; col < 6; ++col)
				_vm->_goblin->_goblins[i]->stateMach[state][col] = 0;

		for (int state = 0; state < 40; ++state)
			for (int col = 0; col < 6; ++col)
				tmpStateData[state * 6 + col] = data.readUint32LE();

		data.skip(160);
		_vm->_goblin->_goblins[i]->multObjIndex = data.readByte();
		data.skip(1);

		_vm->_goblin->_goblins[i]->realStateMach =
			_vm->_goblin->_goblins[i]->stateMach;
		for (int state = 0; state < 40; state++) {
			for (int col = 0; col < 6; col++) {
				if (tmpStateData[state * 6 + col] == 0) {
					_vm->_goblin->_goblins[i]->stateMach[state][col] = 0;
					continue;
				}

				Goblin::Gob_State *tmpState = new Goblin::Gob_State;
				memset(tmpState, 0, sizeof(Goblin::Gob_State));

				_vm->_goblin->_goblins[i]->stateMach[state][col] = tmpState;

				tmpState->animation = data.readUint16LE();
				tmpState->layer = data.readUint16LE();
				data.skip(8);
				tmpState->unk0 = data.readUint16LE();
				tmpState->unk1 = data.readUint16LE();

				data.skip(2);
				if (data.readUint32LE() == 0) {
					data.skip(2);
					tmpState->sndItem = -1;
				} else
					tmpState->sndItem = data.readUint16LE();

				tmpState->freq = data.readUint16LE();
				tmpState->repCount = data.readUint16LE();
				tmpState->sndFrame = data.readUint16LE();
			}
		}
	}

	pState = new Goblin::Gob_State;
	memset(pState, 0, sizeof(Goblin::Gob_State));
	_vm->_goblin->_goblins[0]->stateMach[39][0] = pState;
	pState->layer = 98;
	pState->sndItem = -1;

	pState = new Goblin::Gob_State;
	memset(pState, 0, sizeof(Goblin::Gob_State));
	_vm->_goblin->_goblins[1]->stateMach[39][0] = pState;
	pState->layer = 99;
	pState->sndItem = -1;

	pState = new Goblin::Gob_State;
	memset(pState, 0, sizeof(Goblin::Gob_State));
	_vm->_goblin->_goblins[2]->stateMach[39][0] = pState;
	pState->layer = 100;
	pState->sndItem = -1;

	_vm->_goblin->_goblins[2]->stateMach[10][0]->sndFrame = 13;
	_vm->_goblin->_goblins[2]->stateMach[11][0]->sndFrame = 13;
	_vm->_goblin->_goblins[2]->stateMach[28][0]->sndFrame = 13;
	_vm->_goblin->_goblins[2]->stateMach[29][0]->sndFrame = 13;

	_vm->_goblin->_goblins[1]->stateMach[10][0]->sndFrame = 13;
	_vm->_goblin->_goblins[1]->stateMach[11][0]->sndFrame = 13;

	for (int state = 40; state < 70; state++) {
		pState = new Goblin::Gob_State;
		memset(pState, 0, sizeof(Goblin::Gob_State));
		_vm->_goblin->_goblins[3]->stateMach[state][0] = pState;
		_vm->_goblin->_goblins[3]->stateMach[state][1] = 0;

		pState->animation = 9;
		pState->layer = state - 40;
		pState->sndItem = -1;
	}
}

void Map_v1::loadObjects(Common::SeekableReadStream &data, uint32 objsPos) {
	Goblin::Gob_State *pState;
	uint32 tmpStateData[40 * 6];
	uint32 tmpPos;

	_vm->_goblin->_objCount = data.readUint16LE();
	for (int i = 0; i < _vm->_goblin->_objCount; i++) {
		_vm->_goblin->_objects[i] = new Goblin::Gob_Object;
		memset(_vm->_goblin->_objects[i], 0, sizeof(Goblin::Gob_Object));

		tmpPos = data.pos();
		data.seek(objsPos);
		_vm->_goblin->_objects[i]->xPos = data.readUint16LE();
		_vm->_goblin->_objects[i]->yPos = data.readUint16LE();
		_vm->_goblin->_objects[i]->order = data.readUint16LE();
		_vm->_goblin->_objects[i]->state = data.readUint16LE();
		objsPos = data.pos();
		data.seek(tmpPos);

		_vm->_goblin->_objects[i]->stateMach = new Goblin::Gob_StateLine[40];
		for (int state = 0; state < 40; ++state) {
			for (int col = 0; col < 6; ++col) {
				_vm->_goblin->_objects[i]->stateMach[state][col] = 0;
				tmpStateData[state * 6 + col] = data.readUint32LE();
			}
		}

		data.skip(160);
		_vm->_goblin->_objects[i]->multObjIndex = data.readByte();
		data.skip(1);

		_vm->_goblin->_objects[i]->realStateMach =
			_vm->_goblin->_objects[i]->stateMach;
		for (int state = 0; state < 40; state++) {
			for (int col = 0; col < 6; col++) {
				if (tmpStateData[state * 6 + col] == 0) {
					_vm->_goblin->_objects[i]->stateMach[state][col] = 0;
					continue;
				}

				Goblin::Gob_State *tmpState = new Goblin::Gob_State;
				memset(tmpState, 0, sizeof(Goblin::Gob_State));
				_vm->_goblin->_objects[i]->stateMach[state][col] = tmpState;

				tmpState->animation = data.readUint16LE();
				tmpState->layer = data.readUint16LE();
				data.skip(8);
				tmpState->unk0 = data.readUint16LE();
				tmpState->unk1 = data.readUint16LE();

				data.skip(2);
				if (data.readUint32LE() == 0) {
					data.skip(2);
					tmpState->sndItem = -1;
				} else
					tmpState->sndItem = data.readUint16LE();

				tmpState->freq = data.readUint16LE();
				tmpState->repCount = data.readUint16LE();
				tmpState->sndFrame = data.readUint16LE();
			}
		}
	}

	_vm->_goblin->_objects[10] = new Goblin::Gob_Object;
	memset(_vm->_goblin->_objects[10], 0, sizeof(Goblin::Gob_Object));

	_vm->_goblin->_objects[10]->stateMach = new Goblin::Gob_StateLine[40];
	for (int state = 0; state < 40; ++state)
		for (int col = 0; col < 6; ++col)
			_vm->_goblin->_objects[10]->stateMach[state][col] = 0;

	pState = new Goblin::Gob_State;
	memset(pState, 0, sizeof(Goblin::Gob_State));
	_vm->_goblin->_objects[10]->stateMach[0][0] = pState;

	pState->animation = 9;
	pState->layer = 27;
	pState->sndItem = -1;

	_vm->_goblin->placeObject(_vm->_goblin->_objects[10], 1, 0, 0, 0, 0);

	_vm->_goblin->_objects[10]->realStateMach =
		_vm->_goblin->_objects[10]->stateMach;
	_vm->_goblin->_objects[10]->type = 1;
	_vm->_goblin->_objects[10]->unk14 = 1;
}

void Map_v1::loadItemToObject(Common::SeekableReadStream &data) {
	int16 count;

	if (data.readUint16LE() == 0)
		return;

	data.skip(1456);
	count = data.readUint16LE();
	for (int i = 0; i < count; i++) {
		data.skip(20);
		_vm->_goblin->_itemToObject[i] = data.readUint16LE();
		data.skip(5);
	}
}

void Map_v1::optimizePoints(Mult::Mult_Object *obj, int16 x, int16 y) {
	if (_nearestWayPoint < _nearestDest) {
		for (int i = _nearestWayPoint; i <= _nearestDest; i++) {
			if (checkDirectPath(0, _curGoblinX, _curGoblinY,
				_wayPoints[i].x, _wayPoints[i].y) == 1)
				_nearestWayPoint = i;
		}
	} else if (_nearestWayPoint > _nearestDest) {
		for (int i = _nearestWayPoint; i >= _nearestDest; i--) {
			if (checkDirectPath(0, _curGoblinX, _curGoblinY,
				_wayPoints[i].x, _wayPoints[i].y) == 1)
				_nearestWayPoint = i;
		}
	}
}

void Map_v1::findNearestToGob(Mult::Mult_Object *obj) {
	int16 wayPoint = findNearestWayPoint(_curGoblinX, _curGoblinY);

	if (wayPoint != -1)
		_nearestWayPoint = wayPoint;
}

void Map_v1::findNearestToDest(Mult::Mult_Object *obj) {
	int16 wayPoint = findNearestWayPoint(_destX, _destY);

	if (wayPoint != -1)
		_nearestDest = wayPoint;
}

} // End of namespace Gob