using System;
using System.Collections;


namespace DarkStrideToolbox
{
	public class DSMappedSerialization
	{
		#region Properties
		private static DSMappedSerialization m_oSingletonInstanceOfMe = null;
		private DSSortedList m_oSerializingObjects = new DSSortedList();
		#endregion


		private DSMappedSerialization()
		{
		}

		public static DSMappedSerialization GetGlobalInstance()
		{
			if( m_oSingletonInstanceOfMe == null )
			{
				m_oSingletonInstanceOfMe = new DSMappedSerialization();
			}

			return( m_oSingletonInstanceOfMe );
		}


		public DSMappedSerialize GetMappedSerializationObject( string sKey )
		{
			DSMappedSerialize oRetVal = null;

			if( m_oSerializingObjects.ContainsKey( sKey ) == true )
			{
				oRetVal = (DSMappedSerialize)m_oSerializingObjects.GetByKey( sKey );
			}

			return( oRetVal );
		}
		public DSMappedSerializesPrep PrepareForSerializing( string sKey )
		{
			DSMappedSerialize oSerObj = GetMappedSerializationObject( sKey );
			DSMappedSerializesPrep oSerPrep = null;


			if( oSerObj != null )
			{
				oSerPrep = (DSMappedSerializesPrep)oSerObj.PrepareForSerializing();
			}


			return( oSerPrep );
		}
		public void AddNewMappedSerializationObject( string sKey,DSMappedSerialize oObject )
		{
			if( m_oSerializingObjects.ContainsKey( sKey ) == true )
			{
				m_oSerializingObjects.Remove( sKey );
			}

			m_oSerializingObjects.Add( sKey,oObject );
		}



		#region Properties
		#endregion
	}

	public class DSMappedSerialize
	{
		#region Properties
		public ArrayList m_oMapItems = new ArrayList();
		public DSSortedList m_oMapItemsLookup = new DSSortedList();
		#endregion


