//
// System.Net.HttpWebResponse
//
// Authors:
// 	Lawrence Pit (loz@cable.a2000.nl)
// 	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (c) 2002 Lawrence Pit
// (c) 2003 Ximian, Inc. (http://www.ximian.com)
//

using System;
using System.IO;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Text;

namespace System.Net 
{
	[Serializable]
	public class HttpWebResponse : WebResponse, ISerializable, IDisposable
	{
		Uri uri;
		WebHeaderCollection webHeaders;
		CookieCollection cookieCollection;
		string method;
		Version version;
		HttpStatusCode statusCode;
		string statusDescription;
		long contentLength = -1;
		string contentType;

		bool disposed = false;
		Stream stream;
		
		// Constructors
		
		internal HttpWebResponse (Uri uri, string method, WebConnectionData data, bool cookiesSet)
		{
			this.uri = uri;
			this.method = method;
			webHeaders = data.Headers;
			version = data.Version;
			statusCode = (HttpStatusCode) data.StatusCode;
			statusDescription = data.StatusDescription;
			stream = data.stream;
			if (cookiesSet) {
				FillCookies ();
			} else if (webHeaders != null) {
				webHeaders.RemoveInternal ("Set-Cookie");
				webHeaders.RemoveInternal ("Set-Cookie2");
			}
		}

		protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
		{
			SerializationInfo info = serializationInfo;

			uri = (Uri) info.GetValue ("uri", typeof (Uri));
			contentLength = info.GetInt64 ("contentLength");
			contentType = info.GetString ("contentType");
			method = info.GetString ("method");
			statusDescription = info.GetString ("statusDescription");
			cookieCollection = (CookieCollection) info.GetValue ("cookieCollection", typeof (CookieCollection));
			version = (Version) info.GetValue ("version", typeof (Version));
			statusCode = (HttpStatusCode) info.GetValue ("statusCode", typeof (HttpStatusCode));
		}
		
		// Properties
		
		public string CharacterSet {
			// Content-Type   = "Content-Type" ":" media-type
			// media-type     = type "/" subtype *( ";" parameter )
			// parameter      = attribute "=" value
			// 3.7.1. default is ISO-8859-1
			get { 
				CheckDisposed ();
				string contentType = ContentType;
				if (contentType == null)
					return "ISO-8859-1";
				string val = contentType.ToLower (); 					
				int pos = val.IndexOf ("charset=");
				if (pos == -1)
					return "ISO-8859-1";
				pos += 8;
				int pos2 = val.IndexOf (';', pos);
				return (pos2 == -1)
				     ? contentType.Substring (pos) 
				     : contentType.Substring (pos, pos2 - pos);
			}
		}
		
		public string ContentEncoding {
			get { 
				CheckDisposed ();
				return webHeaders ["Content-Encoding"];
			}
		}
		
		public override long ContentLength {		
			get {
				CheckDisposed ();
				if (contentLength != -1)
					return contentLength;

				try {
					contentLength = (long) UInt64.Parse (webHeaders ["Content-Length"]); 
				} catch (Exception) {
					return -1;
				}

				return contentLength;
			}
		}
		
		public override string ContentType {		
			get {
				CheckDisposed ();
				if (contentType == null)
					contentType = webHeaders ["Content-Type"];

				return contentType;
			}
		}
		
		public CookieCollection Cookies {
			get { 
				CheckDisposed ();
				
				if (cookieCollection == null)
					cookieCollection = new CookieCollection ();
				return cookieCollection;
			}
			set {
				CheckDisposed ();
				cookieCollection = value;
			}
		}
		
		public override WebHeaderCollection Headers {		
			get { 
				CheckDisposed ();
				return webHeaders; 
			}
		}
		
		public DateTime LastModified {
			get {
				CheckDisposed ();
				try {
					string dtStr = webHeaders ["Last-Modified"];
					return MonoHttpDate.Parse (dtStr);
				} catch (Exception) {
					return DateTime.Now;	
				}
			}
		}
		
		public string Method {
			get { 
				CheckDisposed ();
				return method; 
			}
		}
		
		public Version ProtocolVersion {
			get { 
				CheckDisposed ();
				return version; 
			}
		}
		
		public override Uri ResponseUri {		
			get { 
				CheckDisposed ();
				return uri; 
			}
		}		
		
		public string Server {
			get { 
				CheckDisposed ();
				return webHeaders ["Server"]; 
			}
		}
		
		public HttpStatusCode StatusCode {
			get { 
				CheckDisposed ();
				return statusCode; 
			}
		}
		
		public string StatusDescription {
			get { 
				CheckDisposed ();
				return statusDescription; 
			}
		}

		// Methods
		
		public override int GetHashCode ()
		{
			CheckDisposed ();
			return base.GetHashCode ();
		}
		
