#pragma once
#include "../../stdafx.h"
#include "glLibMathMisc.h"

namespace glLib { namespace Math { namespace Fraction {

//TODO: Make methods that can be made const, const, everywhere in glLib

template <typename type_int> class fraction {
	//template <typename type_element, typename type_int2> friend class glLibMathMatrix;
	public:
		//Remember to change the constructor, = operator, and any copy constructor
		type_int numerator;
		type_int denominator;
		char sign;
	private:
		bool reduced;

	public:
		fraction(void) {
			numerator = 0;
			denominator = 1;
			sign = 1;
			reduced = true;
		}
		fraction(type_int new_numerator, type_int new_denominator) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (new_denominator==0) throw GLLIB_ERROR_TYPE_ERROR("Cannot have a denominator of zero!");
			#endif

			numerator = abs(new_numerator);
			denominator = abs(new_denominator);
			sign = ::glLib::Math::Misc::sign(new_numerator,new_denominator);

			reduced = false;
		}
		~fraction(void) {}

		//More operators -> remember "reduced = false;" as applicable!

		fraction& operator =(const fraction& other) {
			sign = other.sign;
			numerator = other.numerator;
			denominator = other.denominator;
			reduced = other.reduced;
			return *this;
		}
		fraction& operator =(type_int other) {
			sign = 1;
			numerator = other;
			denominator = 1;
			reduced = true;
			return *this;
		}

		fraction& operator+=(const fraction& other) {
			type_int new_denom = denominator * other.denominator;
			numerator = numerator*other.denominator + other.numerator*denominator;
			denominator = new_denom;
			reduced = false;
			return *this;
		}
		fraction& operator-=(const fraction& other) {
			type_int new_denom = denominator * other.denominator;
			numerator = numerator*other.denominator - other.numerator*denominator;
			if (numerator<0) {
				numerator = -numerator;
				negate();
			}
			denominator = new_denom;
			reduced = false;
			return *this;
		}
		fraction& operator*=(const fraction& other) {
			numerator *= other.numerator;
			denominator *= other.denominator;
			reduced = (reduced&&other.reduced);
			return *this;
		}
		fraction& operator/=(const fraction& other) {
			numerator *= other.denominator; //multiply by reciprocal
			denominator *= other.numerator;
			reduced = false;
			return *this;
		}

		fraction& operator+=(const type_int& other) {
			numerator += other * denominator;
			reduced = false;
			return *this;
		}
		fraction& operator-=(const type_int& other) {
			numerator -= other * denominator;
			reduced = false;
			return *this;
		}
		fraction& operator*=(const type_int& other) {
			numerator *= other;
			reduced = false;
			return *this;
		}
		fraction& operator/=(const type_int& other) {
			denominator *= other; //scale the denominator instead of the numerator
			reduced = false;
			return *this;
		}

		template <typename type> const fraction operator+(const type& other) const {
			//Expressed as +=, so "reduced = false;" not necessary
			fraction result = *this;
			result += other;
			return result;
		}
		template <typename type> const fraction operator-(const type& other) const {
			//Expressed as -=, so "reduced = false;" not necessary
			fraction result = *this;
			result -= other;
			return result;
		}
		template <typename type> const fraction operator*(const type& other) const {
			//Expressed as *=, so "reduced = false;" not necessary
			fraction result = *this;
			result *= other;
			return result;
		}
		template <typename type> const fraction operator/(const type& other) const {
			//Expressed as /=, so "reduced = false;" not necessary
			fraction result = *this;
			result /= other;
			return result;
		}

		fraction& operator^=(const type_int& power) {
			numerator = pow(numerator,power);
			denominator = pow(denominator,power);
			reduced = false;
			return *this;
		}
		const fraction operator^(const type_int& power) const {
			//Expressed as ^=, so "reduced = false;" not necessary
			fraction result = *this;
			result ^= power;
			return result;
		}

		const fraction operator-(void) const {
			fraction result = *this;
			result.negate();
			return result;
		}

		bool operator >(const fraction &other) const { return (get_double() >other.get_double()); }
		bool operator <(const fraction &other) const { return (get_double() <other.get_double()); }
		bool operator>=(const fraction &other) const { return (get_double()>=other.get_double()); }
		bool operator<=(const fraction &other) const { return (get_double()<=other.get_double()); }

		bool operator >(float other) const { return (get_float() >other); }
		bool operator <(float other) const { return (get_float() <other); }
		bool operator>=(float other) const { return (get_float()>=other); }
		bool operator<=(float other) const { return (get_float()<=other); }

		bool operator >(double other) const { return (get_double() >other); }
		bool operator <(double other) const { return (get_double() <other); }
		bool operator>=(double other) const { return (get_double()>=other); }
		bool operator<=(double other) const { return (get_double()<=other); }

		bool operator==(const fraction &other) const {
			fraction new_other = other; other.reduce();
			fraction new_self = *this; new_self.reduce();
			return ( (new_self.numerator*new_other.numerator) && (new_self.denominator==new_other.denominator) );
		}
		inline bool operator!=(const fraction &other) const {
			return !(*this==other);
		}

		//TODO: cache these?  Would be complicated . . .
		inline  float  get_float(void) const {
			return (float)(numerator) / (float)(denominator);
		}
		inline double get_double(void) const {
			return (double)(numerator) / (double)(denominator);
		}

		inline void negate(void) {
			//sign *= -1; //is slower
			sign = -sign;
		}

		void print(bool add_newline=true) const {
			//Using cout for its flexibility with integer types
			cout << "(";
			glLibInternal_print_numerator();
			cout << "/";
			glLibInternal_print_denominator();
			cout << ")";
			if (add_newline) printf("\n");
		}
		void reduce(void) {
			if (reduced) return;
			type_int factor = abs(::glLib::Math::Misc::gcd(numerator,denominator));
			numerator /= factor;
			denominator /= factor;
		}

	private:
		void   glLibInternal_print_numerator(void) const {
			cout << numerator;
		}
		void glLibInternal_print_denominator(void) const {
			cout << denominator;
		}
};

};};};