/* 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 "bladerunner/light.h"

#include "common/util.h"

namespace BladeRunner {

Light::Light() {
	_frameCount         = 0;
	_animated           = 0;
	_animationData      = nullptr;
	_animatedParameters = 0;
	_falloffStart       = 0.0f;
	_falloffEnd         = 0.0f;
	_angleStart         = 0.0f;
	_angleEnd           = 0.0f;
	_m11ptr             = nullptr;
	_m12ptr             = nullptr;
	_m13ptr             = nullptr;
	_m14ptr             = nullptr;
	_m21ptr             = nullptr;
	_m22ptr             = nullptr;
	_m23ptr             = nullptr;
	_m24ptr             = nullptr;
	_m31ptr             = nullptr;
	_m32ptr             = nullptr;
	_m33ptr             = nullptr;
	_m34ptr             = nullptr;
	_colorRPtr          = nullptr;
	_colorGPtr          = nullptr;
	_colorBPtr          = nullptr;
	_falloffStartPtr    = nullptr;
	_falloffEndPtr      = nullptr;
	_angleStartPtr      = nullptr;
	_angleEndPtr        = nullptr;
}

Light::~Light() {
	if (_animationData != nullptr) {
		delete[] _animationData;
	}
}

void Light::read(Common::ReadStream *stream, int frameCount, int frame, int animated) {
	_frameCount = frameCount;
	_animated = animated;

	int size = stream->readUint32LE();
	size = size - 32;

	char buf[20];
	stream->read(buf, sizeof(buf));
	_name = buf;

	_animatedParameters = stream->readUint32LE();

	if (_animationData != nullptr) {
		delete[] _animationData;
	}
	int floatCount = size / 4;
	_animationData = new float[floatCount];
	for (int i = 0; i < floatCount; i++) {
		_animationData[i] = stream->readFloatLE();
	}

	_m11ptr          = _animationData;
	_m12ptr          = _m11ptr          + ((_animatedParameters &     0x1) ? frameCount : 1);
	_m13ptr          = _m12ptr          + ((_animatedParameters &     0x2) ? frameCount : 1);
	_m14ptr          = _m13ptr          + ((_animatedParameters &     0x4) ? frameCount : 1);
	_m21ptr          = _m14ptr          + ((_animatedParameters &     0x8) ? frameCount : 1);
	_m22ptr          = _m21ptr          + ((_animatedParameters &    0x10) ? frameCount : 1);
	_m23ptr          = _m22ptr          + ((_animatedParameters &    0x20) ? frameCount : 1);
	_m24ptr          = _m23ptr          + ((_animatedParameters &    0x40) ? frameCount : 1);
	_m31ptr          = _m24ptr          + ((_animatedParameters &    0x80) ? frameCount : 1);
	_m32ptr          = _m31ptr          + ((_animatedParameters &   0x100) ? frameCount : 1);
	_m33ptr          = _m32ptr          + ((_animatedParameters &   0x200) ? frameCount : 1);
	_m34ptr          = _m33ptr          + ((_animatedParameters &   0x400) ? frameCount : 1);
	_colorRPtr       = _m34ptr          + ((_animatedParameters &   0x800) ? frameCount : 1);
	_colorGPtr       = _colorRPtr       + ((_animatedParameters &  0x1000) ? frameCount : 1);
	_colorBPtr       = _colorGPtr       + ((_animatedParameters &  0x2000) ? frameCount : 1);
	_falloffStartPtr = _colorBPtr       + ((_animatedParameters &  0x4000) ? frameCount : 1);
	_falloffEndPtr   = _falloffStartPtr + ((_animatedParameters &  0x8000) ? frameCount : 1);
	_angleStartPtr   = _falloffEndPtr   + ((_animatedParameters & 0x10000) ? frameCount : 1);
	_angleEndPtr     = _angleStartPtr   + ((_animatedParameters & 0x20000) ? frameCount : 1);

	setupFrame(frame);
}

void Light::readVqa(Common::ReadStream *stream, int frameCount, int frame, int animated) {
	_frameCount = frameCount;
	_animated = animated;

	_animatedParameters = stream->readUint32LE();

	int size = stream->readUint32LE();

	if (_animationData != nullptr) {
		delete[] _animationData;
	}

	int floatCount = size / 4;
	_animationData = new float[floatCount];
	for (int i = 0; i < floatCount; i++) {
		_animationData[i] = stream->readFloatLE();
	}

	_m11ptr          = _animationData;
	_m12ptr          = _m11ptr          + ((_animatedParameters &     0x1) ? frameCount : 1);
	_m13ptr          = _m12ptr          + ((_animatedParameters &     0x2) ? frameCount : 1);
	_m14ptr          = _m13ptr          + ((_animatedParameters &     0x4) ? frameCount : 1);
	_m21ptr          = _m14ptr          + ((_animatedParameters &     0x8) ? frameCount : 1);
	_m22ptr          = _m21ptr          + ((_animatedParameters &    0x10) ? frameCount : 1);
	_m23ptr          = _m22ptr          + ((_animatedParameters &    0x20) ? frameCount : 1);
	_m24ptr          = _m23ptr          + ((_animatedParameters &    0x40) ? frameCount : 1);
	_m31ptr          = _m24ptr          + ((_animatedParameters &    0x80) ? frameCount : 1);
	_m32ptr          = _m31ptr          + ((_animatedParameters &   0x100) ? frameCount : 1);
	_m33ptr          = _m32ptr          + ((_animatedParameters &   0x200) ? frameCount : 1);
	_m34ptr          = _m33ptr          + ((_animatedParameters &   0x400) ? frameCount : 1);
	_colorRPtr       = _m34ptr          + ((_animatedParameters &   0x800) ? frameCount : 1);
	_colorGPtr       = _colorRPtr       + ((_animatedParameters &  0x1000) ? frameCount : 1);
	_colorBPtr       = _colorGPtr       + ((_animatedParameters &  0x2000) ? frameCount : 1);
	_falloffStartPtr = _colorBPtr       + ((_animatedParameters &  0x4000) ? frameCount : 1);
	_falloffEndPtr   = _falloffStartPtr + ((_animatedParameters &  0x8000) ? frameCount : 1);
	_angleStartPtr   = _falloffEndPtr   + ((_animatedParameters & 0x10000) ? frameCount : 1);
	_angleEndPtr     = _angleStartPtr   + ((_animatedParameters & 0x20000) ? frameCount : 1);

	setupFrame(frame);
}

void Light::setupFrame(int frame) {
	int offset = frame % _frameCount;
	_matrix._m[0][0] = ((_animatedParameters &     0x1) ? _m11ptr[offset]          : *_m11ptr);
	_matrix._m[0][1] = ((_animatedParameters &     0x2) ? _m12ptr[offset]          : *_m12ptr);
	_matrix._m[0][2] = ((_animatedParameters &     0x4) ? _m13ptr[offset]          : *_m13ptr);
	_matrix._m[0][3] = ((_animatedParameters &     0x8) ? _m14ptr[offset]          : *_m14ptr);
	_matrix._m[1][0] = ((_animatedParameters &    0x10) ? _m21ptr[offset]          : *_m21ptr);
	_matrix._m[1][1] = ((_animatedParameters &    0x20) ? _m22ptr[offset]          : *_m22ptr);
	_matrix._m[1][2] = ((_animatedParameters &    0x40) ? _m23ptr[offset]          : *_m23ptr);
	_matrix._m[1][3] = ((_animatedParameters &    0x80) ? _m24ptr[offset]          : *_m24ptr);
	_matrix._m[2][0] = ((_animatedParameters &   0x100) ? _m31ptr[offset]          : *_m31ptr);
	_matrix._m[2][1] = ((_animatedParameters &   0x200) ? _m32ptr[offset]          : *_m32ptr);
	_matrix._m[2][2] = ((_animatedParameters &   0x400) ? _m33ptr[offset]          : *_m33ptr);
	_matrix._m[2][3] = ((_animatedParameters &   0x800) ? _m34ptr[offset]          : *_m34ptr);
	_color.r         = ((_animatedParameters &  0x1000) ? _colorRPtr[offset]       : *_colorRPtr);
	_color.g         = ((_animatedParameters &  0x2000) ? _colorGPtr[offset]       : *_colorGPtr);
	_color.b         = ((_animatedParameters &  0x4000) ? _colorBPtr[offset]       : *_colorBPtr);
	_falloffStart    = ((_animatedParameters &  0x8000) ? _falloffStartPtr[offset] : *_falloffStartPtr);
	_falloffEnd      = ((_animatedParameters & 0x10000) ? _falloffEndPtr[offset]   : *_falloffEndPtr);
	_angleStart      = ((_animatedParameters & 0x20000) ? _angleStartPtr[offset]   : *_angleStartPtr);
	_angleEnd        = ((_animatedParameters & 0x40000) ? _angleEndPtr[offset]     : *_angleEndPtr);
}

float Light::calculate(Vector3 start, Vector3 end) const {
	return calculateFalloutCoefficient(_matrix * start, _matrix * end, _falloffStart, _falloffEnd);
}

void Light::calculateColor(Color *outColor, Vector3 position) const {
	Vector3 positionT = _matrix * position;
	float att = attenuation(_falloffStart, _falloffEnd, positionT.length());
	outColor->r = _color.r * att;
	outColor->g = _color.g * att;
	outColor->b = _color.b * att;
}

float Light::calculateFalloutCoefficient(Vector3 start, Vector3 end, float falloffStart, float falloffEnd) const {
	if (falloffEnd == 0.0f) {
		return 1.0e30f;
	}

	if (falloffStart * falloffStart >= start.length() && falloffStart * falloffStart >= end.length()) {
		return 1.0e30f;
	}

	float diff = (end - start).length();
	float v31 = 0.0f;
	if (diff != 0.0f) {
		Vector3 v27 = Vector3::cross(start, (end - start));
		v31 = v27.length() / diff;
	}

	if (v31 < falloffEnd) {
		return 1.0f / (1.0f - (v31 / falloffEnd));
	}
	return 1.0e30f;
}

float Light::attenuation(float min, float max, float distance) const {
	if (max == 0.0f) {
		return 1.0f;
	}
	if (min < max) {
		distance = CLIP(distance, min, max);
		float x = (max - distance) / (max - min);
		return x * x * (3.0f - 2.0f * x);
	}
	if (distance < min) {
		return 1.0f;
	}
	return 0.0f;
}

float Light1::calculate(Vector3 start, Vector3 end) const {
	start = _matrix * start;
	end = _matrix * end;

	float v40 = 0.0f;
	if (_falloffEnd != 0.0f) {
		v40 = calculateFalloutCoefficient(start, end, _falloffStart, _falloffEnd);
	}

	float v41 = atan2(sqrt(start.x * start.x + start.y * start.y), -start.z);
	float v42 = atan2(sqrt(end.x * end.x + end.y * end.y), -end.z);

	float v43;
	if ((_angleStart >= v41 && _angleStart >= v42) || (_angleEnd <= v41 && _angleEnd <= v42)) {
		v43 = 1.0e30f;
	} else {
		v43 = 2.0;
	}
	if (v43 < v40) {
		return v40;
	} else {
		return v43;
	}
}

void Light1::calculateColor(Color *outColor, Vector3 position) const {
	Vector3 positionT = _matrix * position;

	outColor->r = 0.0f;
	outColor->g = 0.0f;
	outColor->b = 0.0f;

	if (positionT.z < 0.0f) {
		float v12 = attenuation(_angleStart, _angleEnd, atan2(sqrt(positionT.x * positionT.x + positionT.y * positionT.y), -positionT.z));
		float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());

		outColor->r = v12 * v13 * _color.r;
		outColor->g = v12 * v13 * _color.g;
		outColor->b = v12 * v13 * _color.b;
	}
}

float Light2::calculate(Vector3 start, Vector3 end) const {
	start = _matrix * start;
	end = _matrix * end;

	float v54 = 0.0f;
	if (_falloffEnd != 0.0f) {
		v54 = calculateFalloutCoefficient(start, end, _falloffStart, _falloffEnd);
	}

	float v55 = atan2(fabs(start.x), -start.z);
	float v58 = atan2(fabs(start.y), -start.z);
	float v57 = atan2(fabs(end.x), -end.z);
	float v56 = atan2(fabs(end.y), -end.z);

	float v59;
	if ((_angleStart >= v55 && _angleStart >= v57 && _angleStart >= v58 && _angleStart >= v56) || (_angleEnd <= v55 && _angleEnd <= v57 && _angleEnd <= v58 && _angleEnd <= v56)) {
		v59 = 1.0e30f;
	} else {
		v59 = 2.0f;
	}
	if (v59 < v54) {
		return v54;
	} else {
		return v59;
	}
}

void Light2::calculateColor(Color *outColor, Vector3 position) const {
	Vector3 positionT = _matrix * position;

	outColor->r = 0.0f;
	outColor->g = 0.0f;
	outColor->b = 0.0f;

	if (positionT.z < 0.0f) {
		float v11 = attenuation(_angleStart, _angleEnd, atan2(fabs(positionT.y), -positionT.z));
		float v12 = attenuation(_angleStart, _angleEnd, atan2(fabs(positionT.x), -positionT.z));
		float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());

		outColor->r = v11 * v12 * v13 * _color.r;
		outColor->g = v11 * v12 * v13 * _color.g;
		outColor->b = v11 * v12 * v13 * _color.b;
	}
}

void Light3::calculateColor(Color *outColor, Vector3 position) const {
	Vector3 positionT = _matrix * position;

	outColor->r = 0.0f;
	outColor->g = 0.0f;
	outColor->b = 0.0f;

	if (positionT.z < 0.0f) {
		float v12 = attenuation(_angleStart, _angleEnd, sqrt(positionT.x * positionT.x + positionT.y * positionT.y));
		float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());

		outColor->r = v12 * v13 * _color.r;
		outColor->g = v12 * v13 * _color.g;
		outColor->b = v12 * v13 * _color.b;
	}
}

void Light4::calculateColor(Color *outColor, Vector3 position) const {
	Vector3 positionT = _matrix * position;

	outColor->r = 0.0f;
	outColor->g = 0.0f;
	outColor->b = 0.0f;

	if (positionT.z < 0.0f) {
		float v11 = attenuation(_angleStart, _angleEnd, fabs(positionT.y));
		float v12 = attenuation(_angleStart, _angleEnd, fabs(positionT.x));
		float v13 = attenuation(_falloffStart, _falloffEnd, positionT.length());

		outColor->r = v11 * v12 * v13 * _color.r;
		outColor->g = v11 * v12 * v13 * _color.g;
		outColor->b = v11 * v12 * v13 * _color.b;
	}
}

float LightAmbient::calculate(Vector3 start, Vector3 end) const {
	return 1.0e30f;
}

void LightAmbient::calculateColor(Color *outColor, Vector3 position) const {
	outColor->r = _color.r;
	outColor->g = _color.g;
	outColor->b = _color.b;
}

} // End of namespace BladeRunner