		public string GetResponseHeader (string headerName)
		{
			CheckDisposed ();
			string value = webHeaders [headerName];
			return (value != null) ? value : "";
		}
		
		public override Stream GetResponseStream ()
		{
			CheckDisposed ();
			if (stream == null)
				return Stream.Null;  
			if (0 == String.Compare (method, "HEAD", true)) // see par 4.3 & 9.4
				return Stream.Null;  

			return stream;
		}
		
		void ISerializable.GetObjectData (SerializationInfo serializationInfo,
		   				  StreamingContext streamingContext)
		{
			SerializationInfo info = serializationInfo;

			info.AddValue ("uri", uri);
			info.AddValue ("contentLength", contentLength);
			info.AddValue ("contentType", contentType);
			info.AddValue ("method", method);
			info.AddValue ("statusDescription", statusDescription);
			info.AddValue ("cookieCollection", cookieCollection);
			info.AddValue ("version", version);
			info.AddValue ("statusCode", statusCode);
		}		


		// Cleaning up stuff

		public override void Close ()
		{
			((IDisposable) this).Dispose ();
		}
		
		void IDisposable.Dispose ()
		{
			Dispose (true);
			GC.SuppressFinalize (this);  
		}
		
		protected virtual void Dispose (bool disposing) 
		{
			if (this.disposed)
				return;
			this.disposed = true;
			
			if (disposing) {
				// release managed resources
				uri = null;
				webHeaders = null;
				cookieCollection = null;
				method = null;
				version = null;
				statusDescription = null;
			}
			
			// release unmanaged resources
			Stream st = stream;
			stream = null;
			if (st != null) {
				WebConnectionStream wce = st as WebConnectionStream;
				if (wce != null) {
					try {
						wce.ReadAll ();
					} catch {}
				}
				st.Close ();
			}
		}
		
		private void CheckDisposed () 
		{
			if (disposed)
				throw new ObjectDisposedException (GetType ().FullName);
		}

		void FillCookies ()
		{
			if (webHeaders == null)
				return;

			string val = webHeaders ["Set-Cookie"];
			if (val != null && val.Trim () != "")
				SetCookie (val);

			val = webHeaders ["Set-Cookie2"];
			if (val != null && val.Trim () != "")
				SetCookie2 (val);
		}
		
		static string [] SplitValue (string input)
		{
			string [] result = new string [2];
			int eq = input.IndexOf ('=');
			if (eq == -1) {
				result [0] = "invalid";
			} else {
				result [0] = input.Substring (0, eq).Trim ().ToUpper ();
				result [1] = input.Substring (eq + 1);
			}
			
			return result;
		}
		
		[MonoTODO ("Parse dates")]
		void SetCookie (string cookie_str)
		{
			string[] parts = null;
			Collections.Queue options = null;
			Cookie cookie = null;

			options = new Collections.Queue (cookie_str.Split (';'));
			parts = SplitValue ((string) options.Dequeue()); // NAME=VALUE must be first

			cookie = new Cookie (parts[0], parts[1]);

			while (options.Count > 0) {
				parts = SplitValue ((string) options.Dequeue());
				switch (parts [0]) {
					case "COMMENT":
						if (cookie.Comment == null)
							cookie.Comment = parts[1];
					break;
					case "COMMENTURL":
						if (cookie.CommentUri == null)
							cookie.CommentUri = new Uri(parts[1]);
					break;
					case "DISCARD":
						cookie.Discard = true;
					break;
					case "DOMAIN":
						if (cookie.Domain == "")
							cookie.Domain = parts[1];
					break;
					case "MAX-AGE": // RFC Style Set-Cookie2
						if (cookie.Expires == DateTime.MinValue)
							cookie.Expires = cookie.TimeStamp.AddSeconds (Int32.Parse (parts[1]));
					break;
					case "EXPIRES": // Netscape Style Set-Cookie
						if (cookie.Expires == DateTime.MinValue) {
							//FIXME: Does DateTime parse something like: "Sun, 17-Jan-2038 19:14:07 GMT"?
							//cookie.Expires = DateTime.ParseExact (parts[1]);
							cookie.Expires = DateTime.Now.AddDays (1);
						}
					break;
					case "PATH":
							cookie.Path = parts[1];
					break;
					case "PORT":
						if (cookie.Port == null)
							cookie.Port = parts[1];
					break;
					case "SECURE":
						cookie.Secure = true;
					break;
					case "VERSION":
						cookie.Version = Int32.Parse (parts[1]);
					break;
				} // switch
			} // while

			if (cookieCollection == null)
				cookieCollection = new CookieCollection();

			if (cookie.Domain == "")
				cookie.Domain = uri.Host;

			cookieCollection.Add (cookie);
		}

		void SetCookie2 (string cookies_str)
		{
			string [] cookies = cookies_str.Split (',');
	
			foreach (string cookie_str in cookies)
				SetCookie (cookie_str);
		}
	}	
}

