using System;
using System.IO;
using System.Windows.Forms;
using System.Threading;


namespace DarkStrideToolbox
{
	//This class is meant to replace the system registry.  Writting to the registry is fast and convenient but has some
	//downsides, for one its dificult to transfer between machines and directories.  It also leads to DLLHell.  This class
	//makes using an INI file fast and easy while still having the powerful flexability you might need.
	public class DSINIFile
	{
		#region Constants
		private const string m_cVERSION_10 = "DarkStrideToolbox INI v1.0";
		private const string m_cVERSION_11 = "DarkStrideToolbox INI v1.1";
		#endregion
		
		#region Properties
		private DSSortedList m_oValues = new DSSortedList();
		private string m_sFileName = "";
		#endregion


		public DSINIFile(){}
		

		public bool IsKeyDefined( string sKey )
		{
			bool bRetVal = false;
	

			bRetVal = IsKeyDefined( m_oValues,sKey );

			
			return( bRetVal );
		}
		public bool IsKeyDefined( DSSortedList oCluster,string sKey )
		{
			bool bRetVal = false;
	

			bRetVal = oCluster.ContainsKey( sKey );

			
			return( bRetVal );
		}
		public void AddKey( string sKey, string sValue )
		{
			AddKey( m_oValues,sKey,sValue );
		}
		public void AddKey( DSSortedList oStartingCluster,string sKey, string sValue )
		{
			if( true == IsKeyDefined( oStartingCluster,sKey ) )
			{
				RemoveKey( oStartingCluster,sKey );
			}

			oStartingCluster.Add( sKey,sValue );
		}

		public DSSortedList AddCluster( string sClusterKey )
		{
			return( AddCluster( m_oValues,sClusterKey ) );
		}
		public DSSortedList AddCluster( DSSortedList oStartingCluster,string sClusterKey )
		{
			DSSortedList oRetVal = new DSSortedList();

			oStartingCluster.Add( sClusterKey,oRetVal );

			return( oRetVal );
		}
		public void RemoveKey( string sKey )
		{
			RemoveKey( m_oValues,sKey );
		}
		public void RemoveKey( DSSortedList oStartingCluster,string sKey )
		{
			if( true == IsKeyDefined( oStartingCluster,sKey ) )
			{
				oStartingCluster.Remove( sKey );
			}
		}


		public string GetValueForKey( string sKey )
		{
			string sRetVal = "";
	

			sRetVal = GetValueForKey( sKey,"" );


			return( sRetVal );
		}
		public string GetValueForKey( string sKey,string sValueIfKeyNotFound )
		{
			string sRetVal = "";

	
			if( IsKeyDefined( sKey ) == true )
			{
				sRetVal = (string)m_oValues.GetByKey( sKey );
			}
			else
			{
				sRetVal = sValueIfKeyNotFound;
			}

			
			return( sRetVal );
		}

		public string GetValueForKey( DSSortedList oStartingCluster,string sKey )
		{
			string sRetVal = "";
	

			sRetVal = GetValueForKey( oStartingCluster,sKey,"" );


			return( sRetVal );
		}
		public string GetValueForKey( DSSortedList oStartingCluster,string sKey,string sValueIfKeyNotFound )
		{
			string sRetVal = "";

	
			if( IsKeyDefined( oStartingCluster,sKey ) == true )
			{
				sRetVal = (string)oStartingCluster.GetByKey( sKey );
			}
			else
			{
				sRetVal = sValueIfKeyNotFound;
			}

			
			return( sRetVal );
		}

		public DSSortedList GetClusterForKey( string sClusterGroup,string sClusterKey )
		{
			DSSortedList oClusterGroup = null;
			DSSortedList oRetVal = null;


			//First of all get our cluster group
			oClusterGroup = GetClusterForKey( m_oValues,sClusterGroup );
			//Now get our individual cluster
			if( oClusterGroup != null )
			{
                oRetVal = GetClusterForKey( oClusterGroup,sClusterKey );
			}
			else
			{
				throw new System.Exception( "No cluster found for key <" + sClusterKey + ">." );
			}


			return( oRetVal );
		}
		public DSSortedList GetClusterForKey( string sClusterKey )
		{
			object oValue = null;
			DSSortedList oRetVal = null;


			//First of all get our cluster group
			if( IsKeyDefined( m_oValues,sClusterKey ) == true )
			{
				oValue	= m_oValues.GetByKey( sClusterKey );
				if( oValue.GetType().ToString() != "System.String" )
				{
					oRetVal = (DSSortedList)oValue;
				}
				else
				{
					throw new System.Exception( "Key <" + sClusterKey + "> returned an object of type <" + oValue.GetType().ToString() + "> not a cluster type." );
				}
			}


			return( oRetVal );
		}

