//
// System.Web.UI.ObjectStateFormatter
//
// Authors:
//	Ben Maurer (bmaurer@users.sourceforge.net)
//
// (C) 2003 Ben Maurer
//

//#define TRACE

using System.Collections;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.Util;
using System.Diagnostics;

namespace System.Web.UI {
	#if NET_1_2
		public
	#else
		internal
	#endif
	sealed class ObjectStateFormatter : IFormatter {
		public object Deserialize (Stream inputStream)
		{
			if (inputStream == null)
				throw new ArgumentNullException ("inputStream");

			return DeserializeObject (new BinaryReader (inputStream));
		}
		
		public object Deserialize (string inputString)
		{
			if (inputString == null)
				throw new ArgumentNullException ("inputString");

			if (inputString == "")
				return null;

			return Deserialize (new MemoryStream (Convert.FromBase64String (inputString)));
		}
		
		public string Serialize (object stateGraph)
		{
			if (stateGraph == null)
				return "";
			
			MemoryStream ms = new MemoryStream ();
			Serialize (ms, stateGraph);
			
			#if TRACE
				ms.WriteTo (File.OpenWrite (Path.GetTempFileName ()));
			#endif
			
			return Convert.ToBase64String (ms.GetBuffer (), 0, (int) ms.Length);
		}
		
		public void Serialize (Stream outputStream, object stateGraph)
		{
			if (outputStream == null)
				throw new ArgumentNullException ("outputStream");

			if (stateGraph == null)
				throw new ArgumentNullException ("stateGraph");

			SerializeValue (new BinaryWriter (outputStream), stateGraph);
		}
		
		void SerializeValue (BinaryWriter w, object o)
		{
			ObjectFormatter.WriteObject (w, o, new WriterContext ());
		}
		
		object DeserializeObject (BinaryReader r)
		{
			return ObjectFormatter.ReadObject (r, new ReaderContext ());
		}
		
		#region IFormatter
		
		object IFormatter.Deserialize (Stream serializationStream)
		{
			return Deserialize (serializationStream);
		}
		
		void IFormatter.Serialize (Stream serializationStream, object stateGraph)
		{
			Serialize (serializationStream, stateGraph);
		}
		
		SerializationBinder IFormatter.Binder {
			get { return null; }
			set { }
		}
		
		StreamingContext IFormatter.Context {
			get { return new StreamingContext (StreamingContextStates.All); }
			set { }
		}
		
		ISurrogateSelector IFormatter.SurrogateSelector {
			get { return null; }
			set { }
		}
		
		#endregion

		#region Object Readers/Writers
		
		class WriterContext {
			Hashtable cache;
			short nextKey = 0;
			
			public bool RegisterCache (object o, out short key)
			{
				if (cache == null) {
					cache = new Hashtable ();
					cache.Add (o, key = nextKey++);
					return false;
				}
				
				object posKey = cache [o];
				if (posKey == null) {
					cache.Add (o, key = nextKey++);
					return false;
				}
				
				key = (short) posKey;
				return true;
			}
		}
		
		class ReaderContext {
			ArrayList cache;
			
			public void CacheItem (object o)
			{
				if (cache == null)
					cache = new ArrayList ();
				
				cache.Add (o);
			}
			
			public object GetCache (short key)
			{
				return cache [key];
			}
		}
		
		abstract class ObjectFormatter {
			static readonly Hashtable writeMap = new Hashtable ();
			static ObjectFormatter [] readMap = new ObjectFormatter [256];
			static BinaryObjectFormatter binaryObjectFormatter;
			static TypeFormatter typeFormatter;
			static EnumFormatter enumFormatter;
			static SingleRankArrayFormatter singleRankArrayFormatter;
			
