using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

namespace miew.IO
{
	using String = System.String;

	public static class File
	{
		static public String Read(string szfile)
		{
			String ret = String.Empty;
			try
			{
				using (FileStream fs = System.IO.File.Open(szfile, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					// ?? manually detect BOM, because can't seem to set default encoding to 874 using 'detect from BOM' feature of StreamReader constructor 
					byte[] BOM = new byte[4];
					fs.Read(BOM, 0, 4);
					fs.Seek(0, SeekOrigin.Begin);

					if ((BOM[0] == 0xef && BOM[1] == 0xbb && BOM[2] == 0xbf) ||				// UTF-8
						(BOM[0] == 0xFE && BOM[1] == 0xFF) ||								// Unicode (Big-Endian)
						(BOM[0] == 0xFF && BOM[1] == 0xFE) ||								// Unicode (Little-Endian)
						(BOM[0] == 0 && BOM[1] == 0 && BOM[2] == 0xfe && BOM[3] == 0xff) ||	// UTF-32
						(BOM[0] == 0x2b && BOM[1] == 0x2f && BOM[2] == 0x76))				// UTF-7
					{
						using (StreamReader sr = new StreamReader(fs, true))	// (auto-detect BOM)
							ret = sr.ReadToEnd();
					}
					else
						using (StreamReader sr = new StreamReader(fs /*, Encoding.Default */))
							ret = sr.ReadToEnd();
				}
			}
			catch (FileNotFoundException fnf)
			{
				Console.WriteLine("File not found: {0}", fnf.FileName);
				Environment.Exit(1);
			}
			return ret;
		}


		/// <summary>
		/// Case-insensitive search for a file
		/// </summary>
		static public String CaseInsensitiveFilename(String s_dir, String file)
		{
			return Directory.EnumerateFiles(s_dir, "*", SearchOption.TopDirectoryOnly)
											.FirstOrDefault(f => Path.GetFileName(f).ToLower() == file);
		}
	};

	public static class Extensions
	{
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> Lines(this TextReader tr)
		{
			String l;
			while ((l = tr.ReadLine()) != null)
				yield return l;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static Guid ReadGuid(this BinaryReader br)
		{
			return new Guid(br.ReadBytes(16));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Forces unicode encoding, as opposed to the BinaryReader default
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String ReadUnicodeString(this BinaryReader br)
		{
			ushort cch = br.ReadUInt16();
			if (cch == 0)
				return String.Empty;
			return new String(br.ReadChars(cch));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static DateTime ReadSystemTime(this BinaryReader br)
		{
			return DateTime.FromBinary(br.ReadInt64());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void Write(this BinaryWriter bw, Guid guid)
		{
			bw.Write(guid.ToByteArray());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void WriteUnicodeString(this BinaryWriter bw, String s)
		{
			if (String.IsNullOrEmpty(s))
			{
				bw.Write((ushort)0);
				return;
			}
			bw.Write((ushort)s.Length);
			bw.Write(s.ToCharArray(), 0, s.Length);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void Write(this BinaryWriter bw, DateTime dt)
		{
			bw.Write(dt.ToBinary());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void Write67EncodedUint(this BinaryWriter bw, uint i)
		{
			if (miew.BitArray.Extensions.OnesCount(i) == 1)
			{
				bw.Write((byte)(0x80 | miew.BitArray.Extensions.OnlyBitPosition(i)));
			}
			else
			{
				byte b = (byte)(i & 0x3F);
				i >>= 6;
				if (i == 0)
					bw.Write(b);
				else
				{
					bw.Write((byte)(0x40 | b));
					while (true)
					{
						b = (byte)(i & 0x7F);
						i >>= 7;
						if (i == 0)
						{
							bw.Write(b);
							break;
						}
						bw.Write((byte)(0x80 | b));
					}
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static uint Read67EncodedUint(this BinaryReader br)
		{
			byte b = br.ReadByte();
			if ((b & 0x80) > 0)
				return (uint)1 << (b & 0x1F);
			uint i = (uint)(b & 0x3F);
			if ((b & 0x40) == 0)
				return i;
			int r = 6;
			while (true)
			{
				b = br.ReadByte();
				i |= (uint)(b & 0x7F) << r;
				if ((b & 0x80) == 0)
					return i;
				r += 7;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Write the specified thin string to the memory stream
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void WriteStringAsByteArr(this MemoryStream ms, String thin)
		{
			Byte[] buf = miew.String.Thin.Extensions.ToByteArr(thin);
			ms.Write(buf);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Image a portion of the specified string to the memory stream in little-endian Unicode byte order,
		/// without creating an intermediate string object. However, Stringbuilder(String, int, int) is faster than this.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static unsafe void Write(this MemoryStream ms, String s, int index, int length)
		{
			if (index < 0 || index + length >= s.Length)
				throw new ArgumentException();
			long idx = ms.Position;
			int cb = length * sizeof(Char);
			ms.Position = idx + cb;
			ms.SetLength(ms.Position);
			fixed (Char* p = s)
			{
				IntPtr ip = new IntPtr(p + index);
				Marshal.Copy(ip, ms.GetBuffer(), (int)idx, cb);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void Write(this Stream str, Byte[] buf)
		{
			str.Write(buf, 0, buf.Length);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static void AlignQword(this Stream str)
		{
			long pos = str.Position;
			long p2 = (pos + 7) & ~7;
			if (p2 != pos)
			{
				if (str.Length <= p2)
					str.Write(new byte[8 - (pos & 7)]);
				else
					str.Seek(p2, SeekOrigin.Begin);
			}
		}
	}
}