//
// XmlBinaryDictionaryWriter.cs
//
// Author:
//	Atsushi Enomoto  <atsushi@ximian.com>
//
// Copyright (C) 2005, 2007 Novell, Inc.  http://www.novell.com
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

using BF = System.Xml.XmlBinaryFormat;

namespace System.Xml
{
	internal partial class XmlBinaryDictionaryWriter : XmlDictionaryWriter
	{
		class MyBinaryWriter : BinaryWriter
		{
			public MyBinaryWriter (Stream s)
				: base (s)
			{
			}

			public void WriteFlexibleInt (int value)
			{
				Write7BitEncodedInt (value);
			}
		}

		#region Fields
		MyBinaryWriter original, writer, buffer_writer;
		IXmlDictionary dict_ext;
		XmlDictionary dict_int = new XmlDictionary ();
		XmlBinaryWriterSession session;
		bool owns_stream;
		Encoding utf8Enc = new UTF8Encoding ();
		MemoryStream buffer = new MemoryStream ();

		const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
		const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";

		WriteState state = WriteState.Start;
		bool open_start_element = false;
		// transient current node info
		List<KeyValuePair<string,object>> namespaces = new List<KeyValuePair<string,object>> ();
		string xml_lang = null;
		XmlSpace xml_space = XmlSpace.None;
		int ns_index = 0;
		// stacked info
		Stack<int> ns_index_stack = new Stack<int> ();
		Stack<string> xml_lang_stack = new Stack<string> ();
		Stack<XmlSpace> xml_space_stack = new Stack<XmlSpace> ();
		Stack<string> element_ns_stack = new Stack<string> ();
		string element_ns = String.Empty;
		int element_count;
		string element_prefix; // only meaningful at Element state
		// current attribute info
		string attr_value;
		string current_attr_prefix;
		object current_attr_name, current_attr_ns;
		bool attr_typed_value;
		SaveTarget save_target;

		enum SaveTarget {
			None,
			Namespaces,
			XmlLang,
			XmlSpace
		}

		// XmlWriterSettings support

		#endregion

		#region Constructors

		public XmlBinaryDictionaryWriter (Stream stream,
			IXmlDictionary dictionary,
			XmlBinaryWriterSession session, bool ownsStream)
		{
			if (dictionary == null)
				dictionary = new XmlDictionary ();
			if (session == null)
				session = new XmlBinaryWriterSession ();

			original = new MyBinaryWriter (stream);
			this.writer = original;
			buffer_writer = new MyBinaryWriter (buffer);
			this.dict_ext = dictionary;
			this.session = session;
			owns_stream = ownsStream;

			AddNamespace ("xml", "http://www.w3.org/XML/1998/namespace");
			AddNamespace ("xml", "http://www.w3.org/2000/xmlns/");
			ns_index = 2;
		}

		#endregion

		#region Properties

		public override WriteState WriteState {
			get { return state; }
		}
		
		public override string XmlLang {
			get { return xml_lang; }
		}

		public override XmlSpace XmlSpace {
			get { return xml_space; }
		}

		#endregion

		#region Methods

		private void AddMissingElementXmlns ()
		{
			// push new namespaces to manager.
			for (int i = ns_index; i < namespaces.Count; i++) {
				var ent = namespaces [i];
				string prefix = (string) ent.Key;
				string ns = ent.Value as string;
				XmlDictionaryString dns = ent.Value as XmlDictionaryString;
				if (ns != null) {
					if (prefix.Length > 0) {
						writer.Write (BF.PrefixNSString);
						writer.Write (prefix);
					}
					else
						writer.Write (BF.DefaultNSString);
					writer.Write (ns);
				} else {
					if (prefix.Length > 0) {
						writer.Write (BF.PrefixNSIndex);
						writer.Write (prefix);
					}
					else
						writer.Write (BF.DefaultNSIndex);
					WriteDictionaryIndex (dns);
				}
			}
			ns_index = namespaces.Count;
		}

		private void CheckState ()
		{
			if (state == WriteState.Closed) {
				throw new InvalidOperationException ("The Writer is closed.");
			}
		}

