//#define USE_HEX_FLOAT

using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace AssetStudio
{
	public sealed class YAMLScalarNode : YAMLNode
	{
		public YAMLScalarNode()
		{
		}

		public YAMLScalarNode(bool value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(bool value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(byte value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(byte value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(short value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(short value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(ushort value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(ushort value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(int value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(int value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(uint value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(uint value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(long value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(long value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(ulong value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(ulong value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(float value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(float value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(double value) :
			this(value, false)
		{
		}

		public YAMLScalarNode(double value, bool isHex)
		{
			SetValue(value);
			Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain;
		}

		public YAMLScalarNode(string value)
		{
			SetValue(value);
			Style = GetStringStyle(value);
		}

		internal YAMLScalarNode(string value, bool _)
		{
			SetValue(value);
			Style = ScalarStyle.Plain;
		}

		public void SetValue(bool value)
		{
			m_value = value ? 1u : 0u;
			m_objectType = ScalarType.Boolean;
		}

		public void SetValue(byte value)
		{
			m_value = value;
			m_objectType = ScalarType.Byte;
		}

		public void SetValue(short value)
		{
			m_value = unchecked((ushort)value);
			m_objectType = ScalarType.Int16;
		}

		public void SetValue(ushort value)
		{
			m_value = value;
			m_objectType = ScalarType.UInt16;
		}

		public void SetValue(int value)
		{
			m_value = unchecked((uint)value);
			m_objectType = ScalarType.Int32;
		}

		public void SetValue(uint value)
		{
			m_value = value;
			m_objectType = ScalarType.UInt32;
		}

		public void SetValue(long value)
		{
			m_value = unchecked((ulong)value);
			m_objectType = ScalarType.Int64;
		}

		public void SetValue(ulong value)
		{
			m_value = value;
			m_objectType = ScalarType.UInt64;
		}

		public void SetValue(float value)
		{
#if USE_HEX_FLOAT
			// It is more precise technic but output looks vague and less readable
			uint hex = BitConverterExtensions.ToUInt32(value);
			m_string = $"0x{hex.ToHexString()}({value.ToString(CultureInfo.InvariantCulture)})";
			m_objectType = ScalarType.String;
#else
			m_value = BitConverterExtensions.ToUInt32(value);
			m_objectType = ScalarType.Single;
#endif
		}

		public void SetValue(double value)
		{
#if USE_HEX_FLOAT
			// It is more precise technic but output looks vague and less readable
			ulong hex = BitConverterExtensions.ToUInt64(value);
			m_string = $"0x{hex.ToHexString()}({value.ToString(CultureInfo.InvariantCulture)})";
			m_objectType = ScalarType.String;
#else
			m_value = BitConverterExtensions.ToUInt64(value);
			m_objectType = ScalarType.Double;
#endif
		}

		public void SetValue(string value)
		{
			m_string = value;
			m_objectType = ScalarType.String;
		}

		internal Emitter ToString(Emitter emitter)
		{
			if (Style == ScalarStyle.Hex)
			{
				switch (m_objectType)
				{
					case ScalarType.Byte:
						return emitter.WriteHex((byte)m_value);
					case ScalarType.Int16:
						return emitter.WriteHex(unchecked((short)m_value));
					case ScalarType.UInt16:
						return emitter.WriteHex((ushort)m_value);
					case ScalarType.Int32:
						return emitter.WriteHex(unchecked((int)m_value));
					case ScalarType.UInt32:
						return emitter.WriteHex((uint)m_value);
					case ScalarType.Int64:
						return emitter.WriteHex(unchecked((long)m_value));
					case ScalarType.UInt64:
						return emitter.WriteHex(m_value);
					case ScalarType.Single:
						return emitter.WriteHex((uint)m_value);
					case ScalarType.Double:
						return emitter.WriteHex(m_value);
					default:
						throw new NotImplementedException(m_objectType.ToString());
				}
			}

			switch (m_objectType)
			{
				case ScalarType.Boolean:
					return emitter.Write(m_value);
				case ScalarType.Byte:
					return emitter.Write(m_value);
				case ScalarType.Int16:
					return emitter.Write(unchecked((short)m_value));
				case ScalarType.UInt16:
					return emitter.Write(m_value);
				case ScalarType.Int32:
					return emitter.Write(unchecked((int)m_value));
				case ScalarType.UInt32:
					return emitter.Write(m_value);
				case ScalarType.Int64:
					return emitter.Write(unchecked((long)m_value));
				case ScalarType.UInt64:
					return emitter.Write(m_value);
				case ScalarType.Single:
					return emitter.Write(BitConverterExtensions.ToSingle((uint)m_value));
				case ScalarType.Double:
					return emitter.Write(BitConverterExtensions.ToDouble(m_value));
				case ScalarType.String:
					return WriteString(emitter);

				default:
					throw new NotImplementedException(m_objectType.ToString());
			}
		}

		internal override void Emit(Emitter emitter)
		{
			base.Emit(emitter);

			switch (Style)
			{
				case ScalarStyle.Hex:
				case ScalarStyle.Plain:
					ToString(emitter);
					break;

				case ScalarStyle.SingleQuoted:
					emitter.Write('\'');
					ToString(emitter);
					emitter.Write('\'');
					break;

				case ScalarStyle.DoubleQuoted:
					emitter.Write('"');
					ToString(emitter);
					emitter.Write('"');
					break;

				default:
					throw new Exception($"Unsupported scalar style {Style}");
			}
		}

		private Emitter WriteString(Emitter emitter)
		{
			if (Style == ScalarStyle.Plain)
			{
				if (emitter.IsFormatKeys && emitter.IsKey)
				{
					emitter.WriteFormat(m_string);
				}
				else
				{
					emitter.Write(m_string);
				}
			}
			else if (Style == ScalarStyle.SingleQuoted)
			{
				emitter.WriteDelayed();
				for (int i = 0; i < m_string.Length; i++)
				{
					char c = m_string[i];
					emitter.WriteRaw(c);
					if (c == '\'')
					{
						emitter.WriteRaw(c);
					}
					else if (c == '\n')
					{
						emitter.WriteRaw("\n    ");
					}
				}
			}
			else if (Style == ScalarStyle.DoubleQuoted)
			{
				emitter.WriteDelayed();
				for (int i = 0; i < m_string.Length; i++)
				{
					char c = m_string[i];
					switch (c)
					{
						case '\\':
							emitter.WriteRaw('\\').WriteRaw('\\');
							break;
						case '\n':
							emitter.WriteRaw('\\').WriteRaw('n');
							break;
						case '\r':
							emitter.WriteRaw('\\').WriteRaw('r');
							break;
						case '\t':
							emitter.WriteRaw('\\').WriteRaw('t');
							break;
						case '"':
							emitter.WriteRaw('\\').WriteRaw('"');
							break;

						default:
							emitter.WriteRaw(c);
							break;
					}
				}
			}
			else
			{
				throw new NotSupportedException(Style.ToString());
			}
			return emitter;
		}

		private static ScalarStyle GetStringStyle(string value)
		{
			if (s_illegal.IsMatch(value))
			{
				return value.Contains("\n ") ? ScalarStyle.DoubleQuoted : ScalarStyle.SingleQuoted;
			}
			return ScalarStyle.Plain;
		}

		public static YAMLScalarNode Empty { get; } = new YAMLScalarNode();

		public override YAMLNodeType NodeType => YAMLNodeType.Scalar;
		public override bool IsMultiline => false;
		public override bool IsIndent => false;

		public string Value
		{
			get
			{
				if (Style == ScalarStyle.Hex)
				{
					switch (m_objectType)
					{
						case ScalarType.Byte:
							return unchecked((byte)m_value).ToHexString();
						case ScalarType.Int16:
							return unchecked((short)m_value).ToHexString();
						case ScalarType.UInt16:
							return unchecked((ushort)m_value).ToHexString();
						case ScalarType.Int32:
							return unchecked((int)m_value).ToHexString();
						case ScalarType.UInt32:
							return unchecked((uint)m_value).ToHexString();
						case ScalarType.Int64:
							return unchecked((long)m_value).ToHexString();
						case ScalarType.UInt64:
							return m_value.ToHexString();
						case ScalarType.Single:
							return BitConverterExtensions.ToSingle((uint)m_value).ToHexString();
						case ScalarType.Double:
							return BitConverterExtensions.ToDouble(m_value).ToHexString();
						default:
							throw new NotImplementedException(m_objectType.ToString());
					}
				}

				switch (m_objectType)
				{
					case ScalarType.Boolean:
						return m_value == 1 ? "true" : "false";
					case ScalarType.Byte:
						return m_value.ToString();
					case ScalarType.Int16:
						return unchecked((short)m_value).ToString();
					case ScalarType.UInt16:
						return m_value.ToString();
					case ScalarType.Int32:
						return unchecked((int)m_value).ToString();
					case ScalarType.UInt32:
						return m_value.ToString();
					case ScalarType.Int64:
						return unchecked((long)m_value).ToString();
					case ScalarType.UInt64:
						return m_value.ToString();
					case ScalarType.Single:
						return BitConverterExtensions.ToSingle((uint)m_value).ToString(CultureInfo.InvariantCulture);
					case ScalarType.Double:
						return BitConverterExtensions.ToDouble(m_value).ToString(CultureInfo.InvariantCulture);
					case ScalarType.String:
						return m_string;

					default:
						throw new NotImplementedException(m_objectType.ToString());
				}
			}
			set => m_string = value;
		}
		public ScalarStyle Style { get; }

		private static readonly Regex s_illegal = new Regex("(^\\s)|(^-\\s)|(^-$)|(^[\\:\\[\\]'\"*&!@#%{}?<>,\\`])|([:@]\\s)|([\\n\\r])|([:\\s]$)", RegexOptions.Compiled);

		private ScalarType m_objectType = ScalarType.String;
		private string m_string = string.Empty;
		private ulong m_value = 0;
	}
}
