using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;

namespace LumiSoft.Net
{
	#region enum AuthType

	/// <summary>
	/// Authentication type.
	/// </summary>
	public enum AuthType
	{
		/// <summary>
		/// Plain username/password authentication.
		/// </summary>
		Plain = 0,

		/// <summary>
		/// APOP
		/// </summary>
		APOP  = 1,

		/// <summary>
		/// Not implemented.
		/// </summary>
		LOGIN = 2,	
	
		/// <summary>
		/// Cram-md5 authentication.
		/// </summary>
		CRAM_MD5 = 3,	

		/// <summary>
		/// DIGEST-md5 authentication.
		/// </summary>
		DIGEST_MD5 = 4,	
	}

	#endregion

	/// <summary>
	/// Provides net core utility methods.
	/// </summary>
	public class Core
	{

		#region method DoPeriodHandling

		/// <summary>
		/// Does period handling.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="add_Remove">If true add periods, else removes periods.</param>
		/// <returns></returns>
		public static MemoryStream DoPeriodHandling(byte[] data,bool add_Remove)
		{
			using(MemoryStream strm = new MemoryStream(data)){
				return DoPeriodHandling(strm,add_Remove);
			}
		}

		/// <summary>
		/// Does period handling.
		/// </summary>
		/// <param name="strm">Input stream.</param>
		/// <param name="add_Remove">If true add periods, else removes periods.</param>
		/// <returns></returns>
		public static MemoryStream DoPeriodHandling(Stream strm,bool add_Remove)
		{
			return DoPeriodHandling(strm,add_Remove,true);
		}

		/// <summary>
		/// Does period handling.
		/// </summary>
		/// <param name="strm">Input stream.</param>
		/// <param name="add_Remove">If true add periods, else removes periods.</param>
		/// <param name="setStrmPosTo0">If true sets stream position to 0.</param>
		/// <returns></returns>
		public static MemoryStream DoPeriodHandling(Stream strm,bool add_Remove,bool setStrmPosTo0)
		{			
			MemoryStream replyData = new MemoryStream();

			byte[] crlf = new byte[]{(byte)'\r',(byte)'\n'};

			if(setStrmPosTo0){
				strm.Position = 0;
			}

			StreamLineReader r = new StreamLineReader(strm);
			byte[] line = r.ReadLine();

			// Loop through all lines
			while(line != null){
				if(line.Length > 0){
					if(line[0] == (byte)'.'){
						/* Add period Rfc 2821 4.5.2
						   -  Before sending a line of mail text, the SMTP client checks the
						   first character of the line.  If it is a period, one additional
						   period is inserted at the beginning of the line.
						*/
						if(add_Remove){
							replyData.WriteByte((byte)'.');
							replyData.Write(line,0,line.Length);
						}
						/* Remove period Rfc 2821 4.5.2
						 If the first character is a period , the first characteris deleted.							
						*/
						else{
							replyData.Write(line,1,line.Length-1);
						}
					}
					else{
						replyData.Write(line,0,line.Length);
					}
				}					

				replyData.Write(crlf,0,crlf.Length);

				// Read next line
				line = r.ReadLine();
			}

			replyData.Position = 0;

			return replyData;
		}

		#endregion

		#region method ScanInvalid_CR_or_LF

		/// <summary>
		/// Scans invalid CR or LF combination in stream. Returns true if contains invalid CR or LF combination.
		/// </summary>
		/// <param name="strm">Stream which to check.</param>
		/// <returns>Returns true if contains invalid CR or LF combination.</returns>
		public static bool ScanInvalid_CR_or_LF(Stream strm)
		{
			StreamLineReader lineReader = new StreamLineReader(strm);
			byte[] line = lineReader.ReadLine();
			while(line != null){
				foreach(byte b in line){
					// Contains CR or LF. It cannot conatian such sumbols, because CR must be paired with LF
					// and we currently reading lines with CRLF combination.
					if(b == 10 || b == 13){
						return true;
					}
				}

				line = lineReader.ReadLine();
			}

			return false;
		}

		#endregion

		
		#region method GetHostName

		/// <summary>
		/// Gets host name. If fails returns 'UnkownHost'.
		/// </summary>
		/// <param name="IP"></param>
		/// <returns></returns>
		public static string GetHostName(IPAddress IP)
		{
			// ToDo: use LS dns client instead, ms is slow

			try{
				return System.Net.Dns.GetHostByAddress(IP).HostName;
			}
			catch{
				return "UnknownHost";
			}
		}

		#endregion


		#region method GetArgsText

		/// <summary>
		/// Gets argument part of command text.
		/// </summary>
		/// <param name="input">Input srting from where to remove value.</param>
		/// <param name="cmdTxtToRemove">Command text which to remove.</param>
		/// <returns></returns>
		public static string GetArgsText(string input,string cmdTxtToRemove)
		{
			string buff = input.Trim();
			if(buff.Length >= cmdTxtToRemove.Length){
				buff = buff.Substring(cmdTxtToRemove.Length);
			}
			buff = buff.Trim();

			return buff;
		}