		void ProcessStateForContent ()
		{
			CheckState ();

			if (state == WriteState.Element)
				CloseStartElement ();

			ProcessPendingBuffer (false, false);
			if (state != WriteState.Attribute)
				writer = buffer_writer;
		}

		void ProcessTypedValue ()
		{
			ProcessStateForContent ();
			if (state == WriteState.Attribute) {
				if (attr_typed_value)
					throw new InvalidOperationException (String.Format ("A typed value for the attribute '{0}' in namespace '{1}' was already written", current_attr_name, current_attr_ns));
				attr_typed_value = true;
			}
		}

		void ProcessPendingBuffer (bool last, bool endElement)
		{
			if (buffer.Position > 0) {
				byte [] arr = buffer.GetBuffer ();
				if (endElement)
					arr [0]++;
				original.Write (arr, 0, (int) buffer.Position);
				buffer.SetLength (0);
			}
			if (last)
				writer = original;
		}

		public override void Close ()
		{
			CloseOpenAttributeAndElements ();

			if (owns_stream)
				writer.Close ();
			else if (state != WriteState.Closed)
				writer.Flush ();
			state = WriteState.Closed;
		}

		private void CloseOpenAttributeAndElements ()
		{
			CloseStartElement ();

			 while (element_count > 0)
				WriteEndElement ();
		}

		private void CloseStartElement ()
		{
			if (!open_start_element)
				return;

			if (state == WriteState.Attribute)
				WriteEndAttribute ();

			AddMissingElementXmlns ();

			state = WriteState.Content;
			open_start_element = false;
		}

		public override void Flush ()
		{
			writer.Flush ();
		}

		public override string LookupPrefix (string ns)
		{
			if (ns == null || ns == String.Empty)
				throw new ArgumentException ("The Namespace cannot be empty.");

			var de = namespaces.LastOrDefault (i => i.Value.ToString () == ns);
			return de.Key; // de is KeyValuePair and its default key is null.
		}

		public override void WriteBase64 (byte[] buffer, int index, int count)
		{
			if (count < 0)
				throw new IndexOutOfRangeException ("Negative count");
			ProcessStateForContent ();

			if (count < 0x100) {
				writer.Write (BF.Bytes8);
				writer.Write ((byte) count);
				writer.Write (buffer, index, count);
			} else if (count < 0x10000) {
				writer.Write (BF.Bytes8);
				writer.Write ((ushort) count);
				writer.Write (buffer, index, count);
			} else {
				writer.Write (BF.Bytes32);
				writer.Write (count);
				writer.Write (buffer, index, count);
			}
		}

		public override void WriteCData (string text)
		{
			if (text.IndexOf ("]]>") >= 0)
				throw new ArgumentException ("CDATA section cannot contain text \"]]>\".");

			ProcessStateForContent ();

			WriteTextBinary (text);
		}

		public override void WriteCharEntity (char ch)
		{
			WriteChars (new char [] {ch}, 0, 1);
		}

		public override void WriteChars (char[] buffer, int index, int count)
		{
			ProcessStateForContent ();

			int blen = Encoding.UTF8.GetByteCount (buffer, index, count);

			if (blen == 0)
				writer.Write (BF.EmptyText);
			else if (count == 1 && buffer [0] == '0')
				writer.Write (BF.Zero);
			else if (count == 1 && buffer [0] == '1')
				writer.Write (BF.One);
			else if (blen < 0x100) {
				writer.Write (BF.Chars8);
				writer.Write ((byte) blen);
				writer.Write (buffer, index, count);
			} else if (blen < 0x10000) {
				writer.Write (BF.Chars16);
				writer.Write ((ushort) blen);
				writer.Write (buffer, index, count);
			} else {
				writer.Write (BF.Chars32);
				writer.Write (blen);
				writer.Write (buffer, index, count);
			}
		}

		public override void WriteComment (string text)
		{
			if (text.EndsWith("-"))
				throw new ArgumentException ("An XML comment cannot contain \"--\" inside.");
			else if (text.IndexOf("--") > 0)
				throw new ArgumentException ("An XML comment cannot end with \"-\".");

			ProcessStateForContent ();

			if (state == WriteState.Attribute)
				throw new InvalidOperationException ("Comment node is not allowed inside an attribute");

			writer.Write (BF.Comment);
			writer.Write (text);
		}

