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

#include "common/stream.h"

namespace BladeRunner {

Fog::Fog() {
	_frameCount         = 0;
	_animatedParameters = 0;
	_fogDensity         = 0.0f;
	_animationData      = nullptr;
	_m11ptr             = nullptr;
	_m12ptr             = nullptr;
	_m13ptr             = nullptr;
	_m14ptr             = nullptr;
	_m21ptr             = nullptr;
	_m22ptr             = nullptr;
	_m23ptr             = nullptr;
	_m24ptr             = nullptr;
	_m31ptr             = nullptr;
	_m32ptr             = nullptr;
	_m33ptr             = nullptr;
	_m34ptr             = nullptr;
	_next               = nullptr;
}

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

int Fog::readCommon(Common::ReadStream *stream) {
	int offset = stream->readUint32LE();
	char buf[20];
	stream->read(buf, sizeof(buf));
	_name = buf;
	_fogColor.r = stream->readFloatLE();
	_fogColor.g = stream->readFloatLE();
	_fogColor.b = stream->readFloatLE();
	_fogDensity = stream->readFloatLE();
	return offset;
}

void Fog::readAnimationData(Common::ReadStream *stream, int size) {
	_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);

	setupFrame(0);
}

void Fog::reset() {
}

void Fog::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);
	_inverted = invertMatrix(_matrix);
}

void FogSphere::read(Common::ReadStream *stream, int frameCount) {
	_frameCount = frameCount;
	int size = readCommon(stream);
	float radius = stream->readFloatLE();
	_radius_sq = radius * radius;
	readAnimationData(stream, size - 52);
}

void FogSphere::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
	*coeficient = 0.0f;

	// Ray - sphere intersection, where sphere center is always at 0, 0, 0 as everything else transformed by the fog matrix.
	// Quadratic formula can and was simplified because rayDirection is normalized and hence a = 1.
	// Explained on wikipedia https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection

	// There is also alternative approach which will end-up with this formula where plane is created from ray origin, ray destination
	// and sphere center, then there is only need to solve two right triangles.
	// Explained in book Andrew S. Glassner (1995), Graphics Gems I (p. 388-389)

	Vector3 rayOrigin = _matrix * position;
	Vector3 rayDestination = _matrix * viewPosition;
	Vector3 rayDirection = (rayDestination - rayOrigin).normalize();

	float b = Vector3::dot(rayDirection, rayOrigin);
	float c = Vector3::dot(rayOrigin, rayOrigin) - _radius_sq;
	float d = b * b - c;

	if (d >= 0.0f) { // there is an interstection between ray and the sphere
		float sqrt_d = sqrt(d);

		Vector3 intersection1 = rayOrigin + (-b - sqrt_d) * rayDirection;
		Vector3 intersection2 = rayOrigin + (-b + sqrt_d) * rayDirection;

		Vector3 intersection1World = _inverted * intersection1;
		Vector3 intersection2World = _inverted * intersection2;

		float intersection1Distance = (intersection1World - position).length();
		float intersection2Distance = (intersection2World - position).length();

		float distance = (viewPosition - position).length();

		if (intersection1Distance < 0.0f) {
			intersection1Distance = 0.0f;
		}
		if (intersection2Distance > distance) {
			intersection2Distance = distance;
		}
		if (intersection2Distance >= intersection1Distance) {
			*coeficient = intersection2Distance - intersection1Distance;
		}
	}
}

void FogCone::read(Common::ReadStream *stream, int frameCount) {
	_frameCount = frameCount;
	int size = readCommon(stream);
	float coneAngle = stream->readFloatLE();
	float tan_coneAngle = tan(coneAngle);

	_cos_coneAngle = cos(coneAngle);
	_tan_coneAngle_sq = tan_coneAngle * tan_coneAngle;

	readAnimationData(stream, size - 52);
}

