/*
 * Copyright 2015-2017 Michele "King_DuckZ" Santullo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "vectorwrapper/vectorwrapper.hpp"
#include "vectorwrapper/vector_cast.hpp"
#include <gtest/gtest.h>
#include <cstddef>
#include <array>
#include <iostream>

namespace {
	int gcd (int a, int b) {
		if ((a && b) == false)
			return 0;

		int r, q, uRet;

		do {
			uRet = b;

			q = a / b;
			r = a - q * b;

			a = b;
			b = r;
		} while (r);
		return uRet;
	}

	class Rational {
	public:
		Rational() = default;
		Rational (int num, int den) : m_n(num), m_d(den) {}
		Rational (int num) : m_n(num), m_d(1) {}
		Rational (const Rational&) = default;
		Rational (Rational&&) = default;
		~Rational() = default;

		Rational& operator= (const Rational&) = default;
		Rational& operator= (Rational&&) = default;
		bool operator== (const Rational& o) const { return m_n == o.m_n and m_d == o.m_d; }
		bool operator== (int o) const { return m_n == o and 1 == m_d; }

		operator float() const { return static_cast<float>(m_n) / static_cast<float>(m_d); }

		Rational& operator*= (const Rational& o) {
			m_n *= o.m_n; m_d *= o.m_d; simplify(); return *this;
		}
		Rational& operator/= (const Rational& o) {
			m_n *= o.m_d; m_d *= o.m_n; simplify(); return *this;
		}

		int num() const { return m_n; }
		int den() const { return m_d; }

	private:
		void simplify() { const int t = gcd(m_n, m_d); m_n /= t; m_d /= t; }

		int m_n, m_d;
	};

	bool operator== (int a, const Rational& b) {
		return a == b.num() and b.den() == 1;
	}

	Rational operator* (Rational a, const Rational& b) { a *= b; return a; }
	Rational operator/ (Rational a, const Rational& b) { a /= b; return a; }

	std::ostream& operator<< (std::ostream& os, const Rational& r) {
		os << r.num() << '/' << r.den();
		return os;
	}

	struct ThreeRationals {
		Rational a, b, c;
	};
} //unnamed namespace

namespace vwr {
	template <>
	struct VectorWrapperInfo<ThreeRationals> {
		enum { dimensions = 3 };
		typedef Rational scalar_type;

		enum {
			offset_x = offsetof(ThreeRationals, a),
			offset_y = offsetof(ThreeRationals, b),
			offset_z = offsetof(ThreeRationals, c)
		};
	};

	template <>
	struct VectorWrapperInfo<std::array<float, 3> > {
		enum { dimensions = 3 };
		typedef float scalar_type;

		static scalar_type& get_at (size_type i, std::array<float, 3>& v) {
			return v[i];
		}
	};
} //namespace vwr

namespace {
	typedef vwr::Vec<ThreeRationals> rvec3;
	typedef vwr::Vec<std::array<float, 3> > fvec3;
} //unnamed namespace

TEST(vwr, custom_type) {
	using vwr::vector_cast;

	rvec3 rational3(
		Rational(1),
		Rational(1, 2),
		Rational(1, 3)
	);

	EXPECT_EQ(1, rational3.x());
	EXPECT_EQ(Rational(1, 2), rational3.y());
	EXPECT_EQ(Rational(1, 3), rational3.z());

	fvec3 float3 = vector_cast<fvec3>(rational3);
	EXPECT_EQ(1.0f, float3.x());
	EXPECT_EQ(0.5f, float3.y());

	rational3 *= 4;
	EXPECT_EQ(4, rational3.x());
	EXPECT_EQ(2, rational3.y());
	EXPECT_EQ(Rational(4, 3), rational3.z());

	rational3 /= 2;
	EXPECT_EQ(2, rational3.x());
	EXPECT_EQ(1, rational3.y());
	EXPECT_EQ(Rational(2, 3), rational3.z());
}