		public DSSortedList GetClusterForKey( DSSortedList oStartingCluster,string sClusterKey )
		{
			object oValue = null;
			DSSortedList oRetVal = null;


			//First of all get our cluster group
			if( IsKeyDefined( oStartingCluster,sClusterKey ) == true )
			{
				oValue	= oStartingCluster.GetByKey( sClusterKey );
				if( oValue.GetType().ToString() != "System.String" )
				{
					oRetVal = (DSSortedList)oValue;
				}
				else
				{
					throw new System.Exception( "Key <" + sClusterKey + "> returned an object of type <" + oValue.GetType().ToString() + "> not a cluster type." );
				}
			}


			return( oRetVal );
		}


		public void OpenINIFile( string sFileName )
		{
			OpenINIFile( sFileName,true );
		}
		
		public void OpenINIFile( string sFileName,bool bCreateFileIfNotFound )
		{
			StreamReader oStream = null;
			string sLine = "";

			try
			{
				m_sFileName = sFileName;
				m_oValues.Clear();

				//Open the file and parse it!
				oStream = new StreamReader( sFileName );

				//Read in the header information
				sLine = oStream.ReadLine();
				if( sLine.Equals( m_cVERSION_10 ) == true )
				{
					ReadINIFile_v10( oStream );
				}
				else if( sLine.Equals( m_cVERSION_11 ) == true )
				{
					ReadINIFile_v11( oStream );
				}
				else
				{
					throw new System.Exception( "File is not a ini file" );
				}

				oStream.Close();
			}
			catch( System.IO.FileNotFoundException oE )
			{
				//If they told us to create a new file then we do nothing
				if( false == bCreateFileIfNotFound )
				{
					throw new System.IO.FileNotFoundException( "Unable to find INI file <" + m_sFileName + ">",oE );
				}
			}
		}

		private void ReadINIFile_v10( StreamReader oStream )
		{
			string sLine = "";
			string[] saArguments = null;


			//Now read in our values!
			sLine = oStream.ReadLine();
			while( sLine != null )
			{
				saArguments = DSMisc.Split( sLine,"," );
				m_oValues.Add( saArguments[0],saArguments[1] );
				sLine = oStream.ReadLine();
			}
			oStream.Close();
		}

		private void ReadINIFile_v11( StreamReader oStream )
		{
			m_oValues = LoadClusterRecursively( oStream,null,false );
		}

		public DSSortedList LoadClusterRecursively( StreamReader oStream,DSSortedList oStartingCluster,bool bInCluster )
		{
			DSSortedList oCluster = new DSSortedList();
			DSSortedList oRecurCluster = null;
			DSSortedList oKeyCluster = null;
			int nIndex = -1;
			string sKey = "";
			string sLine = "";
			string sValue = "";
			string[] saArguments = null;


			//Setup our initial cluster
			if( oStartingCluster != null )
			{
				oCluster = oStartingCluster;
			}

			sLine = oStream.ReadLine();
			while( sLine != null )
			{
				saArguments = DSMisc.Split( sLine,"," );
				if( sLine.Length > 1 && sLine.Substring( 0,2) == "//" )
				{
					//This is a comment, ignore it.
				}
				else if( sLine.Length > 0 && saArguments.Length >= 2 )
				{
					//Check if this is one of our new clusters
					if( saArguments[1].Length > 0 &&
						FormatKey( saArguments[1] ).Substring( 0,1 ) == "[" && 
						saArguments[1].Substring( saArguments[1].Length-1,1 ) == "]" )
					{
						//Have we added one of this type yet?
						nIndex = oCluster.IndexOfKey( FormatKey( saArguments[0] ) );
						//We don't have it yet so create it
						if( nIndex == -1 )
						{
							oKeyCluster = new DSSortedList();
						}
						else
						{
							oKeyCluster = (DSSortedList)oCluster.GetByIndex( nIndex );
							oCluster.RemoveAt( nIndex );
						}
						//Define our new branch and add its data
						oRecurCluster = LoadClusterRecursively( oStream,null,true );
						sKey = FormatKey( saArguments[1] );
						sKey = sKey.Substring( 1,sKey.Length-2 );
						//Un-Escape
						sKey = UnEscape( sKey );

						oKeyCluster.Add( sKey,oRecurCluster );
						//Add in the cluster we just created
						sValue = saArguments[0];
						sValue = UnEscape( sValue );

						oCluster.Add( FormatKey( sValue.Replace( "\t","" ) ),oKeyCluster );
					}
					//This means its a normal key/value pair
					else
					{
						//Un-Escape
						sValue = UnEscape( saArguments[1] );

						oCluster.Add( FormatKey( saArguments[0].Replace( "\t","" ) ),sValue );
					}
				}
					//This marks the end of our cluster
				else if( bInCluster == true )
				{
					break;
				}

				sLine = oStream.ReadLine();
			}

			return( oCluster );
		}


