//
// XmlMtomDictionaryWriter.cs
//
// Author:
//	Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2009 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.IO;
using System.Net.Mime;
using System.Text;

namespace System.Xml
{
	internal class XmlMtomDictionaryWriter : XmlDictionaryWriter
	{
		public XmlMtomDictionaryWriter (Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo, string boundary, string startUri, bool writeMessageHeaders, bool ownsStream)
		{
			writer = new StreamWriter (stream, encoding);
			max_bytes = maxSizeInBytes;
			write_headers = writeMessageHeaders;
			owns_stream = ownsStream;

			var settings = new XmlWriterSettings ();
			settings.Encoding = encoding;
			settings.OmitXmlDeclaration = true;
			xml_writer_settings = settings;

			// FIXME: actually it does not likely use ContentType.ToString() but writes those header items by own.
			// (so that it could generate "start" header dynamically)
			var c = new ContentType ("multipart/related");
			c.Parameters ["type"] = "application/xop+xml";
			c.Boundary = boundary;
			c.Parameters ["start"] = "<" + startUri + ">";
			c.Parameters ["start-info"] = startInfo;
			content_type = c;
		}

		// constructor arguments
		TextWriter writer;
		XmlWriterSettings xml_writer_settings;
		Encoding encoding;
		int max_bytes;
		bool write_headers;
		bool owns_stream;
		ContentType content_type;

		// state
		XmlWriter w;
		int depth;
		int section_count;

		XmlWriter CreateWriter ()
		{
			return XmlWriter.Create (writer, xml_writer_settings);
		}

		public override void Close ()
		{
			w.Close ();
			if (owns_stream)
				writer.Close ();
		}

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

		public override string LookupPrefix (string namespaceUri)
		{
			return w.LookupPrefix (namespaceUri);
		}

		public override void WriteBase64 (byte [] bytes, int start, int length)
		{
			CheckState ();
			w.WriteBase64 (bytes, start, length);
		}

		public override void WriteCData (string text)
		{
			CheckState ();
			w.WriteCData (text);
		}

		public override void WriteCharEntity (char c)
		{
			CheckState ();
			w.WriteCharEntity (c);
		}

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

		public override void WriteComment (string comment)
		{
			CheckState ();
			w.WriteComment (comment);
		}

		public override void WriteDocType (string name, string pubid, string sysid, string intSubset)
		{
			throw new NotSupportedException (); // indeed
		}

		public override void WriteEndAttribute ()
		{
			w.WriteEndAttribute ();
		}

		public override void WriteEndDocument ()
		{
			w.WriteEndDocument ();
		}

		public override void WriteEndElement ()
		{
			w.WriteEndElement ();
			if (--depth == 0)
				WriteEndOfMimeSection ();
		}

		public override void WriteEntityRef (string name)
		{
			w.WriteEntityRef (name);
		}

		public override void WriteFullEndElement ()
		{
			w.WriteFullEndElement ();
			if (--depth == 0)
				WriteEndOfMimeSection ();
		}

		public override void WriteProcessingInstruction (string name, string data)
		{
			throw new NotSupportedException ();
		}

		public override void WriteRaw (string raw)
		{
			CheckState ();
			w.WriteRaw (raw);
		}

		public override void WriteRaw (char [] chars, int index, int count)
		{
			CheckState ();
			w.WriteRaw (chars, index, count);
		}

		public override void WriteStartAttribute (string prefix, string localName, string namespaceURI)
		{
			CheckState ();
			w.WriteStartAttribute (prefix, localName, namespaceURI);
		}

		public override void WriteStartDocument ()
		{
			CheckState ();
			w.WriteStartDocument ();
		}

		public override void WriteStartDocument (bool standalone)
		{
			CheckState ();
			w.WriteStartDocument (standalone);
		}

		public override void WriteStartElement (string prefix, string localName, string namespaceURI)
		{
			CheckState ();

			if (depth == 0)
				WriteStartOfMimeSection ();

			w.WriteStartElement (prefix, localName, namespaceURI);
			depth++;
		}

		public override WriteState WriteState {
			get { return w.WriteState; }
		}

		public override void WriteString (string text)
		{
			CheckState ();

			int i1, i2 = 0;
			do {
				i1 = text.IndexOf ('\r', i2);
				if (i1 >= 0) {
					w.WriteString (text.Substring (i2, i1 - i2));
					WriteCharEntity ('\r');
					i2 = i1 + 1;
				} else {
					w.WriteString (text.Substring (i2));
					break;
				}
			} while (true);
		}

		public override void WriteSurrogateCharEntity (char low, char high)
		{
			CheckState ();
			w.WriteSurrogateCharEntity (low, high);
		}

		public override void WriteWhitespace (string text)
		{
			CheckState ();
			w.WriteWhitespace (text);
		}

		public override string XmlLang {
			get { return w.XmlLang; }
		}

		public override XmlSpace XmlSpace {
			get { return w.XmlSpace; }
		}

		void CheckState ()
		{
			if (w == null && write_headers)
				WriteMimeHeaders ();
			if (w == null || w.WriteState == WriteState.Closed || w.WriteState == WriteState.Error)
				w = CreateWriter ();
		}

		void WriteMimeHeaders ()
		{
			writer.Write ("MIME-Version: 1.0\r\n");
			writer.Write ("Content-Type: ");
			writer.Write (content_type.ToString ());
			writer.Write ("\r\n\r\n\r\n");
		}

		void WriteStartOfMimeSection ()
		{
			section_count++;

			// I'm not sure what's the expected behavior of this
			// strange XmlWriter, but so far - it outputs only one
			// section.
			if (section_count > 1)
				return;

			writer.Write ("\r\n");
			writer.Write ("--");
			writer.Write (content_type.Boundary);
			writer.Write ("\r\n");
			writer.Write ("Content-ID: ");
			writer.Write (content_type.Parameters ["start"]);
			writer.Write ("\r\n");
			writer.Write ("Content-Transfer-Encoding: 8bit\r\n");
			writer.Write ("Content-Type: application/xop+xml;charset=");
			writer.Write (xml_writer_settings.Encoding.HeaderName);
			writer.Write (";type=\"");
			writer.Write (content_type.Parameters ["start-info"].Replace ("\"", "\\\""));
			writer.Write ("\"\r\n\r\n");
		}

		void WriteEndOfMimeSection ()
		{
			// I'm not sure what's the expected behavior of this
			// strange XmlWriter, but so far - it outputs only one
			// section.
			if (section_count > 1)
				return;

			writer.Write ("\r\n");
			writer.Write ("--");
			writer.Write (content_type.Boundary);
			writer.Write ("--\r\n");
		}
	}
}