using Cryville.Common;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using System.Reflection;
using RBeatTime = Cryville.Crtr.BeatTime;

namespace Cryville.Crtr {
	public abstract class PropOp : PdtOperator {
		internal PropOp() : base(1) { }
		public class Arbitrary : PropOp {
			public int Name { get; set; }
			protected override void Execute() {
				var op = GetOperand(0);
				var value = new byte[op.Length];
				op.CopyTo(value, 0);
				ChartPlayer.etor.ContextCascadeUpdate(Name, new PropSrc.Arbitrary(op.Type, value));
			}
		}
		public class Boolean : PropOp {
			readonly Action<bool> _cb;
			public Boolean(Action<bool> cb) { _cb = cb; }
			protected override void Execute() {
				_cb(GetOperand(0).AsNumber() > 0);
			}
		}
		public class Integer : PropOp {
			readonly Action<int> _cb;
			public Integer(Action<int> cb) { _cb = cb; }
			protected override void Execute() {
				_cb((int)GetOperand(0).AsNumber());
			}
		}
		public class Float : PropOp {
			readonly Action<float> _cb;
			public Float(Action<float> cb) { _cb = cb; }
			protected override void Execute() {
				_cb(GetOperand(0).AsNumber());
			}
		}
		public class String : PropOp {
			readonly Action<string> _cb;
			public String(Action<string> cb) { _cb = cb; }
			protected override void Execute() {
				_cb(GetOperand(0).AsString());
			}
		}
		public class Identifier : PropOp {
			readonly Action<int> _cb;
			public Identifier(Action<int> cb) { _cb = cb; }
			protected override void Execute() {
				_cb(GetOperand(0).AsIdentifier());
			}
		}
		public class Enum<T> : PropOp {
			readonly static Dictionary<int, int> _cache = new Dictionary<int, int>();
			readonly Action<T> _cb;
			public Enum(Action<T> cb) {
				if (!typeof(T).IsEnum)
					throw new ArgumentException("Type is not enum");
				var names = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);
				for (int i = 0; i < names.Length; i++)
					_cache[IdentifierManager.SharedInstance.Request(names[i].Name)] = Convert.ToInt32(names[i].GetValue(null));
				_cb = cb;
			}
			protected override void Execute() {
				int result = 0;
				for (int i = 0; i < LoadedOperandCount; i++)
					result |= _cache[GetOperand(0).AsIdentifier()];
				_cb((T)(object)result);
			}
		}
		public class BeatTime : PropOp {
			readonly Action<RBeatTime> _cb;
			public BeatTime(Action<RBeatTime> cb) { _cb = cb; }
			protected override unsafe void Execute() {
				var o = GetOperand(0);
				_cb(*(RBeatTime*)o.TrustedAsOfLength(sizeof(RBeatTime)));
			}
		}
		public class Vector2 : PropOp {
			readonly Action<UnityEngine.Vector2> _cb;
			public Vector2(Action<UnityEngine.Vector2> cb) { _cb = cb; }
			protected override unsafe void Execute() {
				var o = GetOperand(0);
				switch ((o.Length - 4) / 4) {
					case 0: // Number
					case 1: // Array[1]
						float num = o.AsNumber();
						_cb(new UnityEngine.Vector2(num, num));
						break;
					case 2:
						_cb(*(UnityEngine.Vector2*)o.TrustedAsOfLength(sizeof(UnityEngine.Vector2)));
						break;
					default:
						throw new InvalidOperationException("Invalid array size");
				}
			}
		}
		public class Vector3 : PropOp {
			readonly Action<UnityEngine.Vector3> _cb;
			public Vector3(Action<UnityEngine.Vector3> cb) { _cb = cb; }
			protected override unsafe void Execute() {
				var o = GetOperand(0);
				switch ((o.Length - 4) / 4) {
					case 0: // Number
					case 1: // Array[1]
						float num = o.AsNumber();
						_cb(new UnityEngine.Vector3(num, num));
						break;
					case 2:
						_cb(*(UnityEngine.Vector2*)o.TrustedAsOfLength(sizeof(UnityEngine.Vector2)));
						break;
					case 3:
						_cb(*(UnityEngine.Vector3*)o.TrustedAsOfLength(sizeof(UnityEngine.Vector3)));
						break;
					default:
						throw new InvalidOperationException("Invalid array size");
				}
			}
		}
		public class Color : PropOp {
			readonly Action<UnityEngine.Color> _cb;
			public Color(Action<UnityEngine.Color> cb) { _cb = cb; }
			protected override unsafe void Execute() {
				var o = GetOperand(0);
				switch ((o.Length - 4) / 4) {
					case 3:
						var ptr = (float*)o.TrustedAsOfLength(sizeof(float) * 3);
						_cb(new UnityEngine.Color(ptr[0], ptr[1], ptr[2]));
						break;
					case 4:
						_cb(*(UnityEngine.Color*)o.TrustedAsOfLength(sizeof(UnityEngine.Color)));
						break;
					default:
						throw new InvalidOperationException("Invalid array size");
				}
			}
		}
	}
}