		#endregion

		
		#region method IsNumber

		/// <summary>
		/// Checks if specified string is number(long).
		/// </summary>
		/// <param name="str"></param>
		/// <returns></returns>
		public static bool IsNumber(string str)
		{
			try{
				Convert.ToInt64(str);
				return true;
			}
			catch{
				return false;
			}
		}

		#endregion


		#region method Base64Encode

		/// <summary>
		/// Encodes data with base64 encoding.
		/// </summary>
		/// <param name="data">Data to encode.</param>
		/// <returns></returns>
		public static byte[] Base64Encode(byte[] data)
		{			
			MemoryStream base64Data = new MemoryStream(System.Text.Encoding.Default.GetBytes(Convert.ToBase64String(data)));

			// .NET won't split base64 data to lines, we need to do it manually
			MemoryStream retVal = new MemoryStream();
			while(base64Data.Position < base64Data.Length){
				byte[] dataLine = new byte[76];
				int readedCount = base64Data.Read(dataLine,0,dataLine.Length);

				retVal.Write(dataLine,0,readedCount);
				retVal.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);
			}
			
			return retVal.ToArray();
		}

		#endregion

		#region method Base64Decode

		/// <summary>
		/// Decodes base64 data.
		/// </summary>
		/// <param name="base64Data">Base64 decoded data.</param>
		/// <returns></returns>
		public static byte[] Base64Decode(byte[] base64Data)
		{
			string dataStr = System.Text.Encoding.Default.GetString(base64Data);
			if(dataStr.Trim().Length > 0){
				return Convert.FromBase64String(dataStr);
			}
			else{
				return new byte[]{};
			}
		}

		#endregion

		#region method QuotedPrintableEncode

		/// <summary>
		/// Encodes data with quoted-printable encoding.
		/// </summary>
		/// <param name="data">Data to encode.</param>
		/// <returns></returns>
		public static byte[] QuotedPrintableEncode(byte[] data)
		{
			/* Rfc 2045 6.7. Quoted-Printable Content-Transfer-Encoding
			 
			(2) (Literal representation) Octets with decimal values of 33 through 60 inclusive, 
				and 62 through 126, inclusive, MAY be represented as the US-ASCII characters which
				correspond to those octets (EXCLAMATION POINT through LESS THAN, and GREATER THAN 
				through TILDE, respectively).
			
			(3) (White Space) Octets with values of 9 and 32 MAY be represented as US-ASCII TAB (HT) and 
			    SPACE characters, respectively, but MUST NOT be so represented at the end of an encoded line. 
				You must encode it =XX.
			
			(5) Encoded lines must not be longer than 76 characters, not counting the trailing CRLF. 
				If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks
				must be used.  An equal sign as the last character on a encoded line indicates such 
				a non-significant ("soft") line break in the encoded text.
				
			*)  If binary data is encoded in quoted-printable, care must be taken to encode 
			    CR and LF characters as "=0D" and "=0A", respectively.	 

			*/

			int lineLength = 0;
			// Encode bytes <= 33 , >= 126 and 61 (=)
			MemoryStream retVal = new MemoryStream();
			foreach(byte b in data){
				// Suggested line length is exceeded, add soft line break
				if(lineLength > 75){
					retVal.Write(Encoding.ASCII.GetBytes("=\r\n"),0,3);
					lineLength = 0;
				}

				// We need to encode that byte
				if(b <= 33 || b >= 126 || b == 61){					
					retVal.Write(Encoding.ASCII.GetBytes("=" + b.ToString("X2").ToUpper()),0,3);
					lineLength += 3;
				}
				// We don't need to encode that byte, just write it to stream
				else{
					retVal.WriteByte(b);
					lineLength++;
				}
			}

			return retVal.ToArray();
		}

		#endregion

		#region method QuotedPrintableDecode

		/// <summary>
		/// quoted-printable decoder.
		/// </summary>
		/// <param name="encoding">Input string encoding.</param>
		/// <param name="data">Data which to encode.</param>
		/// <param name="includeCRLF">Specified if line breaks are included or skipped.</param>
		/// <returns>Returns decoded data with specified encoding.</returns>
		public static string QuotedPrintableDecode(System.Text.Encoding encoding,byte[] data,bool includeCRLF)
		{
			return encoding.GetString(QuotedPrintableDecodeB(data,includeCRLF));
		}

