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

using LumiSoft.Net.IO;

namespace LumiSoft.Net
{
    /// <summary>
    /// Common utility methods.
    /// </summary>
    public class Net_Utils
    {
        #region static method GetLocalHostName

        /// <summary>
        /// Gets local host name or argument <b>hostName</b> value if it's specified.
        /// </summary>
        /// <param name="hostName">Host name or null.</param>
        /// <returns>Returns local host name or argument <b>hostName</b> value if it's specified.</returns>
        public static string GetLocalHostName(string hostName)
        {
            if(string.IsNullOrEmpty(hostName)){
                return System.Net.Dns.GetHostName();
            }
            else{
                return hostName;
            }
        }

        #endregion

        #region static method CompareArray

        /// <summary>
        /// Compares if specified array itmes equals.
        /// </summary>
        /// <param name="array1">Array 1.</param>
        /// <param name="array2">Array 2</param>
        /// <returns>Returns true if both arrays are equal.</returns>
        public static bool CompareArray(Array array1,Array array2)
        {
            return CompareArray(array1,array2,array2.Length);
        }

        /// <summary>
        /// Compares if specified array itmes equals.
        /// </summary>
        /// <param name="array1">Array 1.</param>
        /// <param name="array2">Array 2</param>
        /// <param name="array2Count">Number of bytes in array 2 used for compare.</param>
        /// <returns>Returns true if both arrays are equal.</returns>
        public static bool CompareArray(Array array1,Array array2,int array2Count)
        {
            if(array1 == null && array2 == null){
                return true;
            }
            if(array1 == null && array2 != null){
                return false;
            }
            if(array1 != null && array2 == null){
                return false;
            }            
            if(array1.Length != array2Count){
                return false;
            }
            else{
                for(int i=0;i<array1.Length;i++){
                    if(!array1.GetValue(i).Equals(array2.GetValue(i))){
                        return false;
                    }
                }
            }

            return true;
        }

        #endregion

        #region static method ReverseArray

        /// <summary>
        /// Reverses the specified array elements.
        /// </summary>
        /// <param name="array">Array elements to reverse.</param>
        /// <returns>Returns array with reversed items.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>array</b> is null.</exception>
        public static Array ReverseArray(Array array)
        {
            if(array == null){
                throw new ArgumentNullException("array");
            }

            Array.Reverse(array);

            return array;
        }

        #endregion

        #region static method ArrayToString

        /// <summary>
        /// Convert array elements to string.
        /// </summary>
        /// <param name="values">String values.</param>
        /// <param name="delimiter">Values delimiter.</param>
        /// <returns>Returns array elements as string.</returns>
        public static string ArrayToString(string[] values,string delimiter)
        {
            if(values == null){
                return "";
            }

            StringBuilder retVal = new StringBuilder();
            for(int i=0;i<values.Length;i++){
                if(i > 0){
                    retVal.Append(delimiter);
                }

                retVal.Append(values[i]);
            }

            return retVal.ToString();
        }

        #endregion

        #region static method StreamCopy

        /// <summary>
        /// Copies <b>source</b> stream data to <b>target</b> stream.
        /// </summary>
        /// <param name="source">Source stream. Reading starts from stream current position.</param>
        /// <param name="target">Target stream. Writing starts from stream current position.</param>
        /// <param name="blockSize">Specifies transfer block size in bytes.</param>
        /// <returns>Returns number of bytes copied.</returns>
        public static long StreamCopy(Stream source,Stream target,int blockSize)
        {
            if(source == null){
                throw new ArgumentNullException("source");
            }
            if(target == null){
                throw new ArgumentNullException("target");
            }
            if(blockSize < 1024){
                throw new ArgumentException("Argument 'blockSize' value must be >= 1024.");
            }

            byte[] buffer      = new byte[blockSize];
            long   totalReaded = 0;            
            while(true){
                int readedCount = source.Read(buffer,0,buffer.Length);
                // We reached end of stream, we readed all data sucessfully.
                if(readedCount == 0){
                    return totalReaded;
                }
                else{
                    target.Write(buffer,0,readedCount);
                    totalReaded += readedCount;
                }
            }
        }

