using System;
using System.Collections;


namespace DarkStrideToolbox
{
	public enum enumType
	{
		Long,
		Int,
		Double,
		String,
		Bool,
		NotSet,
		List,
		Float
	}


	public class DSSerialize : IEnumerable, IEnumerator
	{
		#region Properties
		private const int m_cINTLONG_INTSIZESTORAGE_NUMOFBITS = 7;
		private const int m_cDOUBLE_INTPRECISION_NUMOFPLACES = 7;
		private const int m_cDOUBLE_DECPRECISION_NUMOFPLACES = 7;
		private const int m_cDOUBLE_INTSIZESTORAGE_NUMOFBITS = 7;
		private const int m_cDOUBLE_DECSIZESTORAGE_NUMOFBITS = 7;		

		private int m_nCurrentEnumeratorIndex = 0;
		private object[] m_oPrepValues = null;
		#endregion


		public DSSerialize()
		{
			m_oPrepValues = new object[0];
		}
		public DSSerialize( string sSerialzed )
		{
			m_oPrepValues = new object[0];

			DeSerialize( sSerialzed );
		}


		public string Serialize()
		{
			return( Serialize( 0 ) );
		}
		public string Serialize( int nVersion )
		{
			DSBitArray oSerializedValues = null;
			enumType nEnumType = enumType.NotSet;
			ArrayList oList = null;
			string sRetVal = "";
			int nPadLen = 0;
			int nPos = 0;


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

			//We store a version for this class to
			DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,4,0 );
			nPos += 4;

			//and a version for this map (0-63)
			DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,6,nVersion );
			nPos += 6;

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

				if( nEnumType == enumType.List )
				{
					oList = (ArrayList)m_oPrepValues[i];

					//Store that this is a list
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,3,Convert.ToInt32( nEnumType ) );
					nPos += 3;