		public DSMappedSerializesPrep PrepareForSerializing()
		{
			DSMappedSerializesPrep oSerPrep = new DSMappedSerializesPrep( this );
			return( oSerPrep );
		}
		public string Serialize( DSMappedSerializesPrep oSerPrep )
		{
			return( Serialize( oSerPrep,true ) );
		}
		public string Serialize( DSMappedSerializesPrep oSerPrep,bool bValidateDataSize )
		{
			DSMappedSerializeMapItem oLoopItem = null;
			DSBitArray oSerializedValues = null;
			enumType nEnumType = enumType.NotSet;
			string sRetVal = "";
			string sValue = "";
			string sErrorMsg = "";
			long nIntValue = 0;
			long nDecValue = 0;
			double dValue = 0;
			bool bValue = false;
			int nDidgets = 0;
			int nStringStartPos = 0;
			int nPos = 0;

try{
			//Make our array
			oSerializedValues = new DSBitArray( 0 );
			nPos = 0;

			//Now fill our array
			for( int i=0 ; i<oSerPrep.Length ; i++ )
			{
				//Data type (Int, Double, String, Bool, NotSet)
				nEnumType = oSerPrep.GetTypeEnum( i );
				oLoopItem = (DSMappedSerializeMapItem)m_oMapItems[ i ];

				if( nEnumType == enumType.NotSet )
				{
					//This means there is a gap, so make sure it gets filled
					continue;
				}
				else if( nEnumType == enumType.Long || nEnumType == enumType.Int )
				{
					nIntValue = System.Convert.ToInt64( oSerPrep.Get(i) );

					//Negative or not
					if( oLoopItem.CanBeNegative == true )
					{
						oSerializedValues.Set( nPos , ( nIntValue < 0 ) );
						nPos += 1;
					}

					//Store the actual value
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,oLoopItem.IntSize,nIntValue );
					nPos += oLoopItem.IntSize;

					#region Validity Checks - Size space exceeded
					//Calculate how many didgets needed
					nDidgets = DSBitArray.GetNumberOfBitsUsedIn( Math.Abs( nIntValue ) );
					//Time for some checks... does our number exceed our storage space?
					if( nDidgets > oLoopItem.IntSize )
					{
						sErrorMsg = "Error: You are trying to store the value " + nIntValue.ToString() + ", " +
								"which requires " + nDidgets.ToString() + " bits of storage space.  The " +
								"header size indicator is only " + oLoopItem.IntSize.ToString() + " bits long " +
								"which can accomidate a max value of " + Math.Pow( 2,oLoopItem.IntSize ).ToString() + ".";

						throw new System.Exception( sErrorMsg );
					}
					#endregion
				}
				else if( nEnumType == enumType.Double )
				{
					dValue = System.Convert.ToDouble( oSerPrep.Get(i) );

					//Negative or not
					if( oLoopItem.CanBeNegative == true )
					{
						oSerializedValues.Set( nPos , ( dValue < 0 ) );
						nPos += 1;
					}

					//Get our dec/int values
					if( dValue < 0 )
					{
						nIntValue = (long)Math.Ceiling( dValue );
					}
					else
					{
						nIntValue = (long)Math.Floor( dValue );
					}
					nDidgets = NumPlacesCanStoreWithXBits( oLoopItem.DecSize );
					nDecValue = (long)( ( Math.Abs( dValue ) - nIntValue ) * Math.Pow( 10,nDidgets ) );

					//Store the actual value
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,oLoopItem.IntSize,nIntValue );
					nPos += oLoopItem.IntSize;

					#region Validity Checks - Size space exceeded
					//Calculate how many didgets needed
					nDidgets = DSBitArray.GetNumberOfBitsUsedIn( Math.Abs( nIntValue ) );
					//Time for some checks... does our number exceed our storage space?
					if( nDidgets > oLoopItem.IntSize && bValidateDataSize == true )
					{
						sErrorMsg = "Error: You are trying to store the value " + dValue.ToString() + ", " +
								"which requires " + nDidgets.ToString() + " bits of integer storage space.  The " +
								"header size indicator is only " + oLoopItem.IntSize.ToString() + " bits long " +
								"which can accomidate a max value of " + Math.Pow( 2,oLoopItem.IntSize ).ToString() + ".";

						throw new System.Exception( sErrorMsg );
					}
					#endregion


					//Store the decimal value
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,oLoopItem.DecSize,nDecValue );
					nPos += oLoopItem.DecSize;
				}
				else if( nEnumType == enumType.String )
				{
					sValue = System.Convert.ToString( oSerPrep.Get(i) );

					//This is pointless because we tack empty space at the front
					//Strings are a little different, since we have to store a full char, 8 bits, anyway.  We 
					//start at the next 8 block (so that its human readable), and store a char count a the front.
					//So start by padding to the next 8 block
					//nPadLen = 8 - ( oSerializedValues.Length % 8 );
					//nPos += nPadLen;

					//Add the string
					nStringStartPos = nPos;
					for( int nStrIndx=0 ; nStrIndx < DSMisc.Min( sValue.Length,oLoopItem.IntSize ) ; nStrIndx++ )
					{
						DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,8,(int)sValue[ nStrIndx ] );
						nPos += 8;
					}

					//Set our end pos if we went short
					nPos = nStringStartPos + oLoopItem.IntSize * 8;
					