		public override void WriteDocType (string name, string pubid, string sysid, string subset)
		{
			throw new NotSupportedException ("This XmlWriter implementation does not support document type.");
		}

		public override void WriteEndAttribute ()
		{
			if (state != WriteState.Attribute)
				throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");

			CheckState ();

			if (attr_value == null)
				attr_value = String.Empty;

			switch (save_target) {
			case SaveTarget.XmlLang:
				xml_lang = attr_value;
				goto default;
			case SaveTarget.XmlSpace:
				switch (attr_value) {
				case "preserve":
					xml_space = XmlSpace.Preserve;
					break;
				case "default":
					xml_space = XmlSpace.Default;
					break;
				default:
					throw new ArgumentException (String.Format ("Invalid xml:space value: '{0}'", attr_value));
				}
				goto default;
			case SaveTarget.Namespaces:
				if (current_attr_name.ToString ().Length > 0 && attr_value.Length == 0)
					throw new ArgumentException ("Cannot use prefix with an empty namespace.");

				AddNamespaceChecked (current_attr_name.ToString (), attr_value);
				break;
			default:
				if (!attr_typed_value)
					WriteTextBinary (attr_value);
				break;
			}

			if (current_attr_prefix.Length > 0 && save_target != SaveTarget.Namespaces)
				AddNamespaceChecked (current_attr_prefix, current_attr_ns);

			state = WriteState.Element;
			current_attr_prefix = null;
			current_attr_name = null;
			current_attr_ns = null;
			attr_value = null;
			attr_typed_value = false;
		}

		public override void WriteEndDocument ()
		{
			CloseOpenAttributeAndElements ();

			switch (state) {
			case WriteState.Start:
				throw new InvalidOperationException ("Document has not started.");
			case WriteState.Prolog:
				throw new ArgumentException ("This document does not have a root element.");
			}

			state = WriteState.Start;
		}

		bool SupportsCombinedEndElementSupport (byte operation)
		{
			switch (operation) {
			case BF.Comment:
				return false;
			}
			return true;
		}

		public override void WriteEndElement ()
		{
			if (element_count-- == 0)
				throw new InvalidOperationException("There was no XML start tag open.");

			if (state == WriteState.Attribute)
				WriteEndAttribute ();

			// Comment+EndElement does not exist
			bool needExplicitEndElement = buffer.Position == 0 || !SupportsCombinedEndElementSupport (buffer.GetBuffer () [0]);
			ProcessPendingBuffer (true, !needExplicitEndElement);
			CheckState ();
			AddMissingElementXmlns ();

			if (needExplicitEndElement)
				writer.Write (BF.EndElement);

			element_ns = element_ns_stack.Pop ();
			xml_lang = xml_lang_stack.Pop ();
			xml_space = xml_space_stack.Pop ();
			int cur = namespaces.Count;
			ns_index = ns_index_stack.Pop ();
			namespaces.RemoveRange (ns_index, cur - ns_index);
			open_start_element = false;

			Depth--;
		}

		public override void WriteEntityRef (string name)
		{
			throw new NotSupportedException ("This XmlWriter implementation does not support entity references.");
		}

		public override void WriteFullEndElement ()
		{
			WriteEndElement ();
		}

		public override void WriteProcessingInstruction (string name, string text)
		{
			if (name != "xml")
				throw new ArgumentException ("Processing instructions are not supported. ('xml' is allowed for XmlDeclaration; this is because of design problem of ECMA XmlWriter)");
			// Otherwise, silently ignored. WriteStartDocument()
			// is still callable after this method(!)
		}

		public override void WriteQualifiedName (XmlDictionaryString local, XmlDictionaryString ns)
		{
			string prefix = namespaces.LastOrDefault (i => i.Value.ToString () == ns.ToString ()).Key;
			bool use_dic = prefix != null;
			if (prefix == null)
				prefix = LookupPrefix (ns.Value);
			if (prefix == null)
				throw new ArgumentException (String.Format ("Namespace URI '{0}' is not bound to any of the prefixes", ns));

			ProcessTypedValue ();

			if (use_dic && prefix.Length == 1) {
				writer.Write (BF.QNameIndex);
				writer.Write ((byte) (prefix [0] - 'a'));
				WriteDictionaryIndex (local);
			} else {
				// QNameIndex is not applicable.
				WriteString (prefix);
				WriteString (":");
				WriteString (local);
			}
		}