void FogCone::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
	*coeficient = 0.0f;

	// ray - cone intersection, cone vertex V lies at (0,0,0) and direction v = (0,0,-1)
	// The algorithm looks like from book Alan W. Paeth (1995), Graphics Gems V (p. 228-230)

	Vector3 positionT = _matrix * position;
	Vector3 viewPositionT = _matrix * viewPosition;

	Vector3 v(0.0f, 0.0f, -1.0f);

	Vector3 planeNormal = Vector3::cross(positionT, viewPositionT).normalize();

	if (planeNormal.x != 0.0f || planeNormal.y != 0.0f || planeNormal.z != 0.0f) {

		if (planeNormal.z < 0.0f) {
			planeNormal = -1.0f * planeNormal;
		}

		float cosTheta = sqrt(1.0f - Vector3::dot(planeNormal, v) * Vector3::dot(planeNormal, v));

		if (cosTheta > _cos_coneAngle) {
			Vector3 u = Vector3::cross(v, planeNormal).normalize();
			Vector3 w = Vector3::cross(u, v).normalize();

			float tanTheta = sqrt(1.0f - cosTheta * cosTheta) / cosTheta;

			Vector3 temp1 = tanTheta * w;
			Vector3 temp2 = sqrt(_tan_coneAngle_sq - tanTheta * tanTheta) * u;

			Vector3 delta1 = v + temp1 - temp2;
			Vector3 delta2 = v + temp1 + temp2;

			Vector3 d = viewPositionT - positionT;
			Vector3 vecVD = -1.0f * positionT;

			Vector3 crossddelta1 = Vector3::cross(d, delta1);
			Vector3 crossddelta2 = Vector3::cross(d, delta2);

			float r1 = Vector3::dot(Vector3::cross(vecVD, delta1), crossddelta1) / Vector3::dot(crossddelta1, crossddelta1);
			float r2 = Vector3::dot(Vector3::cross(vecVD, delta2), crossddelta2) / Vector3::dot(crossddelta2, crossddelta2);

			if (r2 < r1) {
				float temp = r1;
				r1 = r2;
				r2 = temp;
			}

			if (r1 <= 1.0f && r2 >= 0.0f) {
				if (r1 < 0.0f) {
					r1 = 0.0;
				}
				if (r2 > 1.0f) {
					r2 = 1.0;
				}

				Vector3 intersection1 = positionT + (r1 * d);
				Vector3 intersection1World = _inverted * intersection1;

				Vector3 intersection2 = positionT + (r2 * d);
				Vector3 intersection2World = _inverted * intersection2;

				*coeficient = (intersection2World - intersection1World).length();
			}
		}
	}
}

void FogBox::read(Common::ReadStream *stream, int frameCount) {
	_frameCount = frameCount;
	int size = readCommon(stream);
	_size.x = stream->readFloatLE();
	_size.y = stream->readFloatLE();
	_size.z = stream->readFloatLE();
	readAnimationData(stream, size - 60);
}

void FogBox::calculateCoeficient(Vector3 position, Vector3 viewPosition, float *coeficient) {
	*coeficient = 0.0f;

	// line - box intersection, where everything is rotated to box orientation by the fog matrix

	Vector3 point1 = _matrix * position;
	Vector3 point2 = _matrix * viewPosition;

	Vector3 intersection1 = point1;
	Vector3 intersection2 = point2;

	Vector3 direction = point2 - point1;

	// clip X
	float minX = -(_size.x * 0.5f);
	if (point1.x < minX) {
		if (point2.x < minX) {
			return;
		}
		float scale = (minX - point1.x) / direction.x;
		intersection1 = point1 + scale * direction;
	} else if (point2.x < minX) {
		float scale = (minX - point2.x) / direction.x;
		intersection2 = point2 + scale * direction;
	}

	float maxX = _size.x * 0.5f;
	if (intersection1.x > maxX ) {
		if (intersection2.x > maxX) {
			return;
		}
		float scale = (maxX - intersection1.x) / direction.x;
		intersection1 = intersection1 + scale * direction;
	} else if (intersection2.x > maxX) {
		float scale = (maxX - intersection2.x) / direction.x;
		intersection2 = intersection2 + scale * direction;
	}

	// clip Y
	float minY = -(_size.y * 0.5f);
	if (intersection1.y < minY) {
		if (intersection2.y < minY) {
			return;
		}
		float scale = (minY - intersection1.y) / direction.y;
		intersection1 = intersection1 + scale * direction;
	} else if (intersection2.y < minY) {
		float scale = (minY - intersection2.y) / direction.y;
		intersection2 = intersection2 + scale * direction;
	}

	float maxY = _size.y * 0.5f;
	if (intersection1.y > maxY) {
		if (intersection2.y > maxY) {
			return;
		}
		float scale = (maxY - intersection1.y) / direction.y;
		intersection1 = intersection1 + scale * direction;
	} else if (intersection2.y > maxY) {
		float scale = (maxY - intersection2.y) / direction.y;
		intersection2 = intersection2 + scale * direction;
	}

	// clip Z
	if (intersection1.z < 0.0f) {
		if (intersection2.z < 0.0f) {
			return;
		}
		float scale = -intersection1.z / direction.z;
		intersection1 = intersection1 + scale * direction;
	} else if (intersection2.z < 0.0f) {
		float scale = -intersection2.z / direction.z;
		intersection2 = intersection2 + scale * direction;
	}

	if (intersection1.z > _size.z) {
		if (intersection2.z > _size.z) {
			return;
		}
		float scale = (_size.z - intersection1.z) / direction.z;
		intersection1 = intersection1 + scale * direction;
	} else if (intersection2.z > _size.z) {
		float scale = (_size.z - intersection2.z) / direction.z;
		intersection2 = intersection2 + scale * direction;
	}

	Vector3 intersection1World = _inverted * intersection1;
	Vector3 intersection2World = _inverted * intersection2;

	*coeficient = (intersection2World - intersection1World).length();
}
} // End of namespace BladeRunner