        #endregion


        #region static method CompareIP

        /// <summary>
        /// Compares 2 IP addresses. Returns 0 if IPs are equal, 
        /// returns positive value if destination IP is bigger than source IP,
        /// returns negative value if destination IP is smaller than source IP.
        /// </summary>
        /// <param name="source">Source IP address.</param>
        /// <param name="destination">Destination IP address.</param>
        /// <returns>Returns 0 if IPs are equal, 
        /// returns positive value if destination IP is bigger than source IP,
        /// returns negative value if destination IP is smaller than source IP.
        /// </returns>
        public static int CompareIP(IPAddress source,IPAddress destination)
        {
            byte[] sourceIpBytes      = source.GetAddressBytes();
            byte[] destinationIpBytes = destination.GetAddressBytes();

            // IPv4 and IPv6
            if(sourceIpBytes.Length < destinationIpBytes.Length){
                return 1;
            }
            // IPv6 and IPv4
            else if(sourceIpBytes.Length > destinationIpBytes.Length){
                return -1;
            }
            // IPv4 and IPv4 OR IPv6 and IPv6
            else{                
                for(int i=0;i<sourceIpBytes.Length;i++){
                    if(sourceIpBytes[i] < destinationIpBytes[i]){
                        return 1;
                    }
                    else if(sourceIpBytes[i] > destinationIpBytes[i]){
                        return -1;
                    }
                }

                return 0;
            }
        }

        #endregion

        #region static method IsIPAddress

        /// <summary>
        /// Gets if the specified string value is IP address.
        /// </summary>
        /// <param name="value">Value to check.</param>
        /// <returns>Returns true if specified value is IP address.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
        public static bool IsIPAddress(string value)
        {
            if(value == null){
                throw new ArgumentNullException("value");
            }

            IPAddress ip = null;

            return IPAddress.TryParse(value,out ip);
        }

        #endregion

        #region static method IsMulticastAddress

        /// <summary>
        /// Gets if the specified IP address is multicast address.
        /// </summary>
        /// <param name="ip">IP address.</param>
        /// <returns>Returns true if <b>ip</b> is muticast address, otherwise false.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>ip</b> s null reference.</exception>
        public static bool IsMulticastAddress(IPAddress ip)
        {
            if(ip == null){
                throw new ArgumentNullException("ip");
            }

            // IPv4 multicast 224.0.0.0 to 239.255.255.255

            if(ip.IsIPv6Multicast){
                return true;
            }
            else if(ip.AddressFamily == AddressFamily.InterNetwork){
                byte[] bytes = ip.GetAddressBytes();
                if(bytes[0] >= 224 && bytes[0] <= 239){
                    return true;
                }
            }

            return false;
        }

        #endregion

        #region static method IsPrivateIP

        /// <summary>
        /// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
        /// </summary>
        /// <param name="ip">IP address to check.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
        /// <returns>Returns true if IP is private IP.</returns>
        public static bool IsPrivateIP(string ip)
        {
            if(ip == null){
                throw new ArgumentNullException("ip");
            }

            return IsPrivateIP(IPAddress.Parse(ip));
        }

        /// <summary>
        /// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
        /// </summary>
        /// <param name="ip">IP address to check.</param>
        /// <returns>Returns true if IP is private IP.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
        public static bool IsPrivateIP(IPAddress ip)
        {
            if(ip == null){
                throw new ArgumentNullException("ip");
            }

			if(ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork){
				byte[] ipBytes = ip.GetAddressBytes();

				/* Private IPs:
					First Octet = 192 AND Second Octet = 168 (Example: 192.168.X.X) 
					First Octet = 172 AND (Second Octet >= 16 AND Second Octet <= 31) (Example: 172.16.X.X - 172.31.X.X)
					First Octet = 10 (Example: 10.X.X.X)
					First Octet = 169 AND Second Octet = 254 (Example: 169.254.X.X)

				*/

				if(ipBytes[0] == 192 && ipBytes[1] == 168){
					return true;
				}
				if(ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31){
					return true;
				}
				if(ipBytes[0] == 10){
					return true;
				}
				if(ipBytes[0] == 169 && ipBytes[1] == 254){
					return true;
				}
			}

			return false;
        }