		public override void WriteRaw (string data)
		{
			WriteString (data);
		}

		public override void WriteRaw (char[] buffer, int index, int count)
		{
			WriteChars (buffer, index, count);
		}

		void CheckStateForAttribute ()
		{
			CheckState ();

			if (state != WriteState.Element)
				throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
		}

		string CreateNewPrefix ()
		{
			return CreateNewPrefix (String.Empty);
		}
		
		string CreateNewPrefix (string p)
		{
			for (char c = 'a'; c <= 'z'; c++)
				if (!namespaces.Any (iter => iter.Key == p + c))
					return p + c;
			for (char c = 'a'; c <= 'z'; c++) {
				var s = CreateNewPrefix (c.ToString ());
				if (s != null)
					return s;
			}
			throw new InvalidOperationException ("too many prefix population");
		}

		bool CollectionContains (ICollection col, string value)
		{
			foreach (string v in col)
				if (v == value)
					return true;
			return false;
		}

		void ProcessStartAttributeCommon (ref string prefix, string localName, string ns, object nameObj, object nsObj)
		{
			// dummy prefix is created here, while the caller
			// still uses empty string as the prefix there.
			if (prefix.Length == 0 && ns.Length > 0) {
				prefix = LookupPrefix (ns);
				// Not only null but also ""; when the returned
				// string is empty, then it still needs a dummy
				// prefix, since default namespace does not
				// apply to attribute
				if (String.IsNullOrEmpty (prefix))
					prefix = CreateNewPrefix ();
			}
			else if (prefix.Length > 0 && ns.Length == 0) {
				switch (prefix) {
				case "xml":
					nsObj = ns = XmlNamespace;
					break;
				case "xmlns":
					nsObj = ns = XmlnsNamespace;
					break;
				default:
					throw new ArgumentException ("Cannot use prefix with an empty namespace.");
				}
			}
			// here we omit such cases that it is used for writing
			// namespace-less xml, unlike XmlTextWriter.
			if (prefix == "xmlns" && ns != XmlnsNamespace)
				throw new ArgumentException (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));

			CheckStateForAttribute ();

			state = WriteState.Attribute;

			save_target = SaveTarget.None;
			switch (prefix) {
			case "xml":
				// MS.NET looks to allow other names than 
				// lang and space (e.g. xml:link, xml:hack).
				ns = XmlNamespace;
				switch (localName) {
				case "lang":
					save_target = SaveTarget.XmlLang;
					break;
				case "space":
					save_target = SaveTarget.XmlSpace;
					break;
				}
				break;
			case "xmlns":
				save_target = SaveTarget.Namespaces;
				break;
			}

			current_attr_prefix = prefix;
			current_attr_name = nameObj;
			current_attr_ns = nsObj;
		}

		public override void WriteStartAttribute (string prefix, string localName, string ns)
		{
			if (prefix == null)
				prefix = String.Empty;
			if (ns == null)
				ns = String.Empty;
			if (localName == "xmlns" && prefix.Length == 0) {
				prefix = "xmlns";
				localName = String.Empty;
			}

			ProcessStartAttributeCommon (ref prefix, localName, ns, localName, ns);

			// for namespace nodes we don't write attribute node here.
			if (save_target == SaveTarget.Namespaces)
				return;

			byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
				  (byte) (prefix [0] - 'a' + BF.PrefixNAttrStringStart) :
				  prefix.Length == 0 ? BF.AttrString :
				  BF.AttrStringPrefix;

			if (BF.PrefixNAttrStringStart <= op && op <= BF.PrefixNAttrStringEnd) {
				writer.Write (op);
				writer.Write (localName);
			} else {
				writer.Write (op);
				if (prefix.Length > 0)
					writer.Write (prefix);
				writer.Write (localName);
			}
		}

		public override void WriteStartDocument ()
		{
			WriteStartDocument (false);
		}

