using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;


namespace InControl.iOS.Xcode
{
	class PropertyCommentChecker
	{
		private int m_Level;
		private bool m_All;
		private List<List<string>> m_Props;
        
		/*  The argument is an array of matcher strings each of which determine 
                whether a property with a certain path needs to be decorated with a
                comment.
                
                A path is a number of path elements concatenated by '/'. The last path 
                element is either:
                    the key if we're referring to a dict key
                    the value if we're referring to a value in an array
                    the value if we're referring to a value in a dict
                All other path elements are either:
                    the key if the container is dict
                    '*' if the container is array
                
                Path matcher has the same structure as a path, except that any of the
                elements may be '*'. Matcher matches a path if both have the same number
                of elements and for each pair matcher element is the same as path element
                or is '*'.
                
                a/b/c matches a/b/c but not a/b nor a/b/c/d
                a/* /c matches a/d/c but not a/b nor a/b/c/d
                * /* /* matches any path from three elements
            */
		protected PropertyCommentChecker( int level, List<List<string>> props )
		{
			m_Level = level;
			m_All = false;
			m_Props = props;
		}

		public PropertyCommentChecker()
		{
			m_Level = 0;
			m_All = false;
			m_Props = new List<List<string>>();
		}

		public PropertyCommentChecker( IEnumerable<string> props )
		{
			m_Level = 0;
			m_All = false;
			m_Props = new List<List<string>>();
			foreach (var prop in props)
			{
				m_Props.Add( new List<string>( prop.Split( '/' ) ) );
			}
		}
        
		bool CheckContained( string prop )
		{
			if (m_All)
				return true;
			foreach (var list in m_Props)
			{
				if (list.Count == m_Level + 1)
				{
					if (list[m_Level] == prop)
						return true;
					if (list[m_Level] == "*")
					{
						m_All = true; // short-circuit all at this level
						return true;
					}
				}
			}
			return false;
		}
        
		public bool CheckStringValueInArray( string value )
		{
			return CheckContained( value );
		}
		public bool CheckKeyInDict( string key )
		{
			return CheckContained( key );
		}
        
		public bool CheckStringValueInDict( string key, string value )
		{
			foreach (var list in m_Props)
			{
				if (list.Count == m_Level + 2)
				{
					if ((list[m_Level] == "*" || list[m_Level] == key) &&
					    list[m_Level + 1] == "*" || list[m_Level + 1] == value)
						return true;
				}
			}
			return false;       
		}
        
		public PropertyCommentChecker NextLevel( string prop )
		{
			var newList = new List<List<string>>();
			foreach (var list in m_Props)
			{
				if (list.Count <= m_Level + 1)
					continue;
				if (list[m_Level] == "*" || list[m_Level] == prop)
					newList.Add( list );
			}
			return new PropertyCommentChecker( m_Level + 1, newList );
		}
	}

	class Serializer
	{
		public static PBXElementDict ParseTreeAST( TreeAST ast, TokenList tokens, string text )
		{
			var el = new PBXElementDict();
			foreach (var kv in ast.values)
			{
				PBXElementString key = ParseIdentifierAST( kv.key, tokens, text );
				PBXElement value = ParseValueAST( kv.value, tokens, text );
				el[key.value] = value;
			}
			return el;
		}
        
		public static PBXElementArray ParseArrayAST( ArrayAST ast, TokenList tokens, string text )
		{
			var el = new PBXElementArray();
			foreach (var v in ast.values)
			{
				el.values.Add( ParseValueAST( v, tokens, text ) );
			}
			return el;
		}
        
		public static PBXElement ParseValueAST( ValueAST ast, TokenList tokens, string text )
		{
			if (ast is TreeAST)
				return ParseTreeAST( (TreeAST) ast, tokens, text );
			if (ast is ArrayAST)
				return ParseArrayAST( (ArrayAST) ast, tokens, text );
			if (ast is IdentifierAST)
				return ParseIdentifierAST( (IdentifierAST) ast, tokens, text );
			return null;
		}
        
