//
// System.TypeIdentifier.cs
//
// Author:
//   Aleksey Kliger <aleksey@xamarin.com>
//
//
// Copyright (C) 2015 Xamarin, Inc (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace System {

	// A TypeName is wrapper around type names in display form
	// (that is, with special characters escaped).
	//
	// Note that in general if you unescape a type name, you will
	// lose information: If the type name's DisplayName is
	// Foo\+Bar+Baz (outer class ``Foo+Bar``, inner class Baz)
	// unescaping the first plus will give you (outer class Foo,
	// inner class Bar, innermost class Baz).
	//
	// The correct way to take a TypeName apart is to feed its
	// DisplayName to TypeSpec.Parse()
	//
	internal interface TypeName : System.IEquatable<TypeName> {
		string DisplayName {
			get;
		}

		// add a nested name under this one.
		TypeName NestedName (TypeIdentifier innerName);
	}

	// A type identifier is a single component of a type name.
	// Unlike a general typename, a type identifier can be be
	// converted to internal form without loss of information.
	internal interface TypeIdentifier : TypeName {

		string InternalName {
			get;
		}

	}

	internal class TypeNames {
		internal static TypeName FromDisplay (string displayName)
		{
			return new Display (displayName);
		}

		internal abstract class ATypeName : TypeName {
			public abstract string DisplayName { get; }

			public abstract TypeName NestedName (TypeIdentifier innerName);

			public bool Equals(TypeName other)
			{
				return other != null && DisplayName == other.DisplayName;
			}

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

			public override bool Equals(object other)
			{
				return Equals(other as TypeName);
			}
		}


		private class Display : ATypeName {
			string displayName;

			internal Display (string displayName)
			{
				this.displayName = displayName;
			}

			public override string DisplayName { get { return displayName; } }

			public override TypeName NestedName (TypeIdentifier innerName)
			{
				return new Display (DisplayName + "+" + innerName.DisplayName);
			}

		}
	}

	internal class TypeIdentifiers {

		internal static TypeIdentifier FromDisplay (string displayName)
		{
			return new Display (displayName);
		}

		internal static TypeIdentifier FromInternal (string internalName)
		{
			return new Internal (internalName);
		}

		internal static TypeIdentifier FromInternal (string internalNameSpace, TypeIdentifier typeName)
		{
			return new Internal (internalNameSpace, typeName);
		}

		// Only use if simpleName is certain not to contain
		// unexpected characters that ordinarily require
		// escaping: ,+*&[]\
		internal static TypeIdentifier WithoutEscape (string simpleName)
		{
			return new NoEscape (simpleName);
		}

		private class Display : TypeNames.ATypeName, TypeIdentifier {
			string displayName;
			string internal_name; //cached

			internal Display (string displayName)
			{
				this.displayName = displayName;
				internal_name = null;
			}

			public override string DisplayName {
				get { return displayName; }
			}

			public string InternalName {
				get {
					if (internal_name == null)
						internal_name = GetInternalName ();
					return internal_name;
				}
			}

			private string GetInternalName ()
			{
				return TypeSpec.UnescapeInternalName(displayName);
			}

			public override TypeName NestedName (TypeIdentifier innerName) {
				return TypeNames.FromDisplay (DisplayName + "+" + innerName.DisplayName);
			}
		}


		private class Internal : TypeNames.ATypeName, TypeIdentifier {
			string internalName;
			string display_name; //cached

			internal Internal (string internalName)
			{
				this.internalName = internalName;
				this.display_name = null;
			}

			internal Internal (string nameSpaceInternal, TypeIdentifier typeName)
			{
				this.internalName = nameSpaceInternal + "." + typeName.InternalName;
				this.display_name = null;
			}

			public override string DisplayName {
				get {
					if (display_name == null)
						display_name = GetDisplayName ();
					return display_name;
				}
			}

			public string InternalName {
				get { return internalName; }
			}

			private string GetDisplayName ()
			{
				return TypeSpec.EscapeDisplayName (internalName);
			}

			public override TypeName NestedName (TypeIdentifier innerName)
			{
				return TypeNames.FromDisplay (DisplayName + "+" + innerName.DisplayName);
			}

		}

		private class NoEscape : TypeNames.ATypeName, TypeIdentifier {
			string simpleName;
			internal NoEscape (string simpleName)
			{
				this.simpleName = simpleName;
#if DEBUG
				checkNoBadChars(simpleName);
#endif
			}

			public override string DisplayName { get { return simpleName; } }
			public string InternalName { get { return simpleName; } }

#if DEBUG
			static private void checkNoBadChars (string s)
			{
				if (TypeSpec.NeedsEscaping (s))
				    throw new ArgumentException ("simpleName");
			}
#endif

			public override TypeName NestedName (TypeIdentifier innerName) {
				return TypeNames.FromDisplay (DisplayName + "+" + innerName.DisplayName);
			}
		}
	}
}