		private string FormatKey( string sKey )
		{
			string sRetVal = "";

			sRetVal = sKey.Trim();
			sRetVal = sRetVal.Replace( "\t","" );

			return( sRetVal );
		}


		public void SaveINIFile( string sFileName )
		{
			m_sFileName = sFileName;
			SaveINIFile();
		}
		public void SaveINIFile()
		{
			StreamWriter oStream = null;


			try
			{
				oStream = new StreamWriter( m_sFileName,false );
			}
			catch
			{
				Application.DoEvents();
				Thread.Sleep( 500 );
				try
				{
					oStream = new StreamWriter( m_sFileName,false );
				}
				catch( System.Exception oEx )
				{
					throw new System.Exception( "SaveINIFile Failed.",oEx );
				}
			}
			oStream.WriteLine( m_cVERSION_11 );
			oStream.WriteLine( "//Blank lines terminate clusters ([]).  This makes these files very space sensitive.  Be careful when you edit them." );
			oStream.WriteLine( "" );

			SaveINIFile( m_oValues,oStream,0 );
	
			oStream.Close();				
		}
		private void SaveINIFile( DSSortedList oCluster,StreamWriter oStream,long nTabsIn )
		{
			string sRecurKey = "";
			string sKey = "";
			string sValue = "";
			object oValue = null;
			DSSortedList oRecurCluster = null;
			DSSortedList oLoopCluster = null;


			//Pass 1 is for normal keys only, pass 2 is for clusters only
			for( int nPass=0 ; nPass<2 ; nPass++ )
			{
				//On the first pass only add a spare space to make it easy to read
				if( nTabsIn == 0 && nPass == 1 )
				{
					oStream.WriteLine( "" );
				}

				//Walk all the items in our list
				for( int i=0 ; i<oCluster.Count ; i++ )
				{
					sKey	= (string)oCluster.GetKey( i );
					oValue	= oCluster.GetByIndex( i );
					//Escape
					sKey = Escape( sKey );

					//If its a string this is easy
					if( oValue.GetType().ToString() == "System.String" && nPass == 0 )
					{
						sValue = (string)oValue;
						//Escape
						sValue = Escape( sValue );

						oStream.WriteLine( DSMisc.Repeat( "\t",nTabsIn ) + sKey + "," + sValue );
					}
					else if( oValue.GetType().ToString() != "System.String" && nPass == 1 )
					{
						oRecurCluster = (DSSortedList)oValue;
						for( int nRecurIndex=0 ; nRecurIndex<oRecurCluster.Count ; nRecurIndex++ )
						{
							oLoopCluster = (DSSortedList)oRecurCluster.GetByIndex( nRecurIndex );
							sRecurKey = (string)oRecurCluster.GetKey( nRecurIndex );
							//Escape
							sRecurKey = Escape( sRecurKey );

							oStream.WriteLine( DSMisc.Repeat( "\t",nTabsIn ) + sKey + ",[" + sRecurKey + "]" );
							SaveINIFile( oLoopCluster,oStream,nTabsIn+1 );
							oStream.WriteLine( "" );
						}
					}
				}
			}
		}
		private string Escape( string sValue )
		{
			string sNewValue = sValue;

			sNewValue = sNewValue.Replace( "[","(Escpd_LBrckt)" );
			sNewValue = sNewValue.Replace( "]","(Escpd_RBrckt)" );
			sNewValue = sNewValue.Replace( ",","(Escpd_Comma}" );
			sNewValue = sNewValue.Replace( "\n","(Escpd_CR}" );
			sNewValue = sNewValue.Replace( "\r","(Escpd_LF}" );

			return( sNewValue );
		}
		private string UnEscape( string sValue )
		{
			string sNewValue = sValue;

			sNewValue = sNewValue.Replace( "(Escpd_LBrckt)","[" );
			sNewValue = sNewValue.Replace( "(Escpd_RBrckt)","]" );
			sNewValue = sNewValue.Replace( "(Escpd_Comma}","," );
			sNewValue = sNewValue.Replace( "(Escpd_CR}","\n" );
			sNewValue = sNewValue.Replace( "(Escpd_LF}","\r" );

			return( sNewValue );
		}

		

		#region Properties
		public string Version
		{
			get
			{
				return( m_cVERSION_11 );
			}
		}
		public string FileName
		{
			get
			{
				return( m_sFileName );
			}
			set
			{
				m_sFileName = value;
			}
		}
		#endregion
	}
}