					#region Validity Checks - Size space exceeded
					//Time for some checks... does our number exceed our storage space?
					if( sValue.Length > oLoopItem.IntSize && bValidateDataSize == true )
					{
						sErrorMsg = "Error: You are trying to store the value <" + sValue + ">, " +
								"which requires " + sValue.Length.ToString() + " chars of storage space.  The " +
								"header size indicator is only " + oLoopItem.IntSize.ToString() + " chars long.";

						throw new System.Exception( sErrorMsg );
					}
					#endregion
				}
				else if( nEnumType == enumType.Bool )
				{
					//Bools are easy
					bValue = System.Convert.ToBoolean( oSerPrep.Get(i) );
					oSerializedValues.Set( nPos,bValue );
					nPos += 1;
				}
				else
				{
					throw new System.Exception( "Data type not known." );
				}
			}


			//Now that we know how much space we need, we must handle the roll-over.  By roll over I mean, 
			//what if we end up with only 7 bites.  It takes 8 bits to make a char, so we just concatinate
			//a zero on the end.  But now how does the deserialization process know if that zero is valid
			//or not?  Each field can be a minimum of 3 bits, which means that the system may think there
			//is a field on the end when there isn't really.  So... we will pad the front with zeros and
			//then a 1.  THEN the version.  So in a perfect world there will be one wasted bit at the,
			//beginning, but could be as many as 8.
			/*nPadLen = ( 8 - ( oSerializedValues.Length % 8 ) );
			oSerializedValues.Insert( 0,nPadLen );
			oSerializedValues.Set( nPadLen-1,true );
			nPos += nPadLen;*/

			//Convert us
			sRetVal = DSBitArray.ConvertBitArrayToString( oSerializedValues );
}
catch( System.Exception oEx )
{
	DSMisc.ShowErrors( oEx );
}

			return( sRetVal );
		}
		private int NumPlacesCanStoreWithXBits( int nNumBits )
		{
			int nDidgets = 0;
			long nMaxValue = 0;


			nMaxValue = (long)Math.Pow( 2,nNumBits );
			nDidgets = 1;
			for( ; nDidgets<nNumBits && Math.Pow( 10,nDidgets )<nMaxValue ; nDidgets++ )
			{
			}

			nDidgets--;


			return( nDidgets );
		}

		public DSMappedSerializesPrep DeSerialize( string sSerialized )
		{
			DSMappedSerializeMapItem oLoopItem = null;
			DSMappedSerializesPrep oRetVal = new DSMappedSerializesPrep( this );			
			enumType nEnumType = enumType.NotSet;
			DSBitArray oSerializedValues = null;
			object oValue = null;
			long nNegative = 0;
			double dIntValue = 0;
			double dDecValue = 0;
			string sValue = "";
			int nStringStartPos = 0;
			int nPos = 0;
			int nItemIndex = 0;
			int nTemp = 0;
			int nDidgets = 0;
			ArrayList oObjects = new ArrayList();


			//Get it ready for working
			oSerializedValues = DSBitArray.ConvertStringToBitArray( sSerialized );

			//Now that we know how much space we need, we must handle the roll-over.  By roll over I mean, 
			//what if we end up with only 7 bites.  It takes 8 bits to make a char, so we just concatinate
			//a zero on the end.  But now how does the deserialization process know if that zero is valid
			//or not?  Each field can be a minimum of 3 bits, which means that the system may think there
			//is a field on the end when there isn't really.  So... we will pad the front with zeros and
			//then a 1.  THEN the version.  So in a perfect world there will be one wasted bit at the,
			//beginning, but could be as many as 8.  
			//Start by finding the first 1, thats our real start
			/*while( nPos<8 && oSerializedValues.Get( nPos ) == false )
			{
				nPos++;
			}
			nPos++;*/

			while( nPos<oSerializedValues.Length )
			{
				//Data type (Int, Double, String, Bool, NotSet)
				oLoopItem = (DSMappedSerializeMapItem)m_oMapItems[ nItemIndex ];
				nEnumType = (enumType)oLoopItem.TypeEnum;

				nItemIndex++;

				//Now the actual value
				if( nEnumType == enumType.Long || nEnumType == enumType.Int )
				{
					if( oLoopItem.CanBeNegative == true )
					{
						//Negative or not
						nNegative = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,1 );
						nPos += 1;
					}

					//Calculate the actual value
					oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,oLoopItem.IntSize );
					if( nEnumType == enumType.Int )
					{
						oValue = Convert.ToInt32( oValue );
					}
					nPos += oLoopItem.IntSize;

					//Negativity
					if( nNegative == 1 )
					{
						if( nEnumType == enumType.Int )
						{
							oValue = Convert.ToInt32( oValue ) * 1;
						}
						else
						{
							oValue = Convert.ToInt64( oValue ) * -1;
						}
					}
				}
				else if( nEnumType == enumType.Double )
				{
					if( oLoopItem.CanBeNegative == true )
					{
						//Negative or not
						nNegative = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,1 );
						nPos += 1;
					}

					//Calculate the actual value
					oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,oLoopItem.IntSize );
					dIntValue = Convert.ToDouble( oValue );
					nPos += oLoopItem.IntSize;

					//Calculate the actual value
					oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,oLoopItem.DecSize );
					dDecValue = Convert.ToDouble( oValue );
					nPos += oLoopItem.DecSize;

					//Merge the num and denom.
					nDidgets = NumPlacesCanStoreWithXBits( oLoopItem.DecSize );
					dDecValue = dDecValue / Math.Pow( 10,nDidgets );
					oValue = dIntValue + dDecValue;

					//Negativity
					if( nNegative == 1 )
					{
						oValue = Convert.ToDouble( oValue ) * 1;
					}
				}
				else if( nEnumType == enumType.String )
				{
					//Strings are a little different, since we have to store a full char, 8 bits, anyway.  We 
					//start at the next 8 block (so that its human readable), and store a char count a the front.
					//So start by padding to the next 8 block
					//nPos += ( nPos % 8 );

					//Now get our string, until we find a zero
					nStringStartPos = nPos;
					nTemp = 1;
					while( nPos <= nStringStartPos + oLoopItem.IntSize * 8 && nTemp != 0 )
					{
						nTemp = (int)DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,8 );
						nPos += 8;

						if( nTemp!= 0 )
						{
							sValue += (char)nTemp;
						}
					}

					//We found our zero... on we go!
					nPos = nStringStartPos + oLoopItem.IntSize * 8;
					oValue = sValue;					
				}
				else if( nEnumType == enumType.Bool )
				{
					//Bools are easy
					oValue = oSerializedValues.Get( nPos );
					nPos += 1;					
				}
					//This means there is a gap
				else if( nEnumType == enumType.NotSet )
				{
					oValue = null;
				}

				oRetVal.Set( nItemIndex-1,oValue );
			}


			return( oRetVal );
		}

		
		public void AddMapItem( string sKey,Type oType )
		{
			int nIntSize = 0;
			int nDecSize = 0;


			if( oType == typeof( int ) )
			{
				nIntSize = 20;
				nDecSize = 0;
			}
			else if( oType == typeof( long ) )
			{
				nIntSize = 30;
				nDecSize = 0;
			}
			else if( oType == typeof( double ) )
			{
				nIntSize = 30;
				nDecSize = 17;
			}
			else if( oType == typeof( string ) )
			{
				nIntSize = 10;
				nDecSize = 0;
			}

			AddMapItem( sKey,nIntSize,nDecSize,oType );
		}
		public void AddMapItem( string sKey, 
								int nNumBitsUsedToStoreTheIntegerValue, int nNumOfPlacesUsedToStoreTheDecimalValue,
								Type oType )
		{
			DSMappedSerializeMapItem oMapItem = new DSMappedSerializeMapItem();

			oMapItem.Key = sKey;
			oMapItem.IntSize = nNumBitsUsedToStoreTheIntegerValue;
			oMapItem.DecSize = nNumOfPlacesUsedToStoreTheDecimalValue;
			oMapItem.TypeEnum = DSSerialize.GetTypeEnum( oType );

			m_oMapItems.Add( oMapItem );
			m_oMapItemsLookup.Add( sKey,m_oMapItems.Count-1 );
		}

		public DSMappedSerializeMapItem GetMapItem( string sKey )
		{
			int nIndex = (int)m_oMapItemsLookup.GetByKey( sKey );
			return( (DSMappedSerializeMapItem)m_oMapItems[ nIndex ] );
		}



		#region Properties
		public ArrayList MapItems
		{
			get
			{
				return( m_oMapItems );
			}
		}
		public DSSortedList MapItemsLookup
		{
			get
			{
				return( m_oMapItemsLookup );
			}
		}
		#endregion
	}

	public class DSMappedSerializesPrep : IEnumerable, IEnumerator
	{
		#region Properties
		private int m_nCurrentEnumeratorIndex = 0;
		private DSMappedSerialize m_oMap = null;
		private object[] m_oPrepValues = null;
		#endregion


		public DSMappedSerializesPrep( DSMappedSerialize oMap )
		{
			m_oMap = oMap;
			m_oPrepValues = new object[ m_oMap.m_oMapItems.Count ];
		}
		public IEnumerator GetEnumerator() 
		{ 
			return( (IEnumerator)this ); 
		} 

		public bool MoveNext() 
		{ 
			if( m_nCurrentEnumeratorIndex < m_oMap.m_oMapItems.Count ) 
			{ 
				m_nCurrentEnumeratorIndex++; 
				return true; 
			} 
			else 
			{
				return( false ); 
			} 
		} 

		public object Current 
		{ 
			get 
			{ 
				return( m_oPrepValues[ m_nCurrentEnumeratorIndex ] ); 
			} 
		} 

		public void Reset() 
		{ 
			m_nCurrentEnumeratorIndex = 0;
		} 


		public void Set( string sKey,object oValue )
		{
			int nIndex = (int)m_oMap.MapItemsLookup.GetByKey( sKey );
			Set( nIndex,oValue );
		}
		public void Set( int nIndex,object oValue )
		{
			m_oPrepValues[ nIndex ] = oValue;
		}
		#region Get Functions
		public object Get( string sKey )
		{
			if( m_oMap.MapItemsLookup.ContainsKey( sKey ) == true )
			{
				int nIndex = (int)m_oMap.MapItemsLookup.GetByKey( sKey );
				return( Get( nIndex,null ) );
			}
			else
			{
				return( null );
			}
		}
		public object Get( string sKey,object oDefaultIfNotFound )
		{
			if( m_oMap.MapItemsLookup.ContainsKey( sKey ) == true )
			{
				int nIndex = (int)m_oMap.MapItemsLookup.GetByKey( sKey );
				return( Get( nIndex,oDefaultIfNotFound ) );
			}
			else
			{
				return( oDefaultIfNotFound );
			}
		}

		public object Get( int nIndex )
		{
			return( Get( nIndex,null ) );
		}
		public object Get( int nIndex,object oDefaultIfNotFound )
		{
			if( nIndex < 0 ||
				nIndex > m_oPrepValues.Length ||
				m_oPrepValues[ nIndex ] == null )
			{
				return( oDefaultIfNotFound );
			}
			else
			{
				return( m_oPrepValues[ nIndex ] );
			}
		}

		public int GetInt( string sKey )
		{
			return( (int)Get( sKey,(int)0 ) );
		}
		public int GetInt( string sKey,int nDefaultIfNotFound )
		{
			return( (int)Get( sKey,nDefaultIfNotFound ) );
		}
		public int GetInt( int nIndex )
		{
			return( GetInt( nIndex,0 ) );
		}
		public int GetInt( int nIndex,int nDefaultIfNotFound )
		{
			return( (int)Get( nIndex,nDefaultIfNotFound ) );
		}
		public string GetString( string sKey )
		{
			return( (string)Get( sKey,"" ) );
		}
		public string GetString( string sKey,string sDefaultIfNotFound )
		{
			return( (string)Get( sKey,sDefaultIfNotFound ) );
		}
		public string GetString( int nIndex )
		{
			return( (string)Get( nIndex,"" ) );
		}
		public string GetString( int nIndex,string sDefaultIfNotFound )
		{
			return( (string)Get( nIndex,sDefaultIfNotFound ) );
		}
		public double GetDouble( string sKey )
		{
			return( (double)Get( sKey,(double)0 ) );
		}
		public double GetDouble( string sKey,double nDefaultIfNotFound )
		{
			return( (double)Get( sKey,nDefaultIfNotFound ) );
		}
		public double GetDouble( int nIndex )
		{
			return( (double)Get( nIndex,(double)0 ) );
		}
		public double GetDouble( int nIndex,double nDefaultIfNotFound )
		{
			return( (double)Get( nIndex,nDefaultIfNotFound ) );
		}
		public long GetLong( string sKey )
		{
			return( (long)Get( sKey,(long)0 ) );
		}
		public long GetLong( string sKey,long nDefaultIfNotFound )
		{
			return( (long)Get( sKey,nDefaultIfNotFound ) );
		}
		public long GetLong( int nIndex )
		{
			return( (long)Get( nIndex,(long)0 ) );
		}
		public long GetLong( int nIndex,long nDefaultIfNotFound )
		{
			return( (long)Get( nIndex,nDefaultIfNotFound ) );
		}
		public bool GetBool( string sKey )
		{
			return( (bool)Get( sKey,false ) );
		}
		public bool GetBool( string sKey,bool bDefaultIfNotFound )
		{
			return( (bool)Get( sKey,bDefaultIfNotFound ) );
		}
		public bool GetBool( int nIndex )
		{
			return( (bool)Get( nIndex,false ) );
		}
		public bool GetBool( int nIndex,bool bDefaultIfNotFound )
		{
			return( (bool)Get( nIndex,bDefaultIfNotFound ) );
		}
		#endregion

		public enumType GetTypeEnum( int nIndex )
		{
			object oValue = null;

			oValue = this.Get( nIndex );

			return( GetTypeEnum( oValue ) );
		}
		public enumType GetTypeEnum( object oValue )
		{
			enumType nRetVal = enumType.NotSet;


			if( oValue == null )							{ nRetVal = enumType.NotSet; }
			else if( oValue.GetType() == typeof( long ) )	{  nRetVal = enumType.Long; }
			else if( oValue.GetType() == typeof( int ) )	{ nRetVal = enumType.Int; }
			else if( oValue.GetType() == typeof( double ) )	{ nRetVal = enumType.Double; }
			else if( oValue.GetType() == typeof( string ) )	{ nRetVal = enumType.String; }
			else if( oValue.GetType() == typeof( bool ) )	{ nRetVal = enumType.Bool; }


			return( nRetVal );
		}



		#region Properties
		public int Length
		{
			get
			{
				return( m_oPrepValues.Length );
			}
		}
		#endregion
	}

	public class DSMappedSerializeMapItem
	{
		#region Properties
		public static int m_cNOSIZESPECIFIED = -1;
		private int m_nIntSize = 0;
		private int m_nDecSize = 0;
		private string m_sKey = "";
		private enumType m_oTypeEnum = enumType.NotSet;
		private bool m_bCanBeNegative = true;
		#endregion


		#region Properties
		public int IntSize
		{
			get
			{
				return( m_nIntSize );
			}
			set
			{
				m_nIntSize = value;
			}
		}
		public int DecSize
		{
			get
			{
				return( m_nDecSize );
			}
			set
			{
				m_nDecSize = value;
			}
		}
		public string Key
		{
			get
			{
				return( m_sKey );
			}
			set
			{
				m_sKey = value;
			}
		}
		public enumType TypeEnum
		{
			get
			{
				return( m_oTypeEnum );
			}
			set
			{
				m_oTypeEnum = value;
			}
		}
		public bool CanBeNegative
		{
			get
			{
				return( m_bCanBeNegative );
			}
			set
			{
				m_bCanBeNegative = value;
			}
		}
		#endregion
	}
}
