
using System;
using System.IO;
using System.Collections.Generic;


namespace CommonMemoryStream
{
    public class CommonMS
    {
        // endian
        public static bool LittleEndian = true;
        /*
        public static void BitSwap(void* buffer, int typesize, int count)
        {
            // byte
            if (typesize <= 1) return;
            // 2byte
            if (typesize == 2)
            {
                var sptr = (UInt16*)buffer;
                for (int i = 0; i < count; i++)
                {
                    var t = *sptr;
                    *sptr = (UInt16)(((t & 0xff00) >> 8) | ((t & 0x00ff) << 8));
                    sptr++;
                }
                return;
            }
            // 4 byter
            if (typesize == 4)
            {
                var sptr = (UInt32*)buffer;
                for (int i = 0; i < count; i++)
                {
                    var t = *sptr;
                    *sptr = (UInt32)(((t & 0xff000000) >> 24) | ((t & 0x00ff0000) >> 8) | ((t & 0x0000ff00) << 8) | ((t & 0x000000ff) << 24));
                    sptr++;
                }
                return;
            }

            throw new ArgumentException("bitSwap : unsupport bit swap type length : " + typesize.ToString());
        }
        */
        public static void BitSwap(byte[] buf, int typesize)
        {
            var count = buf.Length / typesize;

            // byte
            if (typesize <= 1) return;
            // 2byte
            if (typesize == 2)
            {
                var p = 0;
                for (int i = 0; i < count; i++)
                {
                    var t1 = buf[p + 0];
                    var t2 = buf[p + 1];
                    buf[p + 0] = t2;
                    buf[p + 1] = t1;
                    p += 2;
                }
                return;
            }
            // 4 byter
            if (typesize == 4)
            {
                var p = 0;
                for (int i = 0; i < count; i++)
                {
                    var t1 = buf[p + 0];
                    var t2 = buf[p + 1];
                    var t3 = buf[p + 2];
                    var t4 = buf[p + 3];
                    buf[p + 0] = t4;
                    buf[p + 1] = t3;
                    buf[p + 2] = t2;
                    buf[p + 3] = t1;

                    p += 4;
                }
                return;
            }

            throw new ArgumentException("BitSwap : unsupport bit swap type length : " + typesize.ToString());
        }
        // binary
        public static void WriteString(MemoryStream ms, string s)
        {
            byte[] inToBytes(string ss)
            {
                var count = ss.Length;
                var buf = new byte[count * sizeof(char)];

                for (int i = 0; i < count; i++)
                {
                    int c = s[i];
                    buf[i * 2 + 0] = (byte)(c & 0xff);
                    buf[i * 2 + 1] = (byte)(c >> 8);
                }
                return buf;
            }

            {
                var count = s.Length;
                WriteUInt16(ms, (UInt16)count);

                if (count == 0) return;

                if (LittleEndian)
                {
                    var buf = inToBytes(s);
                    ms.Write(buf, 0, buf.Length);
                }
                else
                {
                    var buf = inToBytes(s);
                    BitSwap(buf, sizeof(char));
                    ms.Write(buf, 0, buf.Length);
                }
            }
        }
        public static string ReadString(MemoryStream ms, int max_ = 4096)
        {
            string inToString(byte[] buf)
            {
                var count = buf.Length / sizeof(char);
                var s = new char[count];

                for (var i = 0; i < count; i++)
                {
                    int c = buf[i * 2 + 0] | (buf[i * 2 + 1] << 8);
                    s[i] = (char)c;
                }
                return new string(s);
            }

            {
                int count = ReadUInt16(ms);
                if (count == 0) return "";
                if (count > max_)
                {
                    throw new FileLoadException("ReadString : too many string length : " + count.ToString());
                }

                var buf = new byte[count * sizeof(char)];
                if (ms.Read(buf, 0, buf.Length) != buf.Length)
                {
                    throw new FileLoadException("ReadString : can not read stream.");
                }

                if (!LittleEndian) BitSwap(buf, sizeof(char));
                var s = inToString(buf);

                return s;
            }
        }
        public static void WriteFloat(MemoryStream ms, float f)
        {
            var buf = BitConverter.GetBytes(f);
            if (!LittleEndian) BitSwap(buf, sizeof(float));

            ms.Write(buf, 0, buf.Length);
        }
        public static float ReadFloat(MemoryStream ms)
        {
            var buf = new byte[sizeof(float)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadFloat : can not read stream.");
            }

            return BitConverter.ToSingle(buf, 0);
        }
        public static void WriteDouble(MemoryStream ms, float d)
        {
            var buf = BitConverter.GetBytes(d);
            if (!LittleEndian) BitSwap(buf, sizeof(Double));

            ms.Write(buf, 0, buf.Length);
        }
        public static double ReadDouble(MemoryStream ms)
        {
            var buf = new byte[sizeof(Double)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadDouble : can not read stream.");
            }

            return BitConverter.ToDouble(buf, 0);
        }
        public static void WriteUInt32(MemoryStream ms, UInt32 v)
        {
            var buf = BitConverter.GetBytes(v);
            if (!LittleEndian) BitSwap(buf, sizeof(int));

            ms.Write(buf, 0, buf.Length);
        }
        public static UInt32 ReadUInt32(MemoryStream ms)
        {
            var buf = new byte[sizeof(UInt32)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadUInt32: can not read stream.");
            }

            return BitConverter.ToUInt32(buf, 0);
        }
        public static void WriteInt32(MemoryStream ms, int v)
        {
            var buf = BitConverter.GetBytes(v);
            if (!LittleEndian) BitSwap(buf, sizeof(int));

            ms.Write(buf, 0, buf.Length);
        }
        public static int ReadInt32(MemoryStream ms)
        {
            var buf = new byte[sizeof(Int32)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadInt32 : can not read stream.");
            }

            return BitConverter.ToInt32(buf, 0);
        }
        public static void WriteInt16(MemoryStream ms, Int16 v)
        {
            var buf = BitConverter.GetBytes(v);
            if (!LittleEndian) BitSwap(buf, sizeof(Int16));

            ms.Write(buf, 0, buf.Length);
        }
        public static Int16 ReadInt16(MemoryStream ms)
        {
            var buf = new byte[sizeof(Int16)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadInt16 : can not read stream.");
            }

            return BitConverter.ToInt16(buf, 0);
        }
        public static void WriteUInt16(MemoryStream ms, UInt16 v)
        {
            var buf = BitConverter.GetBytes(v);
            if (!LittleEndian) BitSwap(buf, sizeof(UInt16));

            ms.Write(buf, 0, buf.Length);
        }
        public static UInt16 ReadUInt16(MemoryStream ms)
        {
            var buf = new byte[sizeof(UInt16)];

            if (ms.Read(buf, 0, buf.Length) != buf.Length)
            {
                throw new FileLoadException("ReadUInt16 : can not read stream.");
            }

            return BitConverter.ToUInt16(buf, 0);
        }
        public static void WriteBool(MemoryStream ms, bool b)
        {
            var v = b ? 1 : 0;
            CommonMS.WriteInt32(ms, v);
        }
        public static bool ReadBool(MemoryStream ms)
        {
            var v = CommonMS.ReadInt32(ms);
            return v == 1;
        }
        public static void WriteHead(MemoryStream ms, string s)
        {
            WriteUInt32(ms, GetStringHash(s));
        }
        public static void ReadHead(MemoryStream ms, string s)
        {
            if (ReadUInt32(ms) != GetStringHash(s))
            {
                throw new FileLoadException($"ReadHead : head miss match : {s}");
            }
        }
        public static UInt32 GetStringHash(string s)
        {
            UInt32 hash = 0;
            var len = s.Length;
            if (len > 256) len = 256;

            for (var i = 0; i < len; i++)
            {
                hash += (UInt32)(s[i] << (i & 7));
            }
            return hash;
        }
        // text
        public enum BomType { None, utf8, utf16LE, utf16BE, utf32LE, utf32BE };
        public static BomType GetBom(MemoryStream ms)
        {
            var p = ms.Position;
            var len = ms.Length - ms.Position;

            var bom = new byte[4];
            bom[0] = 0x88;
            bom[1] = 0x88;
            bom[2] = 0x88;
            bom[3] = 0x88;

            if (len >= 1) bom[0] = (byte)ms.ReadByte();
            if (len >= 2) bom[1] = (byte)ms.ReadByte();
            if (len >= 3) bom[2] = (byte)ms.ReadByte();
            if (len >= 4) bom[3] = (byte)ms.ReadByte();
            ms.Position = p;

            if ((bom[0] == 0xff) && (bom[1] == 0xfe) && (bom[2] == 0x00) && (bom[3] == 0x00)) return BomType.utf32LE;
            if ((bom[0] == 0x00) && (bom[1] == 0x00) && (bom[2] == 0xfe) && (bom[3] == 0xff)) return BomType.utf32BE;
            if ((bom[0] == 0xef) && (bom[1] == 0xbb) && (bom[2] == 0xbf)) return BomType.utf8;
            if ((bom[0] == 0xff) && (bom[1] == 0xfe)) return BomType.utf16LE;
            if ((bom[0] == 0xfe) && (bom[1] == 0xff)) return BomType.utf16BE;

            return BomType.None;
        }
        public static void SkipBom(MemoryStream ms, BomType encoding)
        {
            switch (encoding)
            {
                case BomType.utf8:
                    ms.Position += 3;
                    break;
                case BomType.utf16LE:
                case BomType.utf16BE:
                    ms.Position += 2;
                    break;
                case BomType.utf32LE:
                case BomType.utf32BE:
                    ms.Position += 4;
                    break;
            }
        }
        public static List<string> ReadText(MemoryStream ms, int bytesize = -1, BomType defencoding = BomType.utf8)
        {
            var bom = GetBom(ms);
            SkipBom(ms, bom);
            if (bom == BomType.None) bom = defencoding;

            if (bytesize < 0)
            {
                bytesize = (int)(ms.Length - ms.Position);
            }

            byte[] buf = null;
            if (bom == BomType.utf16LE)
            {
                buf = new byte[bytesize];
				ms.Read(buf, 0, bytesize);
				if (!LittleEndian) BitSwap(buf, sizeof(UInt16));
                
            }
            else if (bom == BomType.utf16BE)
            {
                buf = new byte[bytesize];
                ms.Read(buf, 0, bytesize);
				if (LittleEndian) BitSwap(buf, sizeof(UInt16));
			}
            else if (bom == BomType.utf8)
            {
				throw new Exception("ReadStringText : unsupport encode : utf8");
			}
            else
            {
                throw new Exception("ReadStringText : unsupport encode : utf32");
            }

            // split \r\n
            var sl = new List<string>();
            {
                int count = buf.Length / 2;
                var s = "";
                for (var i = 0; i < count; i++)
                {
                    char c = (char)(buf[i * 2 + 0] | (buf[i * 2 + 1] << 8));
                    if (c == '\r') continue;    // ignore
                    if (c == '\n')
                    {
                        sl.Add(s);
                        s = "";
                        continue;
                    }
                    s += c;
                }
                if (s != "") sl.Add(s);
            }

            return sl;
        }
        // file io
        public static MemoryStream LoadFromFile(string filename)
        {
            bool complete = false;
            MemoryStream ms = null;
            using (var fs = new FileStream(filename,FileMode.Open))
            {
                var buf = new byte[fs.Length];
                fs.Read(buf, 0, (int)fs.Length);
                ms = new MemoryStream(buf, 0, buf.Length);  // read only
                ms.Position = 0;
                complete = true;
            }
            if (!complete) return null;
            return ms;
        }
    }
    public class UnicodeConverter
    {
		public static byte[] utf16to8(string s16) {
			// convert utf32
			var s32 = new int[s16.Length];
			int s32len = 0;
			{
				var l = s16.Length;

				var i = 0;
				while (i < l) { 
					UInt16 c1, c2;
					c1 = (UInt16)s16[i];
					c2 = 0;

					if (0xd800 <= c1 && c1 < 0xdc00) {		// high surrogate	
						if (i + 1 < l) c2 = s16[i + 1];

						if (0xdc00 <= c2 && c2 < 0xe000) {	// low surrogate
							s32[s32len] = 0x10000 + (c1 - 0xd800) * 0x400 + (c1 - 0xdc00);
							i += 2;
							s32len++;
						}
						else if (c2 == 0) {
							s32[s32len] = c1;
							i += 2;
							s32len++;
						}else{
							// error;
							i += 2;
							break;
						}
					}
					else {
						s32[s32len] = c1;
						i += 1;
						s32len++;
					}
				}
			}
			// convert utf8
			var s8 = new byte[s32len*4];
			var s8len = 0;
			for (var i = 0; i<s32len; i++) {
				int c32 = s32[i];

				if (c32 < 0 || c32 > 0x10FFFF) {
					// error
					break;
				}

				if (c32 < 128) {
					s8[s8len + 0] = (byte)c32;
					s8len += 1;
				}
				else if (c32 < 2048) {
					s8[s8len + 0] = (byte)(0xC0 | (c32 >> 6));
					s8[s8len + 1] = (byte)(0x80 | (c32 & 0x3f));
					s8len += 2;
				}
				else if (c32 < 65536) {
					s8[s8len + 0] = (byte)(0xE0 | (c32 >> 12));
					s8[s8len + 1] = (byte)(0x80 | ((c32 >> 6) & 0x3F));
					s8[s8len + 2] = (byte)(0x80 | (c32 & 0x3F));
					s8len += 3;
				}
				else {
					s8[s8len + 0] = (byte)(0xF0 | (c32 >> 18));
					s8[s8len + 1] = (byte)(0x80 | ((c32 >> 12) & 0x3F));
					s8[s8len + 2] = (byte)(0x80 | ((c32 >> 6) & 0x3F));
					s8[s8len + 3] = (byte)(0x80 | (c32 & 0x3F));
					s8len += 4;
				}
			}
			var buf = new byte[s8len];
			s8.CopyTo(buf, 0);
			return buf;
		}
		public static string utf8to16(byte[] s8) {
			// convert utf32
			var s32 = new int[s8.Length];
			int s32len = 0;
			{
				var l = s8.Length;
				var i = 0;
				while (i < l) { 
					byte c1 = s8[i];

					var wordlen = 0;
					{
						if (0 <= c1 && c1 < 0x80) {
							wordlen = 1;
						}else if (0xc2 <= c1 && c1< 0xe0) {
							wordlen = 2;
						}else if (0xe0 <= c1 && c1< 0xf0) {
							wordlen = 3;
						}else if (0xf0 <= c1 && c1< 0xf8) {
							wordlen = 4;
						}
					}
					if (wordlen == 0) break;
					if (i + wordlen > l) break;

					switch (wordlen) {
					case 1:
						s32[s32len] = (s8[i + 0]);
						s32len++;
						i += 1;
						break;
					case 2:
						if ((c1 & 0x1e) == 0) break;

						s32[s32len] = ((s8[i + 0] & 0x1f) << 6) | (s8[i + 1] & 0x3f);
						s32len++;
						i += 2;
						break;
					case 3:
						if ((s8[i + 0] & 0x0f) == 0) {
							if ((s8[i + 1] & 0x20) == 0) break;
						}

						s32[s32len] = ((s8[i + 0] & 0x0f) << 12) | ((s8[i + 1] & 0x3f) << 6) | ((s8[i + 2] & 0x3f));
						s32len++;
						i += 3;
						break;
					case 4:
						if ((s8[i + 0] & 0x07) == 0) {
							if ((s8[i + 1] & 0x30) == 0) break;
						}

						s32[s32len] = ((s8[i + 0] & 0x07) << 18) | ((s8[i + 1] & 0x3f) << 12) | ((s8[i + 2] & 0x3f) << 6) | ((s8[i + 3] & 0x3f));
						s32len++;
						i += 4;
						break;
					}
				}
			}
			// convert utf16
			string s16 = "";
			for (var i = 0; i<s32len; i++) {
				int c32 = s32[i];

				if (c32 < 0 || c32 > 0x10FFFF) break;

				if (c32 < 0x10000) {
					s16 += (char)c32;
				}
				else {
					s16 += (char)((c32 - 0x10000) / 0x400 + 0xd800);
					s16 += (char)((c32 - 0x10000) % 0x400 + 0xdc00);
				}
			}

			return s16;
		}

    }
}