        #endregion

        #region static method ParseIPEndPoint

        /// <summary>
        /// Parses IPEndPoint from the specified string value.
        /// </summary>
        /// <param name="value">IPEndPoint string value.</param>
        /// <returns>Returns parsed IPEndPoint.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public static IPEndPoint ParseIPEndPoint(string value)
        {
            if(value == null){
                throw new ArgumentNullException("value");
            }

            try{
                string[] ip_port = value.Split(':');

                return new IPEndPoint(IPAddress.Parse(ip_port[0]),Convert.ToInt32(ip_port[1]));
            }
            catch(Exception x){
                throw new ArgumentException("Invalid IPEndPoint value.","value",x);
            }
        }

        #endregion


        #region static method IsInteger

		/// <summary>
		/// Checks if specified string is integer(int/long).
		/// </summary>
		/// <param name="value"></param>
		/// <returns>Returns true if specified string is integer.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
		public static bool IsInteger(string value)
		{
            if(value == null){
                throw new ArgumentNullException("value");
            }

            long l = 0;

            return long.TryParse(value,out l);
		}

		#endregion

        #region static method IsAscii

        /// <summary>
        /// Gets if the specified string is ASCII string.
        /// </summary>
        /// <param name="value">String value.</param>
        /// <returns>Returns true if specified string is ASCII string, otherwise false.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
        public static bool IsAscii(string value)
        {
            if(value == null){
                throw new ArgumentNullException("value");
            }

            foreach(char c in value){
				if((int)c > 127){ 
					return false;
				}
			}

			return true;
        }

        #endregion

               
        #region static method IsSocketAsyncSupported

        /// <summary>
        /// Gets if socket async methods supported by OS.
        /// </summary>
        /// <returns>returns ture if supported, otherwise false.</returns>
        public static bool IsSocketAsyncSupported()
        {
            try{                            
                using(SocketAsyncEventArgs e = new SocketAsyncEventArgs()){
                    return true;
                }
            }
            catch(NotSupportedException nX){
                string dummy = nX.Message;
                
                return false;
            }
        }

        #endregion

        #region static method CreateSocket

        /// <summary>
        /// Creates new socket for the specified end point.
        /// </summary>
        /// <param name="localEP">Local end point.</param>
        /// <param name="protocolType">Protocol type.</param>
        /// <returns>Retruns newly created socket.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>localEP</b> is null reference.</exception>
        public static Socket CreateSocket(IPEndPoint localEP,ProtocolType protocolType)
        {
            if(localEP == null){
                throw new ArgumentNullException("localEP");
            }

            SocketType socketType = SocketType.Stream;
            if(protocolType == ProtocolType.Udp){
                socketType = SocketType.Dgram;
            }
                        
            if(localEP.AddressFamily == AddressFamily.InterNetwork){
                Socket socket = new Socket(AddressFamily.InterNetwork,socketType,protocolType);
                socket.Bind(localEP);

                return socket;
            }
            else if(localEP.AddressFamily == AddressFamily.InterNetworkV6){
                Socket socket = new Socket(AddressFamily.InterNetworkV6,socketType,protocolType);
                socket.Bind(localEP);

                return socket;
            }
            else{
                throw new ArgumentException("Invalid IPEndPoint address family.");
            }
        }

        #endregion


        #region static method ToHex

        /// <summary>
		/// Converts specified data to HEX string.
		/// </summary>
		/// <param name="data">Data to convert.</param>
		/// <returns>Returns hex string.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
		public static string ToHex(byte[] data)
		{
            if(data == null){
                throw new ArgumentNullException("data");
            }

			return BitConverter.ToString(data).ToLower().Replace("-","");
		}

		/// <summary>
		/// Converts specified string to HEX string.
		/// </summary>
		/// <param name="text">String to convert.</param>
		/// <returns>Returns hex string.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
		public static string ToHex(string text)
		{
            if(text == null){
                throw new ArgumentNullException("text");
            }

			return BitConverter.ToString(Encoding.Default.GetBytes(text)).ToLower().Replace("-","");
		}

