//  JValue - JString


using System;
using System.Text;
using Leguar.TotalJSON.Internal;

namespace Leguar.TotalJSON {
	
	/// <summary>
	/// Class to store string value in JSON format. Once JString instance is created, its value can't be changed.
	/// </summary>
	public class JString : JValue { // , IEquatable<JString> {

		private const string HEX="0123456789ABCDEF";

		private readonly string stringValue;

		/// <summary>
		/// Creates new instance of JString object.
		/// 
		/// Parameter can't be null. If you wish to add null to JSON or JArray object, create <code>new JNull()</code> and add that one.
		/// </summary>
		/// <param name="stringValue">
		/// C# string value to be stored in this object.
		/// </param>
		/// <exception cref="JArgumentNullException">
		/// If parameter is null.
		/// </exception>
		public JString(string stringValue) : base() {
			if (stringValue==null) {
				throw (new JArgumentNullException("stringValue","Parameter can not be null in constructor JString.<init>(string)"));
			}
			this.stringValue=stringValue;
		}

		/// <summary>
		/// Returns compact information of this JString object as string, for debug purposes.
		/// </summary>
		/// <returns>
		/// Single string with information of this JString object.
		/// </returns>
		public override string ToString() {
			int length = stringValue.Length;
			if (length==0) {
				return ("[JString: Empty string]");
			} else {
				return ("[JString: \""+stringValue+"\" ("+length+" character"+(length>1?"s":"")+")]");
			}
		}

		/// <summary>
		/// Test if another object equals to this object. Always returns false if parameter object is null or it is not instance of JString.
		/// Two JString objects are equal if both contains exactly same string.
		/// </summary>
		/// <param name="anotherObject">
		/// Another object that is compared to this one.
		/// </param>
		/// <returns>
		/// True if objects are equal, false otherwise.
		/// </returns>
		public override bool Equals(object anotherObject) {
			if (anotherObject==null) {
				return false;
			}
			if (!(anotherObject is JString)) {
				return false;
			}
			JString anotherJString=(JString)(anotherObject);
			return (stringValue.Equals(anotherJString.AsString()));
		}

		public override int GetHashCode() {
			return stringValue.GetHashCode();
		}

		/// <summary>
		/// Get value of this JSON string as c# system string.
		/// </summary>
		/// <returns>
		/// c# string value, can not be null.
		/// </returns>
		public string AsString() {
			return stringValue;
		}

		internal override void zCreate(CreateStringRunner createStringRunner) {
			encode(createStringRunner,stringValue,true);
		}

		internal static void encode(CreateStringRunner createStringRunner, string str, bool isValue) {
			createStringRunner.append('"');
			if (isValue) {
				createStringRunner.appendColoring(CreateStringRunner.COLOR_STRING_VALUE);
			}
			foreach (char chr in str) {
				if (chr=='"') {
					createStringRunner.append("\\\"");
				} else if (chr=='\\') {
					createStringRunner.append("\\\\");
				} else if (chr=='/' && createStringRunner.isEscapeForwardSlashes()) {
					createStringRunner.append("\\/");
				} else if (chr=='\b') {
					createStringRunner.append("\\b");
				} else if (chr=='\f') {
					createStringRunner.append("\\f");
				} else if (chr=='\n') {
					createStringRunner.append("\\n");
				} else if (chr=='\r') {
					createStringRunner.append("\\r");
				} else if (chr=='\t') {
					createStringRunner.append("\\t");
				} else if (chr<32 || chr>126) {
					if (chr<16) {
						createStringRunner.append("\\u000");
						createStringRunner.append(HEX[chr]);
					} else if (chr<256) {
						createStringRunner.append("\\u00");
						createStringRunner.append(HEX[chr/16]);
						createStringRunner.append(HEX[chr%16]);
					} else if (chr<4096) {
						createStringRunner.append("\\u0");
						createStringRunner.append(HEX[chr/256]);
						createStringRunner.append(HEX[(chr%256)/16]);
						createStringRunner.append(HEX[chr%16]);
					} else {
						createStringRunner.append("\\u");
						createStringRunner.append(HEX[chr/4096]);
						createStringRunner.append(HEX[(chr%4096)/256]);
						createStringRunner.append(HEX[(chr%256)/16]);
						createStringRunner.append(HEX[chr%16]);
					}
				} else {
					if (createStringRunner.isColoredOutput() && (chr == '<' || chr == '>')) {
						createStringRunner.append("\\u00");
						createStringRunner.append(HEX[chr/16]);
						createStringRunner.append(HEX[chr%16]);
					} else {
						createStringRunner.append(chr);
					}
				}
			}
			if (isValue) {
				createStringRunner.appendColoring(CreateStringRunner.COLOR_END);
			}
			createStringRunner.append('"');
		}

		internal static JString zParse(ParseStringRunner parseStringRunner, bool expectStartOfString) {

			StringPointer sp = parseStringRunner.getStringPointer();

			if (expectStartOfString) {
				char chr;
				if (!sp.tryGetNextNonWhiteChar(out chr)) {
					throw ParseException.forInvalidStart("Parameter string didn't contain any non-white characters",parseStringRunner);
				}
				if (chr!='"') {
					throw ParseException.forInvalidStart("Invalid character "+InternalTools.logSafeCharacter(chr)+" when expecting start of string '\"'", parseStringRunner);
				}
			}

			return (new JString(decode(parseStringRunner)));

		}

		internal static string decode(ParseStringRunner parseStringRunner) {

			// At this point, StringPointer has passed starting "
			StringPointer sp = parseStringRunner.getStringPointer();

			StringBuilder sb=new StringBuilder();
			do {
				char chr=sp.getNextChar();
				if (chr=='"') {
					return sb.ToString();
				}
				if (chr=='\\') {
					chr=sp.getNextChar();
					if (chr=='"' || chr=='\\' || chr=='/') {
						// 'chr' works as is
					} else if (chr=='b') {
						chr='\b';
					} else if (chr=='f') {
						chr='\f';
					} else if (chr=='n') {
						chr='\n';
					} else if (chr=='r') {
						chr='\r';
					} else if (chr=='t') {
						chr='\t';
					} else if (chr=='u') {
						int ucode=0;
						for (int n=0; n<4; n++) {
							chr=sp.getNextChar();
							int uvalue=HEX.IndexOf(char.ToUpperInvariant(chr));
							if (uvalue<0) {
								throw ParseException.forInvalidCharacter("Invalid hexadecimal character "+InternalTools.logSafeCharacter(chr)+" after '\\u' in string value", parseStringRunner);
							}
							ucode=ucode*16+uvalue;
						}
						chr=(char)(ucode);
					} else {
						throw ParseException.forInvalidCharacter("Invalid character "+InternalTools.logSafeCharacter(chr)+" after '\\' in string value", parseStringRunner);
					}
				}
				sb.Append(chr);
			} while (true);

		}

		internal override object zDeserialize(Type type, string toFieldName, DeserializeSettings deserializeSettings) {

			if (type==typeof(string)) {
				return this.AsString();
			}

			if (type==typeof(object)) {
				if (deserializeSettings.AllowFieldsToBeObjects) {
					return this.AsString();
				}
				throw (DeserializeException.forNonMatchingTypeObject(this, toFieldName));
			}

			throw (DeserializeException.forNonMatchingType(this,type,toFieldName));

		}

	}

}