		/// <summary>
		/// quoted-printable decoder.
		/// </summary>
		/// <param name="data">Data which to encode.</param>
		/// <param name="includeCRLF">Specified if line breaks are included or skipped.</param>
		/// <returns>Returns decoded data.</returns>
		public static byte[] QuotedPrintableDecodeB(byte[] data,bool includeCRLF)
		{
			MemoryStream strm = new MemoryStream(data);			
			MemoryStream dStrm = new MemoryStream();

			int b = strm.ReadByte();
			while(b > -1){
				// Hex eg. =E4
				if(b == '='){
					byte[] buf = new byte[2];
					strm.Read(buf,0,2);

					// <CRLF> followed by =, it's splitted line
					if(!(buf[0] == '\r' && buf[1] == '\n')){
						try{
							int val = int.Parse(System.Text.Encoding.ASCII.GetString(buf),System.Globalization.NumberStyles.HexNumber);
							dStrm.WriteByte((byte)val);
						}
						catch{ // If worng hex value, just skip this chars							
						}
					}
				}
				else{
					// For text line breaks are included, for binary data they are excluded

					if(includeCRLF){
						dStrm.WriteByte((byte)b);
					}
					else{
						// Skip \r\n they must be escaped
						if(b != '\r' && b != '\n'){
							dStrm.WriteByte((byte)b);
						}
					}					
				}

				b = strm.ReadByte();
			}

			return dStrm.ToArray();
		}

		#endregion

		#region method QDecode

		/// <summary>
		/// "Q" decoder. This is same as quoted-printable, except '_' is converted to ' '.
		/// </summary>
		/// <param name="encoding">Input string encoding.</param>
		/// <param name="data">String which to encode.</param>
		/// <returns>Returns decoded string.</returns>		
		public static string QDecode(System.Text.Encoding encoding,string data)
		{
			return QuotedPrintableDecode(encoding,System.Text.Encoding.ASCII.GetBytes(data.Replace("_"," ")),true);

		//  REMOVEME:
		//  15.09.2004 - replace must be done before encoding
		//	return QuotedPrintableDecode(encoding,System.Text.Encoding.ASCII.GetBytes(data)).Replace("_"," ");
		}

		#endregion

		#region method CanonicalDecode

		/// <summary>
		/// Canonical decoding. Decodes all canonical encoding occurences in specified text.
		/// Usually mime message header unicode/8bit values are encoded as Canonical.
		/// Format: =?charSet?type[Q or B]?encoded string?= .
		/// </summary>
		/// <param name="text">Text to decode.</param>
		/// <returns>Returns decoded text.</returns>
		public static string CanonicalDecode(string text)
		{
			// =?charSet?type[Q or B]?encoded string?=
			// 
			// Examples:
			//   =?utf-8?q?Buy a Rolex?=
			//   =?ISO-8859-1?Q?Asb=F8rn_Miken?=

			Regex regex = new Regex(@"\=\?(?<charSet>[\w\-]*)\?(?<type>[qQbB])\?(?<text>[\w\s_\-=*+;:,./]*)\?\=");

			// REMOVEME: 22.12.2004 alows ?= inside and won't handle multiple encodings
		//	Regex regex = new Regex(@"\=\?(?<charSet>[\w\-]*)\?(?<type>[qQbB])\?(?<text>[\w\W_\-=*+;:,./]*)\?\=");

		//  REMOVEME: 17.08.2004
		//	Didn't allow some chars (=; ...)	
		//	Regex regex = new Regex(@"\=\?(?<charSet>[\w\-]*)\?(?<type>[qQbB])\?(?<text>[\w\W]*)\?\=");

			MatchCollection m = regex.Matches(text);
			foreach(Match match in m){
				try{
					System.Text.Encoding enc = System.Text.Encoding.GetEncoding(match.Groups["charSet"].Value);
					// QDecode
					if(match.Groups["type"].Value.ToLower() == "q"){
						text = text.Replace(match.Value,Core.QDecode(enc,match.Groups["text"].Value));
					}
					// Base64
					else{
						text = text.Replace(match.Value,enc.GetString(Convert.FromBase64String(match.Groups["text"].Value)));
					}
				}
				catch/*(Exception x)*/{
					// If parsing fails, just leave this string as is
				}
			}

			return text;
		}

		#endregion

		#region method CanonicalEncode

		/// <summary>
		/// Canonical encoding.
		/// </summary>
		/// <param name="str">String to encode.</param>
		/// <param name="charSet">With what charset to encode string. If you aren't sure about it, utf-8 is suggested.</param>
		/// <returns>Returns encoded text.</returns>
		public static string CanonicalEncode(string str,string charSet)
		{
			// Contains non ascii chars, need to encode
			if(!IsAscii(str)){
				string retVal = "=?" + charSet + "?" + "B?";
				retVal += Convert.ToBase64String(System.Text.Encoding.GetEncoding(charSet).GetBytes(str));
				retVal += "?=";

				return retVal;
			}

			return str;
		}

		#endregion

		#region method IsAscii

		/// <summary>
		/// Checks if specified string data is acii data.
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public static bool IsAscii(string data)
		{			
			foreach(char c in data){
				if((int)c > 127){ 
					return false;
				}
			}

			return true;
		}

		#endregion

	}
}
