using UnityEngine;
using System;
using System.Runtime.Serialization;

[SerializableAttribute, DataContract]
public struct InfiniteFloat {

	[DataMember]
	private float value;
	public float Value {
		get{ return value; }
	}
	[DataMember]
	private long index;
	public long Index {
		get{ return index; }
	}

	public InfiniteFloat(float value, long index = 0){
		value *= Mathf.Pow(10.0f, index % 3);
		index -= index % 3;
		ToShorten(ref value, ref index);
		this.value = value;
		this.index = index;
	}
	public InfiniteFloat(ulong value){
		this.index = 0;
		while(value > 1000000000){
			value /= 1000;
			this.index += 3;
		}
		this.value = (float)value;
		ToShorten(ref this.value, ref this.index);
	}

	public static InfiniteFloat Zero {
		get{ return CreateInstance(0); }
	}
	public static InfiniteFloat One {
		get{ return CreateInstance(1); }
	}

	public void Reset(){
		value = 0.0f;
		index = 0;
	}

	public static InfiniteFloat operator+(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index > i2.Index){
			float value = i1.Value + i2.Value * Mathf.Pow(10.0f, i2.Index - i1.Index);
			return CreateInstance(value, i1.Index);
		}else if(i1.Index < i2.Index){
			float value = i1.Value * Mathf.Pow(10.0f, i1.Index - i2.Index) + i2.Value;
			return CreateInstance(value, i2.Index);
		}else{
			return CreateInstance(i1.Value + i2.Value, i1.Index);
		}
	}
	public static InfiniteFloat operator-(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index > i2.Index){
			float value = i1.Value - i2.Value * Mathf.Pow(10.0f, i2.Index - i1.Index);
			return CreateInstance(value, i1.Index);
		}else if(i1.Index < i2.Index){
			float value = i1.Value * Mathf.Pow(10.0f, i1.Index - i2.Index) - i2.Value;
			return CreateInstance(value, i2.Index);
		}else{
			return CreateInstance(i1.Value - i2.Value, i1.Index);
		}
	}
	public static InfiniteFloat operator*(InfiniteFloat i1, InfiniteFloat i2){
		return CreateInstance(i1.Value * i2.Value, i1.Index + i2.Index);
	}
	public static InfiniteFloat operator/(InfiniteFloat i1, InfiniteFloat i2){
		return CreateInstance(i1.Value / i2.Value, i1.Index - i2.Index);
	}
	public static bool operator>(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index == i2.Index){
			return i1.Value > i2.Value;
		}else if(i1.Index > i2.Index){
			return true;
		}else {
			return false;
		}
	}
	public static bool operator>=(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index == i2.Index){
			return i1.Value >= i2.Value;
		}else if(i1.Index > i2.Index){
			return true;
		}else {
			return false;
		}
	}
	public static bool operator<(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index == i2.Index){
			return i1.Value < i2.Value;
		}else if(i1.Index < i2.Index){
			return true;
		}else {
			return false;
		}
	}
	public static bool operator<=(InfiniteFloat i1, InfiniteFloat i2){
		if(i1.Index == i2.Index){
			return i1.Value <= i2.Value;
		}else if(i1.Index < i2.Index){
			return true;
		}else {
			return false;
		}
	}

	public static InfiniteFloat operator+(InfiniteFloat i, float f){
		float value = i.Value + f * Mathf.Pow(10.0f, -i.Index);
		return CreateInstance(value, i.Index);
	}
	public static InfiniteFloat operator+(float f, InfiniteFloat i){
		float value = f * Mathf.Pow(10.0f, -i.Index) + i.Value;
		return CreateInstance(value, i.Index);
	}
	public static InfiniteFloat operator-(InfiniteFloat i, float f){
		float value = i.Value - f * Mathf.Pow(10.0f, -i.Index);
		return CreateInstance(value, i.Index);
	}
	public static InfiniteFloat operator-(float f, InfiniteFloat i){
		float value = f * Mathf.Pow(10.0f, -i.Index) - i.Value;
		return CreateInstance(value, i.Index);
	}
	public static InfiniteFloat operator*(InfiniteFloat i, float f){
		return CreateInstance(i.Value * f, i.Index);
	}
	public static InfiniteFloat operator*(float f, InfiniteFloat i){
		return CreateInstance(f * i.Value, i.Index);
	}
	public static InfiniteFloat operator/(InfiniteFloat i, float f){
		return CreateInstance(i.Value / f, i.Index);
	}
	public static InfiniteFloat operator/(float f, InfiniteFloat i){
		return CreateInstance(f / i.Value, -i.Index);
	}
	public static bool operator>(InfiniteFloat i, float f){
		return i.Value > f * Mathf.Pow(10.0f, -i.Index);
	}
	public static bool operator>(float f, InfiniteFloat i){
		return f * Mathf.Pow(10.0f, -i.Index) > i.Value;
	}
	public static bool operator>=(InfiniteFloat i, float f){
		return i.Value >= f * Mathf.Pow(10.0f, -i.Index);
	}
	public static bool operator>=(float f, InfiniteFloat i){
		return f * Mathf.Pow(10.0f, -i.Index) >= i.Value;
	}
	public static bool operator<(InfiniteFloat i, float f){
		return i.Value < f * Mathf.Pow(10.0f, -i.Index);
	}
	public static bool operator<(float f, InfiniteFloat i){
		return f * Mathf.Pow(10.0f, -i.Index) < i.Value;
	}
	public static bool operator<=(InfiniteFloat i, float f){
		return i.Value <= f * Mathf.Pow(10.0f, -i.Index);
	}
	public static bool operator<=(float f, InfiniteFloat i){
		return f * Mathf.Pow(10.0f, -i.Index) <= i.Value;
	}

    public static InfiniteFloat operator*(InfiniteFloat i, double d){
        long dIndex = 0;
        ToShorten(ref d, ref dIndex);
        return CreateInstance(i.Value * (float)d, i.Index + dIndex);
    }
    public static InfiniteFloat operator*(double d, InfiniteFloat i){
        long dIndex = 0;
        ToShorten(ref d, ref dIndex);
        return CreateInstance((float)d * i.Value, dIndex + i.Index);
    }
    public static InfiniteFloat operator/(InfiniteFloat i, double d){
        long dIndex = 0;
        ToShorten(ref d, ref dIndex);
        return CreateInstance(i.Value / (float)d, i.Index - dIndex);
    }
    public static InfiniteFloat operator/(double d, InfiniteFloat i){
        long dIndex = 0;
        ToShorten(ref d, ref dIndex);
        return CreateInstance((float)d / i.Value, dIndex - i.Index);
    }

	public InfiniteFloat Pow(int p){
		if(p > 0){
			InfiniteFloat result = CreateInstance(value, index);
			for(int i = 1; i < p; ++i){
				result *= this;
			}
			return result;
		}else if(p < 0){
			InfiniteFloat result = CreateInstance(value, index);
			for(int i = 1; i < -p; ++i){
				result *= this;
			}
			return 1.0f / result;
		}else{
			return One;
		}
	}
    public InfiniteFloat Pow(float p){
        return (this.Log() * p).Exp();
    }

    public InfiniteFloat Sqrt(){
        if(value < 0) return InfiniteFloat.Zero;
        if(index % 2 == 0){
            return CreateInstance(Mathf.Sqrt(value), index >> 1);
        }else{
            return CreateInstance(Mathf.Sqrt(value)) * CreateInstance(3.16227766017f, index >> 1);
        }
    }
    public InfiniteFloat Exp(){
        var result = One + this;
        // とりあえず50
        for(uint i = 2u; i <= 50u; ++i){
            result += this.Pow((int)i) / Factorial(i);
        }
        return result;
    }

    public InfiniteFloat Log(){
        // log(x) = a * 2 ^ n (1 <= a < 2)
        var a = this;
        int n = 0;
        while(a > 2.0f){
            a = a / 2.0f;
            ++n;
        }
        // log(x) = log(a * 2 ^ n)
        //        = log(a) + log(2 ^ n)
        //        = log(a) + n * log(2)
        var right = n * 0.693147180559f;
        // log(a) -> log(y + 1)
        var y = a - 1.0f;
        // log(y + 1) = y - (y ^ 2 / 2) + (y ^ 3 / 3) - (y ^ 4 / 4) + (y ^ 5 / 5)...
        var left = y;
        // とりあえず50
        for(int i = 2; i < 50; ++i){
            if(i % 2 == 0){
                left -= y.Pow(i) / i;
            }else{
                left += y.Pow(i) / i;
            }
        }
        return left + right;
    }

    public static InfiniteFloat Factorial(uint i){
        if(i == 0u){
            return One;
        }else{
            return i * Factorial(i - 1u);
        }
    }

	public InfiniteFloat Floor(){
		if(index < 6){
			return CreateInstance(Mathf.Floor(value * Mathf.Pow(10.0f, index)), 0);
		}else{
			return CreateInstance(value, index);
		}
	}
    public int FloorToInt(){
        InfiniteFloat v = Floor();
        return (int)(v.Value * Mathf.Pow(10.0f, v.Index));
    }

    private static void ToShorten(ref float value, ref long index){
        if(float.IsInfinity(value)){
            Debug.LogWarning("float value is Infinity.");
            value = 1.0f;
        }else if(float.IsNaN(value)){
            Debug.LogWarning("float value is NaN.");
            value = 1.0f;
        }
        if(index > 0){
            long rem = index % 3;
            if(rem > 0){
                value *= Mathf.Pow(10.0f, rem);
                index -= rem;
            }
        }
        if(value > 0.0f){
            while(value >= 1000.0f){
                value *= 0.001f;
                index += 3;
            }
            while(value >= 10.0f && index < 0){
                value *= 0.1f;
                ++index;
            }
            while(value < 1.0f && value > 0.0f && index > 0){
                value *= 1000.0f;
                index -= 3;
            }
            while(value < 1.0f && value > 0.0f && index <= 0){
                value *= 10.0f;
                --index;
            }
        }else if(value < 0.0f){
            while(value <= -1000.0f){
                value *= 0.001f;
                index += 3;
            }
            while(value <= -10.0f && index < 0){
                value *= 0.1f;
                ++index;
            }
            while(value > -1.0f && value < 0.0f && index > 0){
                value *= 1000.0f;
                index -= 3;
            }
            while(value > -1.0f && value < 0.0f && index <= 0){
                value *= 10.0f;
                --index;
            }
        }else{
            index = 0;
        }
        if(index > 0){
            long rem = index % 3;
            if(rem > 0){
                value *= Mathf.Pow(10.0f, rem);
                index -= rem;
            }
        }
    }
    private static void ToShorten(ref double value, ref long index){
        if(double.IsInfinity(value)){
            Debug.LogWarning("double value is Infinity.");
            value = 1.0;
        }else if(double.IsNaN(value)){
            Debug.LogWarning("double value is NaN.");
            value = 1.0;
        }
        if(value >= 0.0){
            while(value >= 1000.0){
                value *= 0.001;
                index += 3;
            }
            while(value < 1.0 && value > 0.0){
                value *= 10.0;
                --index;
            }
        }else{
            while(value <= -1000.0){
                value *= 0.001;
                index += 3;
            }
            while(value > -1.0 && value < 0.0){
                value *= 10.0;
                --index;
            }
        }
    }
	public static InfiniteFloat CreateInstance(float value, long index = 0){
		ToShorten(ref value, ref index);
		InfiniteFloat result;
		result.value = value;
		result.index = index;
		return result;
	}


    private static readonly string[] unit = new string[] { "", "K", "M", "B", "T", "Q" };
	public override string ToString(){
		return _ToString(unit, false, 1, 3);
	}
	public string ToString(string[] unit){
		return _ToString(unit, false, 1, 3);
	}
	public string ToString(uint decimalCount = 1, int minIndex = 3){
		return _ToString(unit, false, decimalCount, minIndex);
	}
	public string ToStringInt(uint decimalCount = 1, int minIndex = 3){
		return _ToString(unit, true, decimalCount, minIndex);
	}
	private string _ToString(string[] unit, bool isInt, uint decimalCount, int minIndex){
		float v = value;
		long i = index;
		while(v >= 10.0f || v <= -10.0f){
			v *= 0.1f;
			++i;
		}
		if(i >= minIndex){
			long u = index / 3;
			if(u < unit.Length){
				return string.Format("{0}{1}", value.Floor(decimalCount).ToString(string.Format("N{0}", decimalCount)), unit[u]);
			}else{
				return string.Format("{0}e{1}", v.Floor(decimalCount).ToString(string.Format("N{0}", decimalCount)), i);
			}
		}else if(index < 0){
			if(isInt){
				return 0.ToString();
			}else{
				return string.Format("{0:F2}e{1}", value.Floor(2), index);
			}
		}else{
			if(isInt){
				return string.Format("{0:N0}", (value * Mathf.Pow(10.0f, index)).Floor(1));
			}else{
				return (value * Mathf.Pow(10.0f, index)).Floor(decimalCount).ToString(string.Format("N{0}", decimalCount));
			}
		}
	}

    private static readonly string[] unitC = new string[] { "", "万", "億", "兆", "京", "垓", "秭", "穣", "溝", "澗", "正", "載", "極" };
	public string ToChineseNumerals(){
		return ToChineseNumerals(unitC, unitC[unitC.Length - 1]);
	}
	public string ToChineseNumerals(string[] unit, string lastUnit){
		return _ToChineseNumerals("{0:F1}", unit, lastUnit, false);
	}
	public string ToChineseNumeralsInt(){
		return ToChineseNumeralsInt(unitC, unitC[unitC.Length - 1]);
	}
	public string ToChineseNumeralsInt(string[] unit, string lastUnit){
		return _ToChineseNumerals("{0:F0}", unit, lastUnit, true);
	}
	private string _ToChineseNumerals(string zeroIndexFormat, string[] unit, string lastUnit, bool isInt){
		if(index > 0){
			long u = index / 4;
			long keta = index % 4;
			float v = value;
			while(v >= 10.0f || v <= -10.0f){
				v *= 0.1f;
				++keta;
			}
			while(keta >= 4){
				keta -= 4;
				++u;
			}
			if(u < unit.Length){
				if(u == 0){
					return string.Format(zeroIndexFormat, Mathf.Floor(v * Mathf.Pow(10.0f, keta) * 10.0f) * 0.1f);
				}else{
					return string.Format("{0:F1}{1}", Mathf.Floor(v * Mathf.Pow(10.0f, keta) * 10.0f) * 0.1f, unit[u]);
				}
			}else{
				v = value;
				long i = index;
				while(v >= 10.0f || v <= -10.0f){
					v *= 0.1f;
					++i;
				}
				i -= unitC.Length * 4 - 1; // lastUnitが指数表記出ない場合使用
				return string.Format("{0:F2}{1}+{2}", v.Floor(2), lastUnit, i);
			}
		}else if(index < 0){
			if(isInt){
				return string.Format(zeroIndexFormat, 0);
			}else{
				return string.Format("{0:F1}{1}+{2}", value.Floor(1), lastUnit, index);
			}
		}else{
			return string.Format(zeroIndexFormat, value.Floor(1));
		}
	}

	public float ToFloat(){
		return value * Mathf.Pow(10.0f, index);
	}

	public ulong ToULong(){
		if(index <= 6){
			return (ulong)(value * Mathf.Pow(10.0f, index));
		}else{
			ulong result = (ulong)(value * Mathf.Pow(10.0f, 6));
			for(int i = 0; i < index - 6; ++i){
				result *= 10;
			}
			return result;
		}
	}

	public static InfiniteFloat Lerp(InfiniteFloat a, InfiniteFloat b, float t){
		return a + (b - a) * t;
	}
}