		public static PBXElementString ParseIdentifierAST( IdentifierAST ast, TokenList tokens, string text )
		{
			Token tok = tokens[ast.value];
			string value;
			switch (tok.type)
			{
				case TokenType.String:
					value = text.Substring( tok.begin, tok.end - tok.begin );
					return new PBXElementString( value );
				case TokenType.QuotedString:
					value = text.Substring( tok.begin, tok.end - tok.begin );
					value = PBXStream.UnquoteString( value );
					return new PBXElementString( value );
				default:
					throw new Exception( "Internal parser error" );
			}           
		}
        
		static string k_Indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
        
		static string GetIndent( int indent )
		{
			return k_Indent.Substring( 0, indent );
		}

		static void WriteStringImpl( StringBuilder sb, string s, bool comment, GUIDToCommentMap comments )
		{
			if (comment)
				comments.WriteStringBuilder( sb, s );
			else
				sb.Append( PBXStream.QuoteStringIfNeeded( s ) );
		}
                                         
		public static void WriteDictKeyValue( StringBuilder sb, string key, PBXElement value, int indent, bool compact, 
		                                      PropertyCommentChecker checker, GUIDToCommentMap comments )
		{
			if (!compact)
			{
				sb.Append( "\n" );
				sb.Append( GetIndent( indent ) );
			}
			WriteStringImpl( sb, key, checker.CheckKeyInDict( key ), comments );
			sb.Append( " = " );

			if (value is PBXElementString)
				WriteStringImpl( sb, value.AsString(), checker.CheckStringValueInDict( key, value.AsString() ), comments );
			else
			if (value is PBXElementDict)
				WriteDict( sb, value.AsDict(), indent, compact, checker.NextLevel( key ), comments );
			else
			if (value is PBXElementArray)
				WriteArray( sb, value.AsArray(), indent, compact, checker.NextLevel( key ), comments );
			sb.Append( ";" );
			if (compact)
				sb.Append( " " );
		}
        
		public static void WriteDict( StringBuilder sb, PBXElementDict el, int indent, bool compact, 
		                              PropertyCommentChecker checker, GUIDToCommentMap comments )
		{
			sb.Append( "{" );
            
			if (el.Contains( "isa" ))
				WriteDictKeyValue( sb, "isa", el["isa"], indent + 1, compact, checker, comments );
			var keys = new List<string>( el.values.Keys );
			keys.Sort( StringComparer.Ordinal );
			foreach (var key in keys)
			{
				if (key != "isa")
					WriteDictKeyValue( sb, key, el[key], indent + 1, compact, checker, comments );
			}
			if (!compact)
			{
				sb.Append( "\n" );
				sb.Append( GetIndent( indent ) );
			}
			sb.Append( "}" );
		}
 
		public static void WriteArray( StringBuilder sb, PBXElementArray el, int indent, bool compact, 
		                               PropertyCommentChecker checker, GUIDToCommentMap comments )
		{
			sb.Append( "(" );
			foreach (var value in el.values)
			{
				if (!compact)
				{
					sb.Append( "\n" );
					sb.Append( GetIndent( indent + 1 ) );
				}
                
				if (value is PBXElementString)
					WriteStringImpl( sb, value.AsString(), checker.CheckStringValueInArray( value.AsString() ), comments );
				else
				if (value is PBXElementDict)
					WriteDict( sb, value.AsDict(), indent + 1, compact, checker.NextLevel( "*" ), comments );
				else
				if (value is PBXElementArray)
					WriteArray( sb, value.AsArray(), indent + 1, compact, checker.NextLevel( "*" ), comments );
				sb.Append( "," );
				if (compact)
					sb.Append( " " );
			}
            
			if (!compact)
			{
				sb.Append( "\n" );
				sb.Append( GetIndent( indent ) );
			}
			sb.Append( ")" );
		}
	}
    
}
// namespace UnityEditor.iOS.Xcode