		#endregion

        #region static method FromHex

		/// <summary>
		/// Converts hex byte data to normal byte data. Hex data must be in two bytes pairs, for example: 0F,FF,A3,... .
		/// </summary>
		/// <param name="hexData">Hex data.</param>
		/// <returns>Returns decoded data.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>hexData</b> is null reference.</exception>
		public static byte[] FromHex(byte[] hexData)
		{
            if(hexData == null){
                throw new ArgumentNullException("hexData");
            }

			if(hexData.Length < 2 || (hexData.Length / (double)2 != Math.Floor(hexData.Length / (double)2))){
				throw new Exception("Illegal hex data, hex data must be in two bytes pairs, for example: 0F,FF,A3,... .");
			}

			MemoryStream retVal = new MemoryStream(hexData.Length / 2);
			// Loop hex value pairs
			for(int i=0;i<hexData.Length;i+=2){
				byte[] hexPairInDecimal = new byte[2];
				// We need to convert hex char to decimal number, for example F = 15
				for(int h=0;h<2;h++){
					if(((char)hexData[i + h]) == '0'){
						hexPairInDecimal[h] = 0;
					}
					else if(((char)hexData[i + h]) == '1'){
						hexPairInDecimal[h] = 1;
					}
					else if(((char)hexData[i + h]) == '2'){
						hexPairInDecimal[h] = 2;
					}
					else if(((char)hexData[i + h]) == '3'){
						hexPairInDecimal[h] = 3;
					}
					else if(((char)hexData[i + h]) == '4'){
						hexPairInDecimal[h] = 4;
					}
					else if(((char)hexData[i + h]) == '5'){
						hexPairInDecimal[h] = 5;
					}
					else if(((char)hexData[i + h]) == '6'){
						hexPairInDecimal[h] = 6;
					}
					else if(((char)hexData[i + h]) == '7'){
						hexPairInDecimal[h] = 7;
					}
					else if(((char)hexData[i + h]) == '8'){
						hexPairInDecimal[h] = 8;
					}
					else if(((char)hexData[i + h]) == '9'){
						hexPairInDecimal[h] = 9;
					}
					else if(((char)hexData[i + h]) == 'A' || ((char)hexData[i + h]) == 'a'){
						hexPairInDecimal[h] = 10;
					}
					else if(((char)hexData[i + h]) == 'B' || ((char)hexData[i + h]) == 'b'){
						hexPairInDecimal[h] = 11;
					}
					else if(((char)hexData[i + h]) == 'C' || ((char)hexData[i + h]) == 'c'){
						hexPairInDecimal[h] = 12;
					}
					else if(((char)hexData[i + h]) == 'D' || ((char)hexData[i + h]) == 'd'){
						hexPairInDecimal[h] = 13;
					}
					else if(((char)hexData[i + h]) == 'E' || ((char)hexData[i + h]) == 'e'){
						hexPairInDecimal[h] = 14;
					}
					else if(((char)hexData[i + h]) == 'F' || ((char)hexData[i + h]) == 'f'){
						hexPairInDecimal[h] = 15;
					}
				}

				// Join hex 4 bit(left hex cahr) + 4bit(right hex char) in bytes 8 it
				retVal.WriteByte((byte)((hexPairInDecimal[0] << 4) | hexPairInDecimal[1]));
			}

			return retVal.ToArray();
		}

		#endregion


        #region static method FromBase64

        /// <summary>
        /// Decodes specified base64 data.
        /// </summary>
        /// <param name="data">Base64 string.</param>
        /// <returns>Returns decoded data.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
        public static byte[] FromBase64(string data)
        {
            if(data == null){
                throw new ArgumentNullException("data");
            }

            Base64 base64 = new Base64();

            return base64.Decode(data,true);
        }

        /// <summary>
        /// Decodes specified base64 data.
        /// </summary>
        /// <param name="data">Base64 data.</param>
        /// <returns>Returns decoded data.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
        public static byte[] FromBase64(byte[] data)
        {
            if(data == null){
                throw new ArgumentNullException("data");
            }

            Base64 base64 = new Base64();

            return base64.Decode(data,0,data.Length,true);
        }

