using Cryville.Common.ComponentModel;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace Cryville.Crtr.Config {
	public interface IPropertyAdapter {
		string Category { get; }
		string Name { get; }
		PropertyType Type { get; }
		object[] Range { get; }
		object GetValue();
		void SetValue(object value);
		event Action ValueChanged;
		object MapValue(object value);
		bool SetMapped { get; }
		object MapValueInverse(object value);
	}

	public enum PropertyType {
		Unknown,
		Number,
		NumberStepped,
		Boolean,
		String,
	}

	public class DefaultPropertyAdapter : IPropertyAdapter {
		readonly object _target;
		readonly PropertyInfo _prop;
		public string Category { get; private set; }
		public string Name { get; private set; }
		public PropertyType Type { get; private set; }
		public object[] Range { get; private set; }
		public object GetValue() { return _prop.GetValue(_target, null); }
		public void SetValue(object value) { _prop.SetValue(_target, value, null); }

		public event Action ValueChanged { add { } remove { } }

		readonly double _precision;
		readonly double _step;
		readonly bool _logarithmic;
		public object MapValue(object value) {
			if (Type == PropertyType.Number || Type == PropertyType.NumberStepped) {
				var result = (double)value;
				if (_step != 0) result *= _step;
				if (_logarithmic) result = Math.Pow(Math.E, result);
				if (_precision != 0) result = Math.Round(result / _precision) * _precision;
				return Convert.ChangeType(result, _prop.PropertyType);
			}
			return value;
		}
		public bool SetMapped { get { return true; } }
		public object MapValueInverse(object value) {
			if (Type == PropertyType.Number || Type == PropertyType.NumberStepped) {
				var result = Convert.ToDouble(value);
				if (_logarithmic) result = Math.Log(result);
				if (_step != 0) result /= _step;
				return result;
			}
			return value;
		}

		public DefaultPropertyAdapter(object target, PropertyInfo prop) {
			_target = target;
			_prop = prop;
			var attrs = prop.GetCustomAttributes(typeof(CategoryAttribute), true);
			if (attrs.Length > 0) Category = ((CategoryAttribute)attrs.Single()).Category;
			Name = prop.Name;
			if (prop.PropertyType == typeof(bool)) Type = PropertyType.Boolean;
			else if (prop.PropertyType == typeof(char)) throw new NotSupportedException();
			else if (prop.PropertyType.IsPrimitive) {
				Type = prop.GetCustomAttributes(typeof(StepAttribute), true).Length > 0
					? PropertyType.NumberStepped
					: PropertyType.Number;
				var attr = prop.GetCustomAttributes(typeof(RangeAttribute), true);
				if (attr.Length > 0) {
					var u = (RangeAttribute)attr.Single();
					Range = new object[] { u.Min, u.Max };
				}
				attr = prop.GetCustomAttributes(typeof(PrecisionAttribute), true);
				if (attr.Length > 0) {
					var u = (PrecisionAttribute)attr.Single();
					_precision = u.Precision;
				}
				attr = prop.GetCustomAttributes(typeof(StepAttribute), true);
				if (attr.Length > 0) {
					var u = (StepAttribute)attr.Single();
					_step = u.Step;
				}
				attr = prop.GetCustomAttributes(typeof(LogarithmicScaleAttribute), true);
				if (attr.Length > 0) {
					_logarithmic = true;
				}
			}
			else if (prop.PropertyType == typeof(string)) Type = PropertyType.String;
			else return;
		}
	}
}
