using System;
using System.IO;
using System.Collections;
using System.Text;

namespace LumiSoft.Net.Mime
{
	/// <summary>
	/// Mime entity header fields collection.
	/// </summary>
	public class HeaderFieldCollection : IEnumerable
	{
		private ArrayList m_pHeaderFields = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		public HeaderFieldCollection()
		{
			m_pHeaderFields = new ArrayList();
		}

		#region method Add
        
		/// <summary>
		/// Adds a new header field with specified name and value to the end of the collection.
		/// </summary>
		/// <param name="fieldName">Header field name.</param>
		/// <param name="value">Header field value.</param>
		public void Add(string fieldName,string value)
		{
			m_pHeaderFields.Add(new HeaderField(fieldName,value));
		}

		/// <summary>
		/// Adds specified heade field to the end of the collection.
		/// </summary>
		/// <param name="headerField">Header field.</param>
		public void Add(HeaderField headerField)
		{
			m_pHeaderFields.Add(headerField);
		}

		#endregion

		#region method Insert

		/// <summary>
		/// Inserts a new header field into the collection at the specified location.
		/// </summary>
		/// <param name="index">The location in the collection where you want to add the header field.</param>
		/// <param name="fieldName">Header field name.</param>
		/// <param name="value">Header field value.</param>
		public void Insert(int index,string fieldName,string value)
		{
			m_pHeaderFields.Insert(index,new HeaderField(fieldName,value));
		}

		#endregion


		#region method Remove

		/// <summary>
		/// Removes header field at the specified index from the collection.
		/// </summary>
		/// <param name="index">The index of the header field to remove.</param>
		public void Remove(int index)
		{
			m_pHeaderFields.RemoveAt(index);
		}

		/// <summary>
		/// Removes specified header field from the collection.
		/// </summary>
		/// <param name="field"></param>
		public void Remove(HeaderField field)
		{
			m_pHeaderFields.Remove(field);
		}

		#endregion

		#region method RemoveAll

		/// <summary>
		/// Removes all header fields with specified name from the collection.
		/// </summary>
		/// <param name="fieldName">Header field name.</param>
		public void RemoveAll(string fieldName)
		{
			for(int i=0;i<m_pHeaderFields.Count;i++){
				HeaderField h = (HeaderField)m_pHeaderFields[i];
				if(h.Name.ToLower() == fieldName.ToLower()){
					m_pHeaderFields.Remove(h);
					i--;
				}
			}
		}

		#endregion
		
		#region method Clear

		/// <summary>
		/// Clears the collection of all header fields.
		/// </summary>
		public void Clear()
		{
			m_pHeaderFields.Clear();
		}

		#endregion


		#region method Contains

