/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Math.hpp"

namespace OpenVulkano::Math
{
	class CameraIntrinsic
	{
	public:
		Math::Matrix3f_SIMD cameraMatrix;

		CameraIntrinsic() : CameraIntrinsic(Math::Matrix3f(0)) {}

		CameraIntrinsic(const Math::Matrix3f& camMat) : cameraMatrix(camMat)
		{}

		CameraIntrinsic(const Math::Matrix3f_SIMD& camMat) : cameraMatrix(camMat)
		{}

		CameraIntrinsic(float fx, float fy, float cx, float cy, float skew = 0)
			: cameraMatrix(fx, 0, 0, skew, fy, 0, cx, cy, 0)
		{}

		[[nodiscard]] CameraIntrinsic Scale(float scale) const
		{
			Math::Matrix3f scaled = cameraMatrix;
			scaled[0][0] *= scale;
			scaled[2][0] *= scale;
			scaled[1][1] *= scale;
			scaled[2][1] *= scale;
			return { scaled };
		}

		[[nodiscard]] CameraIntrinsic Scale(Math::Vector2f scale) const
		{
			Math::Matrix3f scaled = cameraMatrix;
			scaled[0][0] *= scale.x;
			scaled[2][0] *= scale.x;
			scaled[1][1] *= scale.y;
			scaled[2][1] *= scale.y;
			scaled[1][0] *= scale.x / scale.y; // Scale skew
			return { scaled };
		}

		[[nodiscard]] float Fx() const
		{
			return cameraMatrix[0][0];
		}

		[[nodiscard]] float Fy() const
		{
			return cameraMatrix[1][1];
		}

		[[nodiscard]] float Cx() const
		{
			return cameraMatrix[2][0];
		}

		[[nodiscard]] float Cy() const
		{
			return cameraMatrix[2][1];
		}

		[[nodiscard]] float Skew() const
		{
			return cameraMatrix[1][0];
		}

		[[nodiscard]] float PixelAspectRatio() const
		{
			return Fy() / Fx();
		}

		[[nodiscard]] float GetFovX(float imageWidth) const
		{
			return 2 * atanf((imageWidth / Fx()) * 0.5f);
		}

		[[nodiscard]] float GetFovY(float imageHeight) const
		{
			return 2 * atanf((imageHeight / Fy()) * 0.5f);
		}

		[[nodiscard]] Math::Vector3f ProjectTo3D(Math::Vector2i pixelCoordinates, float depth) const
		{
			return ProjectTo3D(Math::Vector2f(pixelCoordinates), depth);
		}

		[[nodiscard]] Math::Vector3f ProjectTo3D(Math::Vector2f pixelCoordinates, float depth) const
		{
			// TODO handle skew
			return {
					( pixelCoordinates.x - Cx()) * depth / Fx(),
					(-pixelCoordinates.y + Cy()) * depth / Fy(),
					-depth
			};
		}

		CameraIntrinsic operator * (const float scale) const
		{
			return Scale(scale);
		}

		CameraIntrinsic operator * (const Math::Vector2f& scale) const
		{
			return Scale(scale);
		}
	};

	class CameraIntrinsicWithResolution : public CameraIntrinsic
	{
		Math::Vector2i resolution;
	public:
		CameraIntrinsicWithResolution() : CameraIntrinsic(), resolution(0, 0) {}

		CameraIntrinsicWithResolution(const CameraIntrinsic& intrinsic, const Math::Vector2i resolution)
			: CameraIntrinsic(intrinsic), resolution(resolution)
		{}

		CameraIntrinsicWithResolution(const Math::Matrix3f& camMat, const Math::Vector2i resolution)
			: CameraIntrinsic(camMat), resolution(resolution)
		{}

		CameraIntrinsicWithResolution(const Math::Matrix3f_SIMD& camMat, const Math::Vector2i resolution)
			: CameraIntrinsic(camMat), resolution(resolution)
		{}

		CameraIntrinsicWithResolution(Math::Vector2i resolution, float fx, float fy, float cx, float cy, float skew = 0)
			: CameraIntrinsic(fx, fy, cx, cy, skew), resolution(resolution)
		{}

		[[nodiscard]] float GetFovX() const
		{
			return CameraIntrinsic::GetFovX(static_cast<float>(resolution.x));
		}

		[[nodiscard]] float GetFovY() const
		{
			return CameraIntrinsic::GetFovY(static_cast<float>(resolution.y));
		}

		[[nodiscard]] Math::Vector2i GetResolution() const
		{
			return resolution;
		}

		[[nodiscard]] CameraIntrinsicWithResolution GetForResolution(const Math::Vector2i newResolution) const
		{
			return { Scale(Math::Vector2f(newResolution) / Math::Vector2f(resolution)), resolution };
		}
	};
}