		public override void WriteStartDocument (bool standalone)
		{
			if (state != WriteState.Start)
				throw new InvalidOperationException("WriteStartDocument should be the first call.");

			CheckState ();

			// write nothing to stream.

			state = WriteState.Prolog;
		}

		void PrepareStartElement ()
		{
			ProcessPendingBuffer (true, false);
			CheckState ();
			CloseStartElement ();

			Depth++;

			element_ns_stack.Push (element_ns);
			xml_lang_stack.Push (xml_lang);
			xml_space_stack.Push (xml_space);
			ns_index_stack.Push (ns_index);
		}

		public override void WriteStartElement (string prefix, string localName, string ns)
		{
			PrepareStartElement ();

			if ((prefix != null && prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
				throw new ArgumentException ("Cannot use a prefix with an empty namespace.");

			if (ns == null)
				ns = String.Empty;
			if (ns == String.Empty)
				prefix = String.Empty;
			if (prefix == null)
				prefix = String.Empty;

			byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
				  (byte) (prefix [0] - 'a' + BF.PrefixNElemStringStart) :
				  prefix.Length == 0 ? BF.ElemString :
				  BF.ElemStringPrefix;

			if (BF.PrefixNElemStringStart <= op && op <= BF.PrefixNElemStringEnd) {
				writer.Write (op);
				writer.Write (localName);
			} else {
				writer.Write (op);
				if (prefix.Length > 0)
					writer.Write (prefix);
				writer.Write (localName);
			}

			OpenElement (prefix, ns);
		}

		void OpenElement (string prefix, object nsobj)
		{
			string ns = nsobj.ToString ();

			state = WriteState.Element;
			open_start_element = true;
			element_prefix = prefix;
			element_count++;
			element_ns = nsobj.ToString ();

			if (element_ns != String.Empty && LookupPrefix (element_ns) != prefix)
				AddNamespace (prefix, nsobj);
		}

		void AddNamespace (string prefix, object nsobj)
		{
			namespaces.Add (new KeyValuePair<string,object> (prefix, nsobj));
		}

		void CheckIfTextAllowed ()
		{
			switch (state) {
			case WriteState.Start:
			case WriteState.Prolog:
				throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
			}
		}

		public override void WriteString (string text)
		{
			if (text == null)
				throw new ArgumentNullException ("text");
			CheckIfTextAllowed ();

			if (text == null)
				text = String.Empty;

			ProcessStateForContent ();

			if (state == WriteState.Attribute)
				attr_value += text;
			else
				WriteTextBinary (text);
		}

		public override void WriteString (XmlDictionaryString text)
		{
			if (text == null)
				throw new ArgumentNullException ("text");
			CheckIfTextAllowed ();

			if (text == null)
				text = XmlDictionaryString.Empty;

			ProcessStateForContent ();

			if (state == WriteState.Attribute)
				attr_value += text.Value;
			else if (text.Equals (XmlDictionary.Empty))
				writer.Write (BF.EmptyText);
			else {
				writer.Write (BF.TextIndex);
				WriteDictionaryIndex (text);
			}
		}

		public override void WriteSurrogateCharEntity (char lowChar, char highChar)
		{
			WriteChars (new char [] {highChar, lowChar}, 0, 2);
		}

		public override void WriteWhitespace (string ws)
		{
			for (int i = 0; i < ws.Length; i++) {
				switch (ws [i]) {
				case ' ': case '\t': case '\r': case '\n':
					continue;
				default:
					throw new ArgumentException ("Invalid Whitespace");
				}
			}

			ProcessStateForContent ();

			WriteTextBinary (ws);
		}

		public override void WriteXmlnsAttribute (string prefix, string namespaceUri)
		{
			if (namespaceUri == null)
				throw new ArgumentNullException ("namespaceUri");

			if (String.IsNullOrEmpty (prefix))
				prefix = CreateNewPrefix ();

			CheckStateForAttribute ();

			AddNamespaceChecked (prefix, namespaceUri);

			state = WriteState.Element;
		}

		void AddNamespaceChecked (string prefix, object ns)
		{
			switch (ns.ToString ()) {
			case XmlnsNamespace:
			case XmlNamespace:
				return;
			}

			if (prefix == null)
				throw new InvalidOperationException ();
			var o = namespaces.LastOrDefault (i => i.Key == prefix);
			if (o.Key != null) { // i.e. exists
				if (o.Value.ToString () != ns.ToString ()) {
					if (namespaces.LastIndexOf (o) >= ns_index)
						throw new ArgumentException (String.Format ("The prefix '{0}' is already mapped to another namespace URI '{1}' in this element scope and cannot be mapped to '{2}'", prefix ?? "(null)", o.Value ?? "(null)", ns.ToString ()));
					else
						AddNamespace  (prefix, ns);
				}
			}
			else
				AddNamespace  (prefix, ns);
		}

		#region DictionaryString

		void WriteDictionaryIndex (XmlDictionaryString ds)
		{
			XmlDictionaryString ds2;
			bool isSession = false;
			int idx = ds.Key;
			if (ds.Dictionary != dict_ext) {
				isSession = true;
				if (dict_int.TryLookup (ds.Value, out ds2))
					ds = ds2;
				if (!session.TryLookup (ds, out idx))
					session.TryAdd (dict_int.Add (ds.Value), out idx);
			}
			if (idx >= 0x80) {
				writer.Write ((byte) (0x80 + ((idx % 0x80) << 1) + (isSession ? 1 : 0)));
				writer.Write ((byte) ((byte) (idx / 0x80) << 1));
			}
			else
				writer.Write ((byte) (((idx % 0x80) << 1) + (isSession ? 1 : 0)));
		}

		public override void WriteStartElement (string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri)
		{
			PrepareStartElement ();

			if (prefix == null)
				prefix = String.Empty;

			byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
				  (byte) (prefix [0] - 'a' + BF.PrefixNElemIndexStart) :
				  prefix.Length == 0 ? BF.ElemIndex :
				  BF.ElemIndexPrefix;

			if (BF.PrefixNElemIndexStart <= op && op <= BF.PrefixNElemIndexEnd) {
				writer.Write (op);
				WriteDictionaryIndex (localName);
			} else {
				writer.Write (op);
				if (prefix.Length > 0)
					writer.Write (prefix);
				WriteDictionaryIndex (localName);
			}

			OpenElement (prefix, namespaceUri);
		}

		public override void WriteStartAttribute (string prefix, XmlDictionaryString localName, XmlDictionaryString ns)
		{
			if (localName == null)
				throw new ArgumentNullException ("localName");
			if (prefix == null)
				prefix = String.Empty;
			if (ns == null)
				ns = XmlDictionaryString.Empty;
			if (localName.Value == "xmlns" && prefix.Length == 0) {
				prefix = "xmlns";
				localName = XmlDictionaryString.Empty;
			}

			ProcessStartAttributeCommon (ref prefix, localName.Value, ns.Value, localName, ns);

			if (save_target == SaveTarget.Namespaces)
				return;

			if (prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z') {
				writer.Write ((byte) (prefix [0] - 'a' + BF.PrefixNAttrIndexStart));
				WriteDictionaryIndex (localName);
			} else {
				byte op = ns.Value.Length == 0 ? BF.AttrIndex :BF.AttrIndexPrefix;
				// Write to Stream
				writer.Write (op);
				if (prefix.Length > 0)
					writer.Write (prefix);
				WriteDictionaryIndex (localName);
			}
		}

		public override void WriteXmlnsAttribute (string prefix, XmlDictionaryString namespaceUri)
		{
			if (namespaceUri == null)
				throw new ArgumentNullException ("namespaceUri");

			if (String.IsNullOrEmpty (prefix))
				prefix = CreateNewPrefix ();

			CheckStateForAttribute ();

			AddNamespaceChecked (prefix, namespaceUri);

			state = WriteState.Element;
		}
		#endregion

		#region WriteValue
		public override void WriteValue (bool value)
		{
			ProcessTypedValue ();
			writer.Write ((byte) (value ? BF.BoolTrue : BF.BoolFalse));
		}

		public override void WriteValue (int value)
		{
			WriteValue ((long) value);
		}

		public override void WriteValue (long value)
		{
			ProcessTypedValue ();

			if (value == 0)
				writer.Write (BF.Zero);
			else if (value == 1)
				writer.Write (BF.One);
			else if (value < 0 || value > uint.MaxValue) {
				writer.Write (BF.Int64);
				for (int i = 0; i < 8; i++) {
					writer.Write ((byte) (value & 0xFF));
					value >>= 8;
				}
			} else if (value <= byte.MaxValue) {
				writer.Write (BF.Int8);
				writer.Write ((byte) value);
			} else if (value <= short.MaxValue) {
				writer.Write (BF.Int16);
				writer.Write ((byte) (value & 0xFF));
				writer.Write ((byte) (value >> 8));
			} else if (value <= int.MaxValue) {
				writer.Write (BF.Int32);
				for (int i = 0; i < 4; i++) {
					writer.Write ((byte) (value & 0xFF));
					value >>= 8;
				}
			}
		}

		public override void WriteValue (float value)
		{
			ProcessTypedValue ();
			writer.Write (BF.Single);
			WriteValueContent (value);
		}

		void WriteValueContent (float value)
		{
			writer.Write (value);
		}

		public override void WriteValue (double value)
		{
			ProcessTypedValue ();
			writer.Write (BF.Double);
			WriteValueContent (value);
		}

		void WriteValueContent (double value)
		{
			writer.Write (value);
		}

		public override void WriteValue (decimal value)
		{
			ProcessTypedValue ();
			writer.Write (BF.Decimal);
			WriteValueContent (value);
		}

		void WriteValueContent (decimal value)
		{
			int [] bits = Decimal.GetBits (value);
			// so, looks like it is saved as its internal form,
			// not the returned order.
			// BinaryWriter.Write(Decimal) is useless here.
			writer.Write (bits [3]);
			writer.Write (bits [2]);
			writer.Write (bits [0]);
			writer.Write (bits [1]);
		}

		public override void WriteValue (DateTime value)
		{
			ProcessTypedValue ();
			writer.Write (BF.DateTime);
			WriteValueContent (value);
		}

		void WriteValueContent (DateTime value)
		{
			writer.Write (value.ToBinary ());
		}

		public override void WriteValue (Guid value)
		{
			ProcessTypedValue ();
			writer.Write (BF.Guid);
			WriteValueContent (value);
		}

		void WriteValueContent (Guid value)
		{
			byte [] bytes = value.ToByteArray ();
			writer.Write (bytes, 0, bytes.Length);
		}

		public override void WriteValue (UniqueId value)
		{
			if (value == null)
				throw new ArgumentNullException ("value");

			Guid guid;
			if (value.TryGetGuid (out guid)) {
				// this conditional branching is required for
				// attr_typed_value not being true.
				ProcessTypedValue ();

				writer.Write (BF.UniqueId);
				byte [] bytes = guid.ToByteArray ();
				writer.Write (bytes, 0, bytes.Length);
			} else {
				WriteValue (value.ToString ());
			}
		}

		public override void WriteValue (TimeSpan value)
		{
			ProcessTypedValue ();

			writer.Write (BF.TimeSpan);
			WriteValueContent (value);
		}

		void WriteValueContent (TimeSpan value)
		{
			WriteBigEndian (value.Ticks, 8);
		}
		#endregion

		private void WriteBigEndian (long value, int digits)
		{
			long v = 0;
			for (int i = 0; i < digits; i++) {
				v = (v << 8) + (value & 0xFF);
				value >>= 8;
			}
			for (int i = 0; i < digits; i++) {
				writer.Write ((byte) (v & 0xFF));
				v >>= 8;
			}
		}

		private void WriteTextBinary (string text)
		{
			if (text.Length == 0)
				writer.Write (BF.EmptyText);
			else {
				char [] arr = text.ToCharArray ();
				WriteChars (arr, 0, arr.Length);
			}
		}

		#endregion

		#region Write typed array content

		// they are packed in WriteValue(), so we cannot reuse
		// them for array content.

		void WriteValueContent (bool value)
		{
			writer.Write (value ? (byte) 1 : (byte) 0);
		}

		void WriteValueContent (short value)
		{
			writer.Write (value);
		}

		void WriteValueContent (int value)
		{
			writer.Write (value);
		}

		void WriteValueContent (long value)
		{
			writer.Write (value);
		}

		#endregion
	}
}