					//Now store how many items we have
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,15,(int)oList.Count );
					nPos += 15;

					//Now store the items
					for( int nListItemIndex=0 ; nListItemIndex<oList.Count ; nListItemIndex++ )
					{
						StoreVariable( oList[ nListItemIndex ],ref oSerializedValues,ref nPos );
					}
				}
				else
				{
					StoreVariable( m_oPrepValues[i],ref oSerializedValues,ref nPos );
				}
			}


			//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 );


			return( sRetVal );
		}
		private void StoreVariable( object oVariable,ref DSBitArray oSerializedValues,ref int nPos )
		{
			enumType nEnumType = enumType.NotSet;
			//string sRetVal = "";
			string sValue = "";
			long nIntValue = 0;
			long nDecValue = 0;
			double dValue = 0;
			bool bValue = false;
			int nDidgets = 0;


			//Data type (Int, Double, String, Bool, NotSet)
			nEnumType = GetTypeEnum( oVariable );
			DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,3,Convert.ToInt32( nEnumType ) );
			nPos += 3;

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

				//Negative or not
				oSerializedValues.Set( nPos , ( nIntValue < 0 ) );
				nPos += 1;
				//Calculate how many didgets needed
				nDidgets = DSBitArray.GetNumberOfBitsUsedIn( Math.Abs( nIntValue ) );
				//Size of storage in bytes
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,m_cINTLONG_INTSIZESTORAGE_NUMOFBITS,nDidgets );
				nPos += m_cINTLONG_INTSIZESTORAGE_NUMOFBITS;
				//Store the actual value
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,nDidgets,nIntValue );
				nPos += nDidgets;
			}
			else if( nEnumType == enumType.Double || nEnumType == enumType.Float )
			{
				//Get our int value
				dValue = System.Convert.ToDouble( oVariable );
				if( dValue < 0 )
				{
					nIntValue = (long)Math.Ceiling( dValue );
				}
				else
				{
					nIntValue = (long)Math.Floor( dValue );
				}
				//Get our dec value
				nDecValue = (long)( ( Math.Abs( dValue ) - nIntValue ) * Math.Pow( 10,m_cDOUBLE_DECPRECISION_NUMOFPLACES ) );

				//Negative or not
				oSerializedValues.Set( nPos , ( dValue < 0 ) );
				nPos += 1;

				//Calculate how many didgets needed
				nDidgets = DSBitArray.GetNumberOfBitsUsedIn( Math.Abs( nIntValue ) );
				//Size of storage in bytes
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,m_cDOUBLE_INTSIZESTORAGE_NUMOFBITS,nDidgets );
				nPos += m_cDOUBLE_INTSIZESTORAGE_NUMOFBITS;
				//Store the actual value
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,nDidgets,nIntValue );
				nPos += nDidgets;

				//Calculate how many didgets needed
				nDidgets = DSBitArray.GetNumberOfBitsUsedIn( (long)Math.Abs( nDecValue ) );
				//Size of storage in bytes
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,m_cDOUBLE_DECSIZESTORAGE_NUMOFBITS,nDidgets );
				nPos += m_cDOUBLE_DECSIZESTORAGE_NUMOFBITS;
				//Store the actual value
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,nDidgets,nDecValue );
				nPos += nDidgets;
			}
			else if( nEnumType == enumType.String )
			{
				sValue = System.Convert.ToString( oVariable );

				//Store how long the string is
				DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,15,sValue.Length );
				nPos += 15;

				//Add the string
				for( int nStrIndx=0 ; nStrIndx<sValue.Length ; nStrIndx++ )
				{
					DSBitArray.ConvertIntToBitArray( oSerializedValues,nPos,8,(int)sValue[ nStrIndx ] );
					nPos += 8;
				}
			}
			else if( nEnumType == enumType.Bool )
			{
				//Bools are easy
				bValue = System.Convert.ToBoolean( oVariable );
				oSerializedValues.Set( nPos , bValue );
				nPos += 1;
			}
			else
			{
				throw new System.Exception( "Data type not known." );
			}
		}


		public void DeSerialize( string sSerialized )
		{
			int nVersion = 0;

			DeSerialize( sSerialized,ref nVersion );
		}
		public void DeSerialize( string sSerialized,ref int nVersion )
		{
			enumType nEnumType = enumType.NotSet;
			DSBitArray oSerializedValues = null;
			ArrayList oList = null;
			object oValue = null;
			long nSerializingVersion = 0;
			long nDataType = 0;
			int nListCount = 0;
			int nPos = 0;
			int nItemIndex = 0;
			int nClassVersion = 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++;


			//nVersion (0-63)
			nSerializingVersion = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,4 );
			nPos += 4;

			//nVersion (0-63)
			nClassVersion = (int)DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,6 );
			nVersion = nClassVersion;
			nPos += 6;

			while( nPos<oSerializedValues.Length )
			{
				//Data type (Int, Double, String, Bool, NotSet)
				nDataType = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,3 );
				nEnumType = (enumType)nDataType;
				//Intentionally don't incrament, retreive variable does that
				//nPos += 3;

				if( nEnumType == enumType.List )
				{
					//Ok we want to consume the list type
					nPos += 3;
					
					//Now find out how many items we have
					nListCount = (int)DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,15 );
					nPos += 15;

					//Get all those items
					oList = new ArrayList();
					for( int nListItemIndex=0 ; nListItemIndex<nListCount ; nListItemIndex++ )
					{
						oValue = RetreiveVariable( ref oSerializedValues,ref nPos );
						oList.Add( oValue );
					}

					//Now return our list
					oValue = oList;
				}
				else
				{
					oValue = RetreiveVariable( ref oSerializedValues,ref nPos );
				}

				nItemIndex++;

				oObjects.Add( oValue );
			}


			//Copy the values over
			m_oPrepValues = new object[ oObjects.Count ];
			for( int i=0 ; i<oObjects.Count ; i++ )
			{
				m_oPrepValues[ i ] = oObjects[ i ];
			}
		}
		private object RetreiveVariable( ref DSBitArray oSerializedValues,ref int nPos )
		{
			enumType nEnumType = enumType.NotSet;
			object oValue = null;
			long nNumeratorSize = 0;
			long nDenomSize = 0;
			long nNegative = 0;
			long nDataType = 0;
			double dIntValue = 0;
			double dDecValue = 0;
			string sValue = "";
			int nStrLen = 0;
			int nTemp = 0;
			ArrayList oObjects = new ArrayList();


			//Data type (Int, Double, String, Bool, NotSet)
			nDataType = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,3 );
			nEnumType = (enumType)nDataType;
			nPos += 3;

			//Now the actual value
			if( nEnumType == enumType.Long || nEnumType == enumType.Int )
			{
				//Negative or not
				nNegative = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,1 );
				nPos += 1;
				//Size in bytes
				nNumeratorSize = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,m_cINTLONG_INTSIZESTORAGE_NUMOFBITS );
				nPos += m_cINTLONG_INTSIZESTORAGE_NUMOFBITS;
				//Calculate the actual value
				oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,nNumeratorSize );
				if( nEnumType == enumType.Int )
				{
					oValue = Convert.ToInt32( oValue );
				}
				else
				{
					oValue = Convert.ToInt64( oValue );
				}
				nPos += (int)nNumeratorSize;

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

				//Size in bytes
				nNumeratorSize = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,m_cDOUBLE_INTSIZESTORAGE_NUMOFBITS );
				nPos += m_cDOUBLE_INTSIZESTORAGE_NUMOFBITS;
				//Calculate the actual value
				oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,nNumeratorSize );
				dIntValue = Convert.ToDouble( oValue );
				nPos += (int)nNumeratorSize;

				//Size in bytes
				nDenomSize = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,m_cDOUBLE_DECSIZESTORAGE_NUMOFBITS );
				nPos += m_cDOUBLE_DECSIZESTORAGE_NUMOFBITS;
				//Calculate the actual value
				oValue = DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,nDenomSize );
				dDecValue = Convert.ToDouble( oValue );
				nPos += (int)nDenomSize;

				//Merge the num and denom.
				dDecValue = dDecValue / Math.Pow( 10,m_cDOUBLE_DECPRECISION_NUMOFPLACES );
				oValue = dIntValue + dDecValue;
				if( nEnumType == enumType.Double )
				{
					oValue = Convert.ToDouble( oValue );
				}
				else
				{
					oValue = (float)Convert.ToDouble( oValue );
				}

				//Negativity
				if( nNegative == 1 )
				{
					if( nEnumType == enumType.Double )
					{
						oValue = Convert.ToDouble( oValue ) * 1;
					}
					else
					{
						oValue = (float)Convert.ToDouble( oValue ) * -1;
					}
				}
			}
			else if( nEnumType == enumType.String )
			{
				nStrLen = (int)DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,15 );
				nPos += 15;

				//Now get our string, until we find a zero
				for( int nStrPos=0 ; nStrPos<nStrLen ; nStrPos++ )
				{
					nTemp = (int)DSBitArray.ConvertBitArrayToInt( oSerializedValues,nPos,8 );
					nPos += 8;

					sValue += (char)nTemp;
				}

				//We got it all, exit!
				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;
			}


			return( oValue );
		}

        
		#region Enumerator Functions
		public IEnumerator GetEnumerator() 
		{ 
			return( (IEnumerator)this ); 
		} 

		public bool MoveNext() 
		{ 
			if( m_nCurrentEnumeratorIndex < m_oPrepValues.Length ) 
			{ 
				m_nCurrentEnumeratorIndex++; 
				return true; 
			} 
			else 
			{
				return( false ); 
			} 
		} 

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

		public void Reset() 
		{ 
			m_nCurrentEnumeratorIndex = 0;
		} 

		#endregion

		public void Set( int nIndex,object oValue )
		{
			object[] oNewStorage = null;

			if( nIndex >= m_oPrepValues.Length )
			{
				oNewStorage = new object[ nIndex + 1 ];

				for( int i=0 ; i<m_oPrepValues.Length ; i++ )
				{
					oNewStorage[ i ] = m_oPrepValues[ i ];
				}

				m_oPrepValues = oNewStorage;
			}

			m_oPrepValues[ nIndex ] = oValue;
		}
		public void SetList( int nIndex )
		{
			Set( nIndex,new ArrayList() );
		}
		public void SetListItem( int nIndex,object oValue )
		{
			object oListObj = null;
			ArrayList oArrayList =null;


			//Verify that this item is a list
			oListObj = Get( nIndex );
			if( GetTypeEnum( oListObj ) != enumType.List )
			{
				throw new System.Exception( "Item " + nIndex.ToString() + " is not a list." );
			}

			//Get sorted list
			oArrayList = (ArrayList)oListObj;

			//Append item
			oArrayList.Add( oValue );

			//Save it back
			Set( nIndex,oArrayList );
		}
		#region Get Functions
		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( int nIndex )
		{
			return( GetInt( nIndex,0 ) );
		}
		public int GetInt( int nIndex,int nDefaultIfNotFound )
		{
			return( (int)Get( nIndex,nDefaultIfNotFound ) );
		}
		public string GetString( int nIndex )
		{
			return( (string)Get( nIndex,"" ) );
		}
		public string GetString( int nIndex,string sDefaultIfNotFound )
		{
			return( (string)Get( nIndex,sDefaultIfNotFound ) );
		}
		public double GetDouble( int nIndex )
		{
			return( (double)Get( nIndex,(double)0 ) );
		}
		public double GetDouble( int nIndex,double nDefaultIfNotFound )
		{
			return( (double)Get( nIndex,nDefaultIfNotFound ) );
		}
		public float GetFloat( int nIndex )
		{
			return( (float)Get( nIndex,(float)0 ) );
		}
		public float GetFloat( int nIndex,float nDefaultIfNotFound )
		{
			return( (float)Get( nIndex,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( int nIndex )
		{
			return( (bool)Get( nIndex,false ) );
		}
		public bool GetBool( int nIndex,bool bDefaultIfNotFound )
		{
			return( (bool)Get( nIndex,bDefaultIfNotFound ) );
		}
		public ArrayList GetList( int nIndex )
		{
			object oListObj = null;
			ArrayList oArrayList =null;


			//Verify that this item is a list
			oListObj = Get( nIndex );
			if( GetTypeEnum( oListObj ) != enumType.List )
			{
				throw new System.Exception( "Item " + nIndex.ToString() + " is not a list." );
			}

			//Get sorted list
			oArrayList = (ArrayList)oListObj;

			
			return( oArrayList );
		}
		#endregion

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

			oValue = this.Get( nIndex );

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

			if( oValue == null )	
			{ 
				nRetVal = enumType.NotSet; 
			}
			else
			{
				nRetVal = GetTypeEnum( oValue.GetType() );
			}

			return( nRetVal );
		}
		public static enumType GetTypeEnum( Type oType )
		{
			enumType nRetVal = enumType.NotSet;

			     if( oType == typeof( long ) )	{  nRetVal = enumType.Long; }
			else if( oType == typeof( int ) )	{ nRetVal = enumType.Int; }
			else if( oType == typeof( double ) ){ nRetVal = enumType.Double; }
			else if( oType == typeof( string ) ){ nRetVal = enumType.String; }
			else if( oType == typeof( bool ) )	{ nRetVal = enumType.Bool; }
			else if( oType == typeof( ArrayList ) )	{ nRetVal = enumType.List; }
			else if( oType == typeof( float ) )	{ nRetVal = enumType.Float; }

			return( nRetVal );
		}



		#region Properties
		#endregion
	}
}
