using Cryville.Common.Math;
using System;

namespace Cryville.Crtr.Extensions {
	public abstract class TimingModel {
		public double Time { get; protected set; }
		public double BeatTime { get; protected set; }
		public BeatTime FractionalBeatTime { get; protected set; }
		double m_bpm;
		public double BPM { get { return m_bpm; } set { m_bpm = value; } }
		public double BeatLength { get { return 60 / m_bpm; } set { m_bpm = 60 / value; } }
		public TimingModel(double offset) {
			Time = offset;
			FractionalBeatTime = new BeatTime(0, 0, 1);
		}
	}
	public class FractionalBeatTimeTimingModel : TimingModel {
		public FractionalBeatTimeTimingModel(double offset = 0) : base(offset) { }
		public void ForwardTo(BeatTime t) {
			if (t == FractionalBeatTime) return;
			if (BPM == 0) throw new InvalidOperationException("BPM not determined");
			FractionalBeatTime = t;
			var nt = t.Decimal;
			Time += (nt - BeatTime) / BPM * 60;
			BeatTime = nt;
		}
	}
	public class BeatTimeTimingModel : TimingModel {
		public BeatTimeTimingModel(double offset = 0) : base(offset) { }
		public void ForwardTo(double t) {
			if (t == BeatTime) return;
			if (BPM == 0) throw new InvalidOperationException("BPM not determined");
			Time += (t - BeatTime) / BPM * 60;
			BeatTime = t;
			FractionalBeatTime = ToBeatTime(t);
		}
		static BeatTime ToBeatTime(double beat, double error = 1e-4) {
			int i, n, d;
			FractionUtils.ToFraction(beat, error, out n, out d);
			i = n / d; n %= d;
			return new BeatTime(i, n, d);
		}
	}
	public class TimeTimingModel : TimingModel {
		public TimeTimingModel(double offset = 0) : base(offset) { }
		public void ForwardTo(double t) {
			if (t == Time) return;
			if (BPM == 0) throw new InvalidOperationException("BPM not determined");
			BeatTime += (t - Time) * BPM / 60;
			int n, d;
			FractionUtils.ToFraction(BeatTime, 1f / 48 / BPM * 60, out n, out d);
			FractionalBeatTime = new BeatTime(n, d);
			Time = t;
		}
	}
}