        #endregion

        #region static method Base64Encode

        /// <summary>
		/// Encodes specified data with base64 encoding.
		/// </summary>
		/// <param name="data">Data to encode.</param>
		/// <returns></returns>
		public static byte[] Base64Encode(byte[] data)
		{
			return Base64EncodeEx(data,null,true);
		}

		/// <summary>
		/// Encodes specified data with bas64 encoding.
		/// </summary>
		/// <param name="data">Data to to encode.</param>
		/// <param name="base64Chars">Custom base64 chars (64 chars) or null if default chars used.</param>
		/// <param name="padd">Padd missing block chars. Normal base64 must be 4 bytes blocks, if not 4 bytes in block, 
		/// missing bytes must be padded with '='. Modified base64 just skips missing bytes.</param>
		/// <returns></returns>
		public static byte[] Base64EncodeEx(byte[] data,char[] base64Chars,bool padd)
		{
			/* RFC 2045 6.8.  Base64 Content-Transfer-Encoding
			
				Base64 is processed from left to right by 4 6-bit byte block, 4 6-bit byte block 
				are converted to 3 8-bit bytes.
				If base64 4 byte block doesn't have 3 8-bit bytes, missing bytes are marked with =. 
				
			
				Value Encoding  Value Encoding  Value Encoding  Value Encoding
					0 A            17 R            34 i            51 z
					1 B            18 S            35 j            52 0
					2 C            19 T            36 k            53 1
					3 D            20 U            37 l            54 2
					4 E            21 V            38 m            55 3
					5 F            22 W            39 n            56 4
					6 G            23 X            40 o            57 5
					7 H            24 Y            41 p            58 6
					8 I            25 Z            42 q            59 7
					9 J            26 a            43 r            60 8
					10 K           27 b            44 s            61 9
					11 L           28 c            45 t            62 +
					12 M           29 d            46 u            63 /
					13 N           30 e            47 v
					14 O           31 f            48 w         (pad) =
					15 P           32 g            49 x
					16 Q           33 h            50 y
					
				NOTE: 4 base64 6-bit bytes = 3 8-bit bytes				
					// |    6-bit    |    6-bit    |    6-bit    |    6-bit    |
					// | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 |
					// |    8-bit         |    8-bit        |    8-bit         |
			*/

			if(base64Chars != null && base64Chars.Length != 64){
				throw new Exception("There must be 64 chars in base64Chars char array !");
			}

			if(base64Chars == null){
				base64Chars = new char[]{
					'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
					'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
					'0','1','2','3','4','5','6','7','8','9','+','/'
				};
			}

			// Convert chars to bytes
			byte[] base64LoockUpTable = new byte[64];
			for(int i=0;i<64;i++){
				base64LoockUpTable[i] = (byte)base64Chars[i];
			}
						
			int encodedDataLength = (int)Math.Ceiling((data.Length * 8) / (double)6);
			// Retrun value won't be interegral 4 block, but has less. Padding requested, padd missing with '='
			if(padd && (encodedDataLength / (double)4 != Math.Ceiling(encodedDataLength / (double)4))){
				encodedDataLength += (int)(Math.Ceiling(encodedDataLength / (double)4) * 4) - encodedDataLength;
			}

			// See how many line brakes we need
			int numberOfLineBreaks = 0;
			if(encodedDataLength > 76){
				numberOfLineBreaks = (int)Math.Ceiling(encodedDataLength / (double)76) - 1;
			}

			// Construc return valu buffer
			byte[] retVal = new byte[encodedDataLength + (numberOfLineBreaks * 2)];  // * 2 - CRLF

			int lineBytes = 0;
			// Loop all 3 bye blocks
			int position = 0; 
			for(int i=0;i<data.Length;i+=3){
				// Do line splitting
				if(lineBytes >= 76){
					retVal[position + 0] = (byte)'\r';
					retVal[position + 1] = (byte)'\n';					
					position += 2;
					lineBytes = 0;
				}

				// Full 3 bytes data block
				if((data.Length - i) >= 3){
					retVal[position + 0] = base64LoockUpTable[data[i + 0] >> 2];
					retVal[position + 1] = base64LoockUpTable[(data[i + 0] & 0x3) << 4 | data[i + 1] >> 4];
					retVal[position + 2] = base64LoockUpTable[(data[i + 1] & 0xF) << 2 | data[i + 2] >> 6];
					retVal[position + 3] = base64LoockUpTable[data[i + 2] & 0x3F];
					position += 4;
					lineBytes += 4;
				}
				// 2 bytes data block, left (last block)
				else if((data.Length - i) == 2){
					retVal[position + 0] = base64LoockUpTable[data[i + 0] >> 2];
					retVal[position + 1] = base64LoockUpTable[(data[i + 0] & 0x3) << 4 | data[i + 1] >> 4];
					retVal[position + 2] = base64LoockUpTable[(data[i + 1] & 0xF) << 2];					
					if(padd){
						retVal[position + 3] = (byte)'=';
					}
				}
				// 1 bytes data block, left (last block)
				else if((data.Length - i) == 1){
					retVal[position + 0] = base64LoockUpTable[data[i + 0] >> 2];
					retVal[position + 1] = base64LoockUpTable[(data[i + 0] & 0x3) << 4];					
					if(padd){
						retVal[position + 2] = (byte)'=';
						retVal[position + 3] = (byte)'=';
					}
				}
			}

			return retVal;
		}

