/* 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 "chewy/detail.h"
#include "chewy/globals.h"
#include "chewy/mcga_graphics.h"
#include "chewy/memory.h"
#include "chewy/sound.h"
#include "chewy/resource.h"

namespace Chewy {

#define ON 1
#define OFF 0

bool RoomInfo::load(Common::SeekableReadStream *src) {
	_roomNr = src->readByte();
	_imageNr = src->readByte();
	_autoMovNr = src->readByte();
	_tafLoad = src->readByte();
	src->read(_tafName, 14);
	_zoomFactor = src->readByte();
	_dummy = src->readByte();

	return true;
}

bool RoomAutoMov::load(Common::SeekableReadStream *src) {
	_x = src->readSint16LE();
	_y = src->readSint16LE();
	_sprNr = src->readByte();
	_dummy = src->readByte();

	return true;
}

bool SoundDefBlk::load(Common::SeekableReadStream *src) {
	int i;

	src->skip(MAX_SOUNDS * 2);	// sound_enable flags
	for (i = 0; i < MAX_SOUNDS; ++i)
		sound_index[i] = src->readSint16LE();
	for (i = 0; i < MAX_SOUNDS; ++i)
		sound_start[i] = src->readSint16LE();
	for (i = 0; i < MAX_SOUNDS; ++i)
		channel[i] = src->readSint16LE();
	for (i = 0; i < MAX_SOUNDS; ++i)
		volume[i] = src->readSint16LE();
	for (i = 0; i < MAX_SOUNDS; ++i)
		repeats[i] = src->readSint16LE();
	for (i = 0; i < MAX_SOUNDS; ++i)
		stereo[i] = src->readSint16LE();

	return true;
}

bool AniDetailInfo::load(Common::SeekableReadStream *src) {
	x = src->readSint16LE();
	y = src->readSint16LE();
	start_flag = src->readByte();
	repeat = src->readByte();
	start_ani = src->readSint16LE();
	end_ani = src->readSint16LE();
	ani_count = src->readSint16LE();
	delay = src->readSint16LE();
	delay_count = src->readSint16LE();
	reverse = src->readSint16LE();
	timer_start = src->readSint16LE();
	z_ebene = src->readSint16LE();
	load_flag = src->readByte();
	zoom = src->readByte();
	sfx.load(src);
	show_1_phase = src->readSint16LE();
	phase_nr = src->readSint16LE();

	return true;
}

bool StaticDetailInfo::load(Common::SeekableReadStream *src) {
	x = src->readSint16LE();
	y = src->readSint16LE();
	SprNr = src->readSint16LE();
	z_ebene = src->readSint16LE();
	hide = src->readByte();
	Dummy = src->readByte();

	return true;
}

bool RoomDetailInfo::load(Common::SeekableReadStream *src) {
	int i;

	_staticDetailNr = src->readSint16LE();
	_aniDetailNr = src->readSint16LE();
	src->skip(4);	// dptr
	for (i = 0; i < MAXDETAILS; ++i)
		Ainfo[i].load(src);
	for (i = 0; i < MAXDETAILS; ++i)
		staticSprite[i].load(src);
	for (i = 0; i < MAX_M_ITEMS * 4; ++i)
		mvect[i] = src->readSint16LE();
	for (i = 0; i < MAX_M_ITEMS; ++i)
		mtxt[i] = src->readSint16LE();
	Ri.load(src);
	for (i = 0; i < MAX_AUTO_MOV; ++i)
		autoMove[i].load(src);
	for (i = 0; i < MAXDETAILS * MAX_SOUNDS; ++i)
		detailSfxIndex[i] = src->readSint16LE();
	src->skip(4 * MAXDETAILS * MAX_SOUNDS); // sample offsets

	return true;
}

bool RdiDataHeader::load(Common::SeekableReadStream *src) {
	src->read(_id, 4);
	_nr = src->readSint16LE();

	return true;
}


Detail::Detail() {
	int16 size = sizeof(RoomDetailInfo);
	char *tptr = (char *)&_rdi;

	for (int16 i = 0; i < size; i++)
		*tptr++ = 0;

	for (int16 i = 0; i < MAXDETAILS; i++) {
		_rdi.staticSprite[i].SprNr = -1;
		_rdi.Ainfo[i].start_ani = -1;
	}

	for (int16 i = 0; i < (MAX_M_ITEMS - 1) << 2; i += 4)
		_rdi.mvect[i] = -1;

	_globalDelay = 0;
	_aniFreezeflag = false;
	_fullTaf = false;
	_rdi.dptr = nullptr;
	_tafName = "";
	for (int16 i = 0; i < (MAXDETAILS * MAX_SOUNDS); i++) {
		_rdi.detailSfxIndex[i] = -1;
	}
	_directTafAni = OFF;
}

Detail::~Detail() {
	_directTafAni = OFF;
}

void Detail::load_rdi(const char *fname_, int16 room_nr) {
	Common::File f;
	TafInfo *tmprdi = _rdi.dptr;

	if (fname_ && f.open(fname_)) {
		if (_rdiDataHeader.load(&f)) {
			if (!scumm_strnicmp(_rdiDataHeader._id, "RDI", 3)) {
				f.seek(room_nr * RoomDetailInfo::SIZE(), SEEK_CUR);

				if (!_rdi.load(&f)) {
					error("load_rdi error");
				}
			} else {
				error("load_rdi error");
			}
		} else {
			error("load_rdi error");
		}

		f.close();
	} else {
		error("load_rdi error");
	}

	_rdi.dptr = tmprdi;
}

void Detail::load_rdi_taf(const char *filename, int16 load_flag) {
	if (!_tafName.equalsIgnoreCase(filename)) {
		if (_rdi.dptr) {
			if (_fullTaf) {
				free(_rdi.dptr);
				_rdi.dptr = nullptr;
				_fullTaf = false;
			} else {
				del_taf_tbl(_rdi.dptr);
			}
		}
		_tafName = filename;
		if (!load_flag) {
			_rdi.dptr = init_taf_tbl(filename);
			load_taf_tbl(_rdi.dptr);
		} else {
			_rdi.dptr = _G(mem)->taf_adr(filename);
			_fullTaf = true;
		}
	} else if (!_fullTaf)
		load_taf_tbl(_rdi.dptr);
}

void Detail::del_dptr() {
	if (_rdi.dptr) {
		if (_fullTaf)
			free((char *)_rdi.dptr);
		else {
			del_taf_tbl(_rdi.dptr);
			_rdi.dptr = nullptr;
			_tafName.clear();
		}
		_rdi.dptr = nullptr;
	}
}

void Detail::load_taf_tbl(TafInfo *fti) {
	if (!fti) {
		fti = _rdi.dptr;
	}

	if (fti) {
		for (int16 i = 0; i < MAXDETAILS; i++) {
			if (_rdi.staticSprite[i].SprNr != -1)
				load_taf_seq(_rdi.staticSprite[i].SprNr, 1, fti);
			if (_rdi.Ainfo[i].start_ani != -1 &&
					_rdi.Ainfo[i].end_ani != -1 && !_rdi.Ainfo[i].load_flag)
				load_taf_seq(_rdi.Ainfo[i].start_ani, (_rdi.Ainfo[i].end_ani - _rdi.Ainfo[i].start_ani) + 1, fti);
		}
	}
}

TafInfo *Detail::init_taf_tbl(const char *fname_) {
	TafInfo *Tt = nullptr;
	SpriteResource *res = new SpriteResource(_tafName);
	int16 nr = res->getChunkCount();
	byte *tmp = (byte *)MALLOC((int32)nr * sizeof(byte *) + sizeof(TafInfo));

	Tt = (TafInfo *)tmp;
	Tt->count = nr;
	Tt->correction = (int16 *)MALLOC((int32)Tt->count * 2 * sizeof(int16));
	Tt->image = (byte **)(tmp + sizeof(TafInfo));
	memcpy(Tt->correction, (byte *)res->getSpriteCorrectionsTable(), Tt->count * 2 * sizeof(int16));
	Tt->palette = nullptr;

	delete res;

	return Tt;
}

void Detail::del_taf_tbl(TafInfo *Tt) {
	if (!Tt) {
		Tt = _rdi.dptr;
	}
	for (int16 i = 0; i < Tt->count; i++) {
		free(Tt->image[i]);
	}
	free(Tt->correction);
	free(Tt);
}

void Detail::del_taf_tbl(int16 start, int16 nr, TafInfo *Tt) {
	if (!Tt)
		Tt = _rdi.dptr;
	for (int16 i = start; i < start + nr && i < Tt->count; i++) {
		free(Tt->image[i]);
		Tt->image[i] = nullptr;
	}
}

void Detail::load_taf_seq(int16 sprNr, int16 sprCount, TafInfo *Tt) {
	if (!Tt)
		Tt = _rdi.dptr;

	SpriteResource *res = new SpriteResource(_tafName);

	for (int16 i = 0; i < sprCount; i++) {
		if (!Tt->image[sprNr + i]) {
			res->getSpriteData(sprNr + i, &Tt->image[sprNr + i], true);
		}
	}

	delete res;
}

void Detail::hideStaticSpr(int16 nr) {
	if (nr >= 0 && nr < MAXDETAILS)
		_rdi.staticSprite[nr].hide = true;
}

void Detail::showStaticSpr(int16 nr) {
	if (nr >= 0 && nr < MAXDETAILS)
		_rdi.staticSprite[nr].hide = false;
}

void Detail::setStaticPos(int16 detNr, int16 x, int16 y, bool hideFl, bool correctionFlag) {
	if (correctionFlag) {
		int16 *Cxy = &_rdi.dptr->correction[_rdi.staticSprite[detNr].SprNr * 2];
		x += Cxy[0];
		y += Cxy[1];
	}
	_rdi.staticSprite[detNr].x = x;
	_rdi.staticSprite[detNr].y = y;
	_rdi.staticSprite[detNr].hide = hideFl;
}

void Detail::setDetailPos(int16 detNr, int16 x, int16 y) {
	_rdi.Ainfo[detNr].x = x;
	_rdi.Ainfo[detNr].y = y;
}

void Detail::getAniValues(int16 aniNr, int16 *start, int16 *end) {
	*start = _rdi.Ainfo[aniNr].start_ani;
	*end = _rdi.Ainfo[aniNr].end_ani;
}

AniDetailInfo *Detail::getAniDetail(int16 aniNr) {
	return &_rdi.Ainfo[aniNr];
}

void Detail::init_taf(TafInfo *dptr) {
	_rdi.dptr = dptr;
}

TafInfo *Detail::get_taf_info() {
	return _rdi.dptr;
}

RoomDetailInfo *Detail::getRoomDetailInfo() {
	return &_rdi;
}

void Detail::freezeAni() {
	_aniFreezeflag = true;
}

void Detail::unfreezeAni() {
	_aniFreezeflag = false;
}

void Detail::set_static_ani(int16 ani_nr, int16 static_nr) {
	_rdi.Ainfo[ani_nr].show_1_phase = true;
	if (static_nr != -1)
		_rdi.Ainfo[ani_nr].phase_nr = static_nr;
}

void Detail::del_static_ani(int16 ani_nr) {
	_rdi.Ainfo[ani_nr].show_1_phase = false;
}

void Detail::plot_ani_details(int16 scrx, int16 scry, int16 start, int16 end, int16 zoomx, int16 zoomy) {
	if (start > end)
		SWAP(start, end);

	if (start >= MAXDETAILS)
		start = 0;

	if (end >= MAXDETAILS)
		end = MAXDETAILS - 1;

	for (int16 i = start; (i <= end); i++) {
		AniDetailInfo *adiptr = &_rdi.Ainfo[i];
		if ((adiptr->start_flag) && (adiptr->start_ani != -1) && (adiptr->end_ani != -1)) {
			int16 sprnr = adiptr->ani_count;
			int16 *Cxy = _rdi.dptr->correction + sprnr * 2;
			int16 kx = Cxy[0];
			int16 ky = Cxy[1];
			if (zoomx != 0 || zoomy != 0)
				calc_zoom_kor(&kx, &ky, zoomx, zoomy);
			int16 x = adiptr->x + kx - scrx;
			int16 y = adiptr->y + ky - scry;
			if (adiptr->load_flag == 1) {
				load_taf_ani_sprite(sprnr);
				_G(out)->scale_set(_tafLoadBuffer, x, y, zoomx, zoomy, 0);
			} else
				_G(out)->scale_set(_rdi.dptr->image[sprnr], x, y, zoomx, zoomy, 0);

			Sound *sound = g_engine->_sound;

			for (int16 k = 0; k < MAX_SOUNDS; k++) {
				const int16 sfxSlot = adiptr->sfx.sound_index[k];
				if (sfxSlot != -1) {
					const int16 sfxIndex = _rdi.detailSfxIndex[sfxSlot];
					if (adiptr->sfx.sound_start[k] == adiptr->ani_count &&
					        !adiptr->delay_count) {
						const uint channel = adiptr->sfx.channel[k] & 7;
						sound->playSound(sfxIndex, channel, adiptr->sfx.repeats[k], adiptr->sfx.volume[k], adiptr->sfx.stereo[k]);
					}
				}
			}

			if (!_aniFreezeflag) {
				if (adiptr->reverse) {
					if (adiptr->delay_count > 0)
						--adiptr->delay_count;
					else {
						adiptr->delay_count = adiptr->delay + _globalDelay;
						if (adiptr->ani_count > adiptr->start_ani)
							--adiptr->ani_count;
						else {
							adiptr->ani_count = adiptr->end_ani;
							if ((adiptr->start_flag != 255) && (adiptr->start_flag > 0)) {
								--adiptr->start_flag;

							}
						}
					}
				} else {
					if (adiptr->delay_count > 0)
						--adiptr->delay_count;
					else {
						adiptr->delay_count = adiptr->delay + _globalDelay;
						if (adiptr->ani_count < adiptr->end_ani)
							++adiptr->ani_count;
						else {
							adiptr->ani_count = adiptr->start_ani;
							if ((adiptr->start_flag != 255) && (adiptr->start_flag > 0)) {
								--adiptr->start_flag;
							}
						}
					}
				}
			}
		} else {
			adiptr->start_flag = 0;
			if (adiptr->show_1_phase) {
				_rdi.staticSprite[adiptr->phase_nr].hide = false;
				plot_static_details(scrx, scry, adiptr->phase_nr, adiptr->phase_nr);
				_rdi.staticSprite[adiptr->phase_nr].hide = true;
			}
		}
	}
}

void Detail::plot_static_details(int16 scrx, int16 scry, int16 start, int16 end) {
	if (start > end)
		SWAP(start, end);

	if (start >= MAXDETAILS)
		start = 0;
	
	if (end >= MAXDETAILS)
		end = MAXDETAILS - 1;

	for (int16 i = start; i <= end; i++) {
		if (_rdi.staticSprite[i].SprNr != -1 && !_rdi.staticSprite[i].hide) {
			int16 x = _rdi.staticSprite[i].x - scrx;
			int16 y = _rdi.staticSprite[i].y - scry;
			byte *spriteImage = _rdi.dptr->image[_rdi.staticSprite[i].SprNr];
			_G(out)->spriteSet(spriteImage, x, y, 0);
		}
	}
}

void Detail::startDetail(int16 nr, int16 rep, int16 reverse) {
	if (nr != -1) {
		AniDetailInfo *adiptr = &_rdi.Ainfo[nr];
		if (rep)
			adiptr->start_flag = rep;
		else
			adiptr->start_flag = adiptr->repeat;

		adiptr->reverse = reverse;
		if (reverse)
			adiptr->ani_count = adiptr->end_ani;
		else
			adiptr->ani_count = adiptr->start_ani;
	}
}

void Detail::stopDetail(int16 nr) {
	if (nr >= 0 && nr < 32) {
		_rdi.Ainfo[nr].start_flag = 0;
	}
}

void Detail::playSound(int16 nr, int16 slot) {
	const int16 sfxSlot = _rdi.Ainfo[nr].sfx.sound_index[slot];
	if (sfxSlot != -1) {
		const int16 sfxIndex = _rdi.detailSfxIndex[sfxSlot];
		g_engine->_sound->playSound(sfxIndex, slot,
			_rdi.Ainfo[nr].sfx.repeats[slot], _rdi.Ainfo[nr].sfx.volume[slot], _rdi.Ainfo[nr].sfx.stereo[slot]);
	}
}

void Detail::stopSound(int16 slot) {
	g_engine->_sound->stopSound(slot);
}

int16 Detail::maus_vector(int16 x, int16 y) {
	int16 i = -1;
	for (int16 j = 0; (j < (MAX_M_ITEMS - 1) << 2) && (i == -1); j += 4) {
		if (_rdi.mvect[j] != -1) {
			if ((x >= _rdi.mvect[j]) && (x <= _rdi.mvect[j + 2]) &&
			        (y >= _rdi.mvect[j + 1]) && (y <= _rdi.mvect[j + 3]))
				i = j / 4;
		}
	}

	return i;
}

int16 Detail::get_ani_status(int16 det_nr) {
	return (_rdi.Ainfo[det_nr].start_flag > 0) ? 1 : 0;
}

SprInfo Detail::plot_detail_sprite(int16 scrx, int16 scry, int16 det_nr, int16 spr_nr, int16 mode) {
	if (det_nr >= MAXDETAILS)
		det_nr = MAXDETAILS - 1;
	AniDetailInfo *adiptr = &_rdi.Ainfo[det_nr];
	if (spr_nr < adiptr->start_ani)
		spr_nr = adiptr->start_ani;
	if (spr_nr > adiptr->end_ani)
		spr_nr = adiptr->end_ani - 1;
	int16 *Cxy = &_rdi.dptr->correction[spr_nr << 1];
	int16 *Xy = (int16 *)_rdi.dptr->image[spr_nr];
	_sprInfo._image = _rdi.dptr->image[spr_nr];
	_sprInfo._x = adiptr->x + Cxy[0] - scrx;
	_sprInfo._y = adiptr->y + Cxy[1] - scry;
	_sprInfo.X1 = _sprInfo._x + Xy[0];
	_sprInfo.Y1 = _sprInfo._y + Xy[1];
	if (mode)
		_G(out)->spriteSet(_sprInfo._image, _sprInfo._x, _sprInfo._y, 0);

	Sound *sound = g_engine->_sound;

	for (int16 k = 0; k < MAX_SOUNDS; k++) {
		int16 soundEffect = adiptr->sfx.sound_index[k];
		if (soundEffect != -1) {
			if (adiptr->sfx.sound_start[k] == spr_nr) {
				const uint channel = adiptr->sfx.channel[k] & 7;
				sound->playSound(soundEffect, channel, adiptr->sfx.repeats[k], adiptr->sfx.volume[k], adiptr->sfx.stereo[k]);
			}
		}
	}

	return _sprInfo;
}

void Detail::set_global_delay(int16 delay) {
	_globalDelay = delay - 2;
}

void Detail::calc_zoom_kor(int16 *kx, int16 *ky, int16 xzoom, int16 yzoom) {
	float tmpx = (float)(((float)*kx / 100.0) * ((float)xzoom));
	float tmpy = (float)(((float)*ky / 100.0) * ((float)yzoom));

	float tmpx1 = tmpx - (int16)tmpx;
	if (fabs(tmpx1) > 0.5)
		++tmpx;
	float tmpy1 = tmpy - (int16)tmpy;
	if (fabs(tmpy1) > 0.5)
		++tmpy;
	*kx += (int16)tmpx;
	*ky += (int16)tmpy;
}

int16 Detail::mouse_on_detail(int16 mouse_x, int16 mouse_y, int16 scrx, int16 scry) {
	int16 ok = false;
	int16 detail_nr = -1;
	for (int16 i = 0; i < MAXDETAILS && !ok; i++) {
		AniDetailInfo *adiptr = &_rdi.Ainfo[i];
		if ((adiptr->start_flag) && (adiptr->start_ani != -1) && (adiptr->end_ani != -1)) {
			int16 sprnr = adiptr->ani_count;
			int16 *Cxy = _rdi.dptr->correction + (sprnr << 1);
			int16 x = adiptr->x + Cxy[0] - scrx;
			int16 y = adiptr->y + Cxy[1] - scry;
			int16 *Xy;
			if (adiptr->load_flag == 1) {
				Xy = (int16 *)_tafLoadBuffer;
			} else {
				Xy = (int16 *)_rdi.dptr->image[sprnr];
			}
			if (mouse_x >= x && mouse_x <= x + Xy[0] &&
			        mouse_y >= y && mouse_y <= y + Xy[1]) {
				ok = true;
				detail_nr = i;
			}
		}
	}

	return detail_nr;
}

void Detail::set_taf_ani_mem(byte *load_area) {
	_tafLoadBuffer = load_area;
	_directTafAni = ON;
}

void Detail::load_taf_ani_sprite(int16 nr) {
	SpriteResource *res = new SpriteResource(_tafName);
	res->getSpriteData(nr, &_tafLoadBuffer, false);
	delete res;
}

} // namespace Chewy