			static ObjectFormatter ()
			{			
				new StringFormatter ().Register ();
				new Int64Formatter ().Register ();
				new Int32Formatter ().Register ();
				new Int16Formatter ().Register ();
				new ByteFormatter ().Register ();
				new BooleanFormatter ().Register ();
				new CharFormatter ().Register ();
				new DateTimeFormatter ().Register ();
				new PairFormatter ().Register ();
				new TripletFormatter ().Register ();
				new ArrayListFormatter ().Register ();
				new HashtableFormatter ().Register ();
				new ObjectArrayFormatter ().Register ();
				new UnitFormatter ().Register ();
				new FontUnitFormatter ().Register ();
				
				new ColorFormatter ().Register ();
				
				enumFormatter = new EnumFormatter ();
				enumFormatter.Register ();
				
				typeFormatter = new TypeFormatter ();
				typeFormatter.Register ();
				
				singleRankArrayFormatter = new SingleRankArrayFormatter ();
				singleRankArrayFormatter.Register ();
				
				binaryObjectFormatter = new BinaryObjectFormatter ();
				binaryObjectFormatter.Register ();
			}
		
			// 0 == null
			static byte nextId = 1;
			
			public ObjectFormatter ()
			{
				PrimaryId = nextId ++;
				if (NumberOfIds == 1)
					return;
				
				SecondaryId = nextId ++;
				if (NumberOfIds == 2)
					return;
				
				TertiaryId = nextId ++;
				if (NumberOfIds == 3)
					return;
				
				throw new Exception ();
			}
			
			protected readonly byte PrimaryId, SecondaryId = 255, TertiaryId = 255;
			
			protected abstract void Write (BinaryWriter w, object o, WriterContext ctx);
			protected abstract object Read (byte token, BinaryReader r, ReaderContext ctx);
			protected abstract Type Type { get; }
			protected virtual int NumberOfIds { get { return 1; } }
			
			public virtual void Register ()
			{
				writeMap [Type] = this;
				readMap [PrimaryId] = this;
				if (SecondaryId != 255) {
					readMap [SecondaryId] = this;
					if (TertiaryId != 255)
						readMap [TertiaryId] = this;
				}
			}
			
			public static void WriteObject (BinaryWriter w, object o, WriterContext ctx)
			{
				#if TRACE
				if (o != null) {
					Trace.WriteLine (String.Format ("Writing {0} (type: {1})", o, o.GetType ()));
					Trace.Indent ();
				} else {
					Trace.WriteLine ("Writing null");
				}
				long pos = w.BaseStream.Position;
				#endif
				
				if (o == null) {
					w.Write ((byte) 0);
					return;
				}
				
				Type t = o.GetType ();
				
				ObjectFormatter fmt = writeMap [t] as ObjectFormatter;
				if (fmt == null) {
					// Handle abstract types here
					
					if (o is Type)
						fmt = typeFormatter;
					else if (t.IsEnum)
						fmt = enumFormatter;
					else if (t.IsArray && ((Array) o).Rank == 1)
						fmt = singleRankArrayFormatter;
					else
						fmt = binaryObjectFormatter;
				}

				fmt.Write (w, o, ctx);
				#if TRACE
				Trace.Unindent ();
				Trace.WriteLine (String.Format ("Wrote {0} (type: {1}) {2} bytes", o, o.GetType (), w.BaseStream.Position - pos));
				#endif
			}
			
			public static object ReadObject (BinaryReader r, ReaderContext ctx)
			{
				byte sig = r.ReadByte ();
				
				if (sig == 0)
					return null;
				
				return readMap [sig].Read (sig, r, ctx);
			}
			
			protected void Write7BitEncodedInt (BinaryWriter w, int value)
			{
				do {
					int high = (value >> 7) & 0x01ffffff;
					byte b = (byte)(value & 0x7f);
	
					if (high != 0) {
						b = (byte)(b | 0x80);
					}
	
					w.Write(b);
					value = high;
				} while(value != 0);
			}
			
			protected int Read7BitEncodedInt (BinaryReader r)
			{
				int ret = 0;
				int shift = 0;
				byte b;
	
				do {
					b = r.ReadByte();
					
					ret = ret | ((b & 0x7f) << shift);
					shift += 7;
				} while ((b & 0x80) == 0x80);
	
				return ret;
			}
		}
		
		#region Primitive Formatters
		class StringFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				short key;
				if (ctx.RegisterCache (o, out key)) {
					w.Write (SecondaryId);
					w.Write (key);
				} else {
					w.Write (PrimaryId);
					w.Write ((string)o);
				}
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				if (token == PrimaryId) {
					string s = r.ReadString ();
					ctx.CacheItem (s);
					return s;
				} else {
					return ctx.GetCache (r.ReadInt16 ());
				}
			}
			protected override Type Type {
				get { return typeof (string); }
			}
			