		#endregion

		#region static method Base64Decode
        
		/// <summary>
		/// Decodes base64 data. Defined in RFC 2045 6.8.  Base64 Content-Transfer-Encoding.
		/// </summary>
		/// <param name="base64Data">Base64 decoded data.</param>
		/// <param name="base64Chars">Custom base64 chars (64 chars) or null if default chars used.</param>
		/// <returns></returns>
		public static byte[] Base64DecodeEx(byte[] base64Data,char[] base64Chars)
		{
			/* RFC 2045 6.8.  Base64 Content-Transfer-Encoding
			
				Base64 is processed from left to right by 4 6-bit byte block, 4 6-bit byte block 
				are converted to 3 8-bit bytes.
				If base64 4 byte block doesn't have 3 8-bit bytes, missing bytes are marked with =. 
				
			
				Value Encoding  Value Encoding  Value Encoding  Value Encoding
					0 A            17 R            34 i            51 z
					1 B            18 S            35 j            52 0
					2 C            19 T            36 k            53 1
					3 D            20 U            37 l            54 2
					4 E            21 V            38 m            55 3
					5 F            22 W            39 n            56 4
					6 G            23 X            40 o            57 5
					7 H            24 Y            41 p            58 6
					8 I            25 Z            42 q            59 7
					9 J            26 a            43 r            60 8
					10 K           27 b            44 s            61 9
					11 L           28 c            45 t            62 +
					12 M           29 d            46 u            63 /
					13 N           30 e            47 v
					14 O           31 f            48 w         (pad) =
					15 P           32 g            49 x
					16 Q           33 h            50 y
					
				NOTE: 4 base64 6-bit bytes = 3 8-bit bytes				
					// |    6-bit    |    6-bit    |    6-bit    |    6-bit    |
					// | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 |
					// |    8-bit         |    8-bit        |    8-bit         |
			*/
			
			if(base64Chars != null && base64Chars.Length != 64){
				throw new Exception("There must be 64 chars in base64Chars char array !");
			}

			if(base64Chars == null){
				base64Chars = new char[]{
					'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
					'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
					'0','1','2','3','4','5','6','7','8','9','+','/'
				};
			}

			//--- Create decode table ---------------------//
			byte[] decodeTable = new byte[128];
			for(int i=0;i<128;i++){
				int mappingIndex = -1;
				for(int bc=0;bc<base64Chars.Length;bc++){
					if(i == base64Chars[bc]){
						mappingIndex = bc;
						break;
					}
				}

				if(mappingIndex > -1){
					decodeTable[i] = (byte)mappingIndex;
				}
				else{
					decodeTable[i] = 0xFF;
				}
			}
			//---------------------------------------------//

			byte[] decodedDataBuffer  = new byte[((base64Data.Length * 6) / 8) + 4];
			int    decodedBytesCount  = 0;
			int    nByteInBase64Block = 0;
			byte[] decodedBlock       = new byte[3];
			byte[] base64Block        = new byte[4];

			for(int i=0;i<base64Data.Length;i++){
				byte b = base64Data[i];

				// Read 4 byte base64 block and process it 			
				// Any characters outside of the base64 alphabet are to be ignored in base64-encoded data.

				// Padding char
				if(b == '='){
					base64Block[nByteInBase64Block] = 0xFF;
				}
				else{
					byte decodeByte = decodeTable[b & 0x7F];
					if(decodeByte != 0xFF){
						base64Block[nByteInBase64Block] = decodeByte;
						nByteInBase64Block++;
					}
				}

                /* Check if we can decode some bytes. 
                 * We must have full 4 byte base64 block or reached at the end of data.
                 */
                int encodedBytesCount = -1;
                // We have full 4 byte base64 block
                if(nByteInBase64Block == 4){
                    encodedBytesCount = 3;
                }
                // We have reached at the end of base64 data, there may be some bytes left
                else if(i == base64Data.Length - 1){
                    // Invalid value, we can't have only 6 bit, just skip 
                    if(nByteInBase64Block == 1){
                        encodedBytesCount = 0;
                    }
                    // There is 1 byte in two base64 bytes (6 + 2 bit)
                    else if(nByteInBase64Block == 2){
                        encodedBytesCount = 1;
                    }
                    // There are 2 bytes in two base64 bytes ([6 + 2],[4 + 4] bit)
                    else if(nByteInBase64Block == 3){
                        encodedBytesCount = 2;
                    }
                }

                // We have some bytes available to decode, decode them
                if(encodedBytesCount > -1){
                    decodedDataBuffer[decodedBytesCount + 0] = (byte)((int)base64Block[0] << 2         | (int)base64Block[1] >> 4);
					decodedDataBuffer[decodedBytesCount + 1] = (byte)(((int)base64Block[1] & 0xF) << 4 | (int)base64Block[2] >> 2);
					decodedDataBuffer[decodedBytesCount + 2] = (byte)(((int)base64Block[2] & 0x3) << 6 | (int)base64Block[3] >> 0);

                    // Increase decoded bytes count
					decodedBytesCount += encodedBytesCount;

                    // Reset this block, reade next if there is any
					nByteInBase64Block = 0;
                }
			}

			// There is some decoded bytes, construct return value
			if(decodedBytesCount > -1){
				byte[] retVal = new byte[decodedBytesCount];
				Array.Copy(decodedDataBuffer,0,retVal,0,decodedBytesCount);
				return retVal;
			}
			// There is no decoded bytes
			else{
				return new byte[0];
			}
		}

		#endregion


        #region static method ComputeMd5

        /// <summary>
        /// Computes md5 hash.
        /// </summary>
        /// <param name="text">Text to hash.</param>
        /// <param name="hex">Specifies if md5 value is returned as hex string.</param>
        /// <returns>Returns md5 value or md5 hex value.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
        public static string ComputeMd5(string text,bool hex)
        {
            if(text == null){
                throw new ArgumentNullException("text");
            }

            using(System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider()) {
                byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(text));

                if(hex){
			        return ToHex(hash).ToLower();
                }
                else{
                    return System.Text.Encoding.Default.GetString(hash);
                }
            }
        }

        #endregion


        //--- Obsolete ------------------------------------------------------------------
        
        #region static method IsIoCompletionPortsSupported

        /// <summary>
        /// Gets if IO completion ports supported by OS.
        /// </summary>
        /// <returns></returns>
        [Obsolete("Use method 'IsSocketAsyncSupported' instead.")]
        public static bool IsIoCompletionPortsSupported()
        {
            try{                            
                using(SocketAsyncEventArgs e = new SocketAsyncEventArgs()){
                    return true;
                }
            }
            catch(NotSupportedException nX){
                string dummy = nX.Message;
                
                return false;
            }
        }

        #endregion

    }
}
