//
// SyndicationElementExtension.cs
//
// Author:
//	Atsushi Enomoto  <atsushi@ximian.com>
//
// Copyright (C) 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.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace System.ServiceModel.Syndication
{
	public class SyndicationElementExtension
	{
		ReadWriteHandler handler;

		public SyndicationElementExtension (object dataContractExtension)
			: this (dataContractExtension, (XmlObjectSerializer) null)
		{
		}

		public SyndicationElementExtension (object dataContractExtension, XmlObjectSerializer dataContractSerializer)
			: this (null, null, dataContractExtension, dataContractSerializer)
		{
		}

		public SyndicationElementExtension (string outerName, string outerNamespace, object dataContractExtension)
			: this (outerName, outerNamespace, dataContractExtension, null)
		{
		}

		public SyndicationElementExtension (string outerName, string outerNamespace, object dataContractExtension, XmlObjectSerializer dataContractSerializer)
		{
			if (dataContractExtension == null)
				throw new ArgumentNullException ("dataContractExtension");
			handler = new DataContractReadWriteHandler (outerName, outerNamespace, dataContractExtension, dataContractSerializer);
		}

		public SyndicationElementExtension (object xmlSerializerExtension, XmlSerializer serializer)
		{
			if (xmlSerializerExtension == null)
				throw new ArgumentNullException ("xmlSerializerExtension");
			handler = new XmlSerializationReadWriteHandler (xmlSerializerExtension, serializer);
		}

		public SyndicationElementExtension (XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException ("xmlReader");
			xmlReader.MoveToContent ();
			if (xmlReader.NodeType != XmlNodeType.Element)
				throw new XmlException ("Element node is expected on the argument xmlReader");

			handler = new XmlReaderReadWriteHandler (xmlReader);
		}

		public string OuterName {
			get { return handler != null ? handler.Name : null; }
		}

		public string OuterNamespace {
			get { return handler != null ? handler.Namespace : null; }
		}

		public TExtension GetObject<TExtension> ()
		{
			return GetObject<TExtension> (new DataContractSerializer (typeof (TExtension)));
		}

		public TExtension GetObject<TExtension> (XmlObjectSerializer serializer)
		{
			if (serializer == null)
				throw new ArgumentNullException ("serializer");
			return (TExtension) serializer.ReadObject (GetReader (), false);
		}

		public TExtension GetObject<TExtension> (XmlSerializer serializer)
		{
			if (serializer == null)
				throw new ArgumentNullException ("serializer");
			return (TExtension) serializer.Deserialize (GetReader ());
		}

		public XmlReader GetReader ()
		{
			var r = handler.GetReader ();
			r.MoveToContent ();
			return r;
		}

		public void WriteTo (XmlWriter writer)
		{
			if (writer == null)
				throw new ArgumentNullException ("writer");

			handler.WriteTo (writer);
		}

		abstract class ReadWriteHandler
		{
			public string Name { get; protected set; }

			public string Namespace { get; protected set; }

			public virtual XmlReader GetReader ()
			{
				StringWriter sw = new StringWriter ();
				using (XmlWriter w = XmlWriter.Create (sw))
					WriteTo (w);
				return XmlReader.Create (new StringReader (sw.ToString ()));
			}

			public abstract void WriteTo (XmlWriter writer);
		}

		class DataContractReadWriteHandler : ReadWriteHandler
		{
			object extension;
			XmlObjectSerializer serializer;
			
			public DataContractReadWriteHandler (string name, string ns, object extension, XmlObjectSerializer serializer)
			{
				if (serializer == null)
					serializer = new DataContractSerializer (extension.GetType ());
				// FIXME: this is a nasty workaround that mcs somehow fails to resolve InternalVisibleTo(System.ServiceModel) in System.Runtime.Serialization.dll and thus rejects the use of KnownTypeCollection unlike NET_2_0 case (where System.ServiceModel.Web.dll is referenced).
				XmlQualifiedName qname = null;
				if (name == null || ns == null) {
					var ms = new MemoryStream ();
					using (var xw = XmlWriter.Create (ms))
						serializer.WriteStartObject (xw, extension);
					ms.Position = 0;
					var xr = XmlReader.Create (ms);
					xr.MoveToContent ();
					qname = new XmlQualifiedName (xr.LocalName, xr.NamespaceURI);
				}
				this.Name = name ?? qname.Name;
				this.Namespace = ns ?? qname.Namespace;
				this.extension = extension;
				this.serializer = serializer;
			}

			public override void WriteTo (XmlWriter writer)
			{
				if (Name != null) {
					writer.WriteStartElement (Name, Namespace);
					serializer.WriteObjectContent (writer, extension);
					writer.WriteFullEndElement ();
				}
				else
					serializer.WriteObject (writer, extension);
			}
		}

		class XmlSerializationReadWriteHandler : ReadWriteHandler
		{
			object extension;
			XmlSerializer serializer;

			public XmlSerializationReadWriteHandler (object extension, XmlSerializer serializer)
			{
				this.extension = extension;
				this.serializer = serializer;

				if (serializer == null)
					serializer = new XmlSerializer (extension.GetType ());
			}

			public override void WriteTo (XmlWriter writer)
			{
				serializer.Serialize (writer, extension);
			}
		}

		class XmlReaderReadWriteHandler : ReadWriteHandler
		{
			string xml;

			public XmlReaderReadWriteHandler (XmlReader reader)
			{
				reader.MoveToContent ();
				Name = reader.LocalName;
				Namespace = reader.NamespaceURI;
				xml = reader.ReadOuterXml ();
			}

			public override XmlReader GetReader ()
			{
				var r = XmlReader.Create (new StringReader (xml));
				r.MoveToContent ();
				return r;
			}

			public override void WriteTo (XmlWriter writer)
			{
				writer.WriteNode (GetReader (), false);
			}
		}
	}
}