			protected override int NumberOfIds {
				get { return 2; }
			}
		}
		
		class Int64Formatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				w.Write ((long)o);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return r.ReadInt64 ();
			}
			protected override Type Type {
				get { return typeof (long); }
			}
		}
		
		class Int32Formatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				int i = (int) o;
				if ((int)(byte) i == i) {
					w.Write (SecondaryId);
					w.Write ((byte) i);
				} else {
					w.Write (PrimaryId);
					w.Write (i);
				}
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				if (token == PrimaryId)
					return r.ReadInt32 ();
				else
					return (int) r.ReadByte ();
			}
			protected override Type Type {
				get { return typeof (int); }
			}
			
			protected override int NumberOfIds {
				get { return 2; }
			}
		}
		
		class Int16Formatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				w.Write ((short)o);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return r.ReadInt16 ();
			}
			protected override Type Type {
				get { return typeof (short); }
			}
		}
		
		class ByteFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				w.Write ((byte)o);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return r.ReadByte ();
			}
			protected override Type Type {
				get { return typeof (byte); }
			}
		}
		
		class BooleanFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				if ((bool)o == true)
					w.Write (PrimaryId);
				else
					w.Write (SecondaryId);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return token == PrimaryId;
			}
			
			protected override Type Type {
				get { return typeof (bool); }
			}
			
			protected override int NumberOfIds {
				get { return 2; }
			}
		}
		
		class CharFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				w.Write ((char) o);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return r.ReadChar ();
			}
			
			protected override Type Type {
				get { return typeof (char); }
			}
		}
		
		class DateTimeFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				w.Write (((DateTime) o).Ticks);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return new DateTime (r.ReadInt64 ());
			}
			
			protected override Type Type {
				get { return typeof (DateTime); }
			}
		}
		
		class PairFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				Pair p = (Pair) o;
				w.Write (PrimaryId);
				WriteObject (w, p.First, ctx);
				WriteObject (w, p.Second, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				Pair p = new Pair ();
				p.First = ReadObject (r, ctx);
				p.Second = ReadObject (r, ctx);
				return p;
			}
			
			protected override Type Type {
				get { return typeof (Pair); }
			}
		}
		
		class TripletFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				Triplet t = (Triplet) o;
				w.Write (PrimaryId);
				WriteObject (w, t.First, ctx);
				WriteObject (w, t.Second, ctx);
				WriteObject (w, t.Third, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				Triplet t = new Triplet ();
				t.First = ReadObject (r, ctx);
				t.Second = ReadObject (r, ctx);
				t.Third = ReadObject (r, ctx);
				return t;
			}
			
			protected override Type Type {
				get { return typeof (Triplet); }
			}
		}
		
		class ArrayListFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				ArrayList l = (ArrayList) o;
				
				w.Write (PrimaryId);
				Write7BitEncodedInt (w, l.Count);
				foreach (object i in l)
					WriteObject (w, i, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				int len = Read7BitEncodedInt (r);
				ArrayList l = new ArrayList (len);
				
				for (int i = 0; i < len; i++)
					l.Add (ReadObject (r, ctx));
				
				return l;
			}
			
			protected override Type Type {
				get { return typeof (ArrayList); }
			}
		}
		
		class HashtableFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				Hashtable ht = (Hashtable) o;
				
				w.Write (PrimaryId);
				Write7BitEncodedInt (w, ht.Count);
				foreach (DictionaryEntry de in ht) {
					WriteObject (w, de.Key, ctx);
					WriteObject (w, de.Value, ctx);
				}
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				int len = Read7BitEncodedInt (r);
				Hashtable ht = new Hashtable (len);
				
				for (int i = 0; i < len; i++) {
					object key = ReadObject (r, ctx);
					object val = ReadObject (r, ctx);
					
					ht.Add (key, val);
				}
				
				return ht;
			}
			
			protected override Type Type {
				get { return typeof (Hashtable); }
			}
		}
		
		class ObjectArrayFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				object [] val = (object []) o;
				
				w.Write (PrimaryId);
				Write7BitEncodedInt (w, val.Length);
				foreach (object i in val)
					WriteObject (w, i, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				int len = Read7BitEncodedInt (r);
				object [] ret = new object [len];
				
				for (int i = 0; i < len; i++)
					ret [i] = ReadObject (r, ctx);
				
				return ret;
			}
			
			protected override Type Type {
				get { return typeof (object []); }
			}
		}
		
		#endregion
		
		#region System.Web Optimizations
		class ColorFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				Color c = (Color) o;
				
				if (!c.IsKnownColor) {
					w.Write (PrimaryId);
					w.Write (c.ToArgb ());
				} else {
					w.Write (SecondaryId);
					w.Write ((int) c.ToKnownColor ());
				}
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				if (token == PrimaryId)
					return Color.FromArgb (r.ReadInt32 ());
				else
					return Color.FromKnownColor ((KnownColor) r.ReadInt32 ());
			}
			
			protected override Type Type {
				get { return typeof (Color); }
			}
			
			protected override int NumberOfIds {
				get { return 2; }
			}
		}
		
		#endregion
		
		#region Special Formatters
		class EnumFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				object value = Convert.ChangeType (o, ((Enum) o).GetTypeCode ());
				w.Write (PrimaryId);
				WriteObject (w, o.GetType (), ctx);
				WriteObject (w, value, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				Type t = (Type) ReadObject (r, ctx);
				object value = ReadObject (r, ctx);
				
				return Enum.ToObject (t, value);
			}
			protected override Type Type {
				get { return typeof (Enum); }
			}
		}
		
		class TypeFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				short key;
				if (ctx.RegisterCache (o, out key)) {
					w.Write (SecondaryId);
					w.Write (key);
				} else {
					w.Write (PrimaryId);
					w.Write (((Type) o).FullName);
					
					// We should cache the name of the assembly
					w.Write (((Type) o).Assembly.FullName);
				}
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				if (token == PrimaryId) {
					string type = r.ReadString ();
					string assembly = r.ReadString ();
					
					Type t = Assembly.Load (assembly).GetType (type);
					ctx.CacheItem (t);
					return t;
				} else {
					return ctx.GetCache (r.ReadInt16 ());
				}
			}
			
			protected override Type Type {
				get { return typeof (Type); }
			}
			
			protected override int NumberOfIds {
				get { return 2; }
			}
		}
		
		class SingleRankArrayFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				Array val = (Array) o;
				
				w.Write (PrimaryId);
				WriteObject (w, val.GetType ().GetElementType (), ctx);
				
				Write7BitEncodedInt (w, val.Length);
				foreach (object i in val)
					WriteObject (w, i, ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				Type t = (Type) ReadObject (r, ctx);
				int len = Read7BitEncodedInt (r);
				Array val = Array.CreateInstance (t, len);
				
				for (int i = 0; i < len; i++)
					val.SetValue (ReadObject (r, ctx), i);
				
				return val;
			}
			
			protected override Type Type {
				get { return typeof (Array); }
			}
		}
		
		class FontUnitFormatter : StringFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				base.Write (w, o.ToString (), ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return FontUnit.Parse ((string) base.Read (token, r, ctx));
			}
			
			protected override Type Type {
				get { return typeof (FontUnit); }
			}
		}

		class UnitFormatter : StringFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				base.Write (w, o.ToString (), ctx);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				return Unit.Parse ((string) base.Read (token, r, ctx));
			}
			
			protected override Type Type {
				get { return typeof (Unit); }
			}
		}

		class BinaryObjectFormatter : ObjectFormatter {
			protected override void Write (BinaryWriter w, object o, WriterContext ctx)
			{
				w.Write (PrimaryId);
				
				MemoryStream ms = new MemoryStream (128);
				new BinaryFormatter ().Serialize (ms, o);
				
				byte [] buf = ms.GetBuffer ();
				Write7BitEncodedInt (w, buf.Length);
				w.Write (buf, 0, buf.Length);
			}
			
			protected override object Read (byte token, BinaryReader r, ReaderContext ctx)
			{
				int len = Read7BitEncodedInt (r);
				byte [] buf = r.ReadBytes (len);
				if (buf.Length != len)
					throw new Exception ();
				
				return new BinaryFormatter ().Deserialize (new MemoryStream (buf));
			}
			
			protected override Type Type {
				get { return typeof (object); }
			}
		}
		
		#endregion
		
		#endregion
	}
}