		/// <summary>
		/// Gets if collection contains specified header field.
		/// </summary>
		/// <param name="fieldName">Header field name.</param>
		/// <returns></returns>
		public bool Contains(string fieldName)
		{
			foreach(HeaderField h in m_pHeaderFields){
				if(h.Name.ToLower() == fieldName.ToLower()){
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Gets if collection contains specified header field.
		/// </summary>
		/// <param name="headerField">Header field.</param>
		/// <returns></returns>
		public bool Contains(HeaderField headerField)
		{
			return m_pHeaderFields.Contains(headerField);
		}

		#endregion


		#region method GetFirst

		/// <summary>
		/// Gets first header field with specified name, returns null if specified field doesn't exist.
		/// </summary>
		/// <param name="fieldName">Header field name.</param>
		/// <returns></returns>
		public HeaderField GetFirst(string fieldName)
		{
			foreach(HeaderField h in m_pHeaderFields){
				if(h.Name.ToLower() == fieldName.ToLower()){
					return h;
				}
			}

			return null;
		}

		#endregion

		#region method Get

		/// <summary>
		/// Gets header fields with specified name, returns null if specified field doesn't exist.
		/// </summary>
		/// <param name="fieldName">Header field name.</param>
		/// <returns></returns>
		public HeaderField[] Get(string fieldName)
		{
			ArrayList fields = new ArrayList();
			foreach(HeaderField h in m_pHeaderFields){
				if(h.Name.ToLower() == fieldName.ToLower()){
					fields.Add(h);
				}
			}

			if(fields.Count > 0){
				HeaderField[] retVal = new HeaderField[fields.Count];
				fields.CopyTo(retVal);

				return retVal;
			}
			else{
				return null;
			}			
		}

		#endregion


		#region method Parse
	
		/// <summary>
		/// Parses header fields from stream. Stream position stays where header reading ends.
		/// </summary>
		/// <param name="stream">Stream from where to parse.</param>
		public void Parse(Stream stream)
		{			
			Parse(MimeUtils.ParseHeaders(stream));
		}

		/// <summary>
		/// Parses header fields from string.
		/// </summary>
		/// <param name="headerString">Header string.</param>
		public void Parse(string headerString)
		{
			m_pHeaderFields.Clear();

			headerString = UnfoldHeader(headerString);
			headerString = headerString.Replace("\r\n","\n");
			string[] headerFields = headerString.Split('\n');
			foreach(string headerField in headerFields){
				string[] nameValue = headerField.Split(new char[]{':'},2);
				// There must be header field name and value, otherwise invalid header field
				if(nameValue.Length == 2){
					Add(nameValue[0] + ":",Core.CanonicalDecode(nameValue[1].Trim()));
				}
			}
		}

		#endregion

		#region method ToHeaderString

		/// <summary>
		/// Converts header fields to rfc 2822 message header string.
		/// </summary>
		/// <param name="encodingCharSet">CharSet to use for non ASCII header field values. Utf-8 is recommended value, if you explicity don't need other.</param>
		/// <returns></returns>
		public string ToHeaderString(string encodingCharSet)
		{
			StringBuilder headerString = new StringBuilder();
			foreach(HeaderField f in this){
				headerString.Append(f.Name + " " + EncodeHeaderField(f.Value) + "\r\n");
			}

			return headerString.ToString();
		}

		#endregion


		#region private method EncodeHeaderField

		/// <summary>
		/// Encodes header field with quoted-printable encoding, if value contains ANSI or UNICODE chars.
		/// </summary>
		/// <param name="text">Text to encode.</param>
		/// <returns></returns>
		private string EncodeHeaderField(string text)
		{
			if(Core.IsAscii(text)){
				return text;
			}

			// First try to encode quoted strings("unicode-text") only, if no
			// quoted strings, encode full text.

			if(text.IndexOf("\"") > -1){
				string retVal = text;
				int offset = 0;							
				while(offset < retVal.Length - 1){
					int quoteStartIndex = retVal.IndexOf("\"",offset);
					// There is no more qouted strings, but there are some text left
					if(quoteStartIndex == -1){
						break;
					}
					int quoteEndIndex = retVal.IndexOf("\"",quoteStartIndex + 1);
					// If there isn't closing quote, encode full text
					if(quoteEndIndex == -1){
						break;
					}

					string leftPart = retVal.Substring(0,quoteStartIndex);
					string rightPart = retVal.Substring(quoteEndIndex + 1);
					string quotedString = retVal.Substring(quoteStartIndex + 1,quoteEndIndex - quoteStartIndex - 1);

					// Encode only not ASCII text
					if(!Core.IsAscii(quotedString)){
						string quotedStringCEncoded = Core.CanonicalEncode(quotedString,"utf-8");
						retVal = leftPart + "\"" + quotedStringCEncoded + "\"" + rightPart;
						offset += quoteEndIndex + 1 + quotedStringCEncoded.Length - quotedString.Length;
					}
					else{
						offset += quoteEndIndex + 1;
					}
				}

				// See if all encoded ok, if not encode all text
				if(Core.IsAscii(retVal)){
					return retVal;
				}
				else{
					return Core.CanonicalEncode(retVal,"utf-8");
				}
			}
			
			return Core.CanonicalEncode(text,"utf-8");
		}

		#endregion

		#region private method UnfoldHeader

		/// <summary>
		/// Unfolds header.
		/// </summary>
		/// <param name="header">Header string.</param>
		/// <returns></returns>
		private string UnfoldHeader(string header)
		{
			/* Rfc 2822 2.2.3 Long Header Fields
				The process of moving from this folded multiple-line representation
				of a header field to its single line representation is called
				"unfolding". Unfolding is accomplished by simply removing any CRLF
				that is immediately followed by WSP.  Each header field should be
				treated in its unfolded form for further syntactic and semantic
				evaluation.
				
				Example:
					Subject: aaaaa<CRLF>
					<TAB or SP>aaaaa<CRLF>
			*/

			// TODO: make this code better

			StringBuilder retVal = new StringBuilder();
			
			header = header.Replace("\r\n","\n");
			string[] headerLines = header.Split('\n');			
			string lastLine = headerLines[0];
			for(int i=1;i<headerLines.Length;i++){
				string headerLine = headerLines[i];
				// Header field is folded
				if(headerLine.StartsWith(" ") || headerLine.StartsWith("\t")){
					lastLine += headerLine;
				}
				else{
					retVal.Append(lastLine + "\r\n");
					lastLine = headerLine;
				}
			}
			retVal.Append(lastLine);

			return retVal.ToString();
		}

		#endregion


		#region interface IEnumerator

		/// <summary>
		/// Gets enumerator.
		/// </summary>
		/// <returns></returns>
		public IEnumerator GetEnumerator()
		{
			return m_pHeaderFields.GetEnumerator();
		}

		#endregion

		#region Properties Implementation
		
		/// <summary>
		/// Gets header field from specified index.
		/// </summary>
		public HeaderField this[int index]
		{
			get{ return (HeaderField)m_pHeaderFields[index]; }
		}

		/// <summary>
		/// Gets header fields count in the collection.
		/// </summary>
		public int Count
		{
			get{ return m_pHeaderFields.Count; }
		}

		#endregion

	}
}
