// #pragma once
#ifndef CommonMemoryStream_H
#define CommonMemoryStream_H

#include <vector>
#include <string>
#include <algorithm>
//#include <math.h>
//#include <iostream>
#include <fstream>
#include <algorithm>

using namespace std;

namespace CommonMemoryStream {
	class MemoryStream
	{
	public:
		inline int64_t position() { return fposition; };
		inline void position(int64_t p) { 
			if (p < 0) p = 0;
			if (p > fsize) p = fsize;
			fposition = p; 
		}
		inline int64_t length() { return fsize; }

		// create from memory buffer. use read case.
		MemoryStream(void *buffer, int size)
		{
			fbuffer = (uint8_t*)buffer;
			fsize = size;
			fcapacity = size;
			fposition = 0;
		}
		// create empty. use write case.
		MemoryStream(int capacity_ = 0) {
			fbuffer = (uint8_t*)malloc(0);
			fsize = 0;
			fcapacity = 0;
			fposition = 0;
			if (capacity_ > 0) checkcapacity(capacity_, false);	// get capacity pool on start.
		}
		virtual ~MemoryStream() {
			free(fbuffer);
		}

		// clear buffer.
		void clear() {
			free(fbuffer);
			fbuffer = (uint8_t*)malloc(0);
			fsize = 0;
			fcapacity = 0;
			fposition = 0;
		}
		// read 1 byte
		uint8_t readbyte() {
			if (fposition == fsize) throw exception("MemoryStream : end of file.");
			auto b = fbuffer[fposition];
			fposition++;
			return b;
		}
		// read block. set byte length. 
		int read(void *buffer, int size) {
			if (fposition + size > fsize) {
				size = (int)(fsize - fposition);
			}
			if (size < 1) return size;

			{
				auto sptr = (uint8_t*)(fbuffer + fposition);
				auto dptr = (uint8_t*)buffer;
				memcpy(dptr, sptr, size);
			}
			fposition += size;
			return size;
		}
		// write 1byte
		void writebyte(uint8_t b) {
			checkcapacity(1);

			auto dptr = (uint8_t*)(fbuffer + fposition);
			*dptr = b;
			fposition++;
			fsize = max(fsize, fposition);
		}
		// write block. set byte length.
		int write(const void *buffer, int size) 
		{
			checkcapacity(size);
			{
				auto dptr = (uint8_t*)(fbuffer + fposition);
				auto sptr = (uint8_t*)buffer;
				memcpy(dptr, sptr, size);
			}
			fposition += size;
			fsize = max(fsize, fposition);
			return size;
		}

	protected:
		uint8_t* fbuffer = nullptr;
		int64_t fsize = 0;
		int64_t fposition = 0;
		int64_t fcapacity = 0;

		inline void checkcapacity(int32_t writesize, bool margin = true) {
			int64_t size = writesize + fposition;

			if (size <= fcapacity) return;

			// calc capacity
			int64_t needsize;
			if (margin) {
				// get double memory size on next time
				int64_t m = 2048;
				int64_t size = writesize + fposition;
				while (m < 1024 * 1024 * 1024) {
					if (size <= m) break;
					m *= 2;
				}
				needsize = max(m,size);
			}
			else {
				needsize = size;
			}

			// malloc
			if (fcapacity < needsize) {
				if (needsize > 1024 * 1024 * 1024) {
					throw std::exception("MemoryStream.malloc : too many memory size. can not alloc 1GB over.");
				}

				fbuffer = (uint8_t*)realloc(fbuffer, (size_t)needsize);
				
				if (fbuffer == nullptr) {
					throw std::exception("MemoryStream.malloc : can not alloc memory.");
				}

				fcapacity = needsize;
			}
		}
	};

	class CommonMS
	{
	public:
		static bool LittleEndian;
		static void bitswap(void *buffer, int typesize, int buffersize)
		{
			int count = buffersize / typesize;

			// byte
			if (typesize <= 1) return;
			// 2byte
			if (typesize == 2)
			{
				auto dptr = (uint16_t*)buffer;
				for (int i = 0; i < count; i++)
				{
					auto t = *dptr;
					*dptr = ((t & 0xff) << 8) | ((t >> 8) & 0xff);
					dptr++;
				}
				return;
			}
			// 4 byter
			if (typesize == 4)
			{
				auto dptr = (uint32_t*)buffer;
				for (int i = 0; i < count; i++)
				{
					auto t = *dptr;
					*dptr = ((t & 0xff000000) >> 24) | ((t & 0x00ff0000) >> 8) | ((t & 0x0000ff00) << 8) | ((t & 0x000000ff) << 24);
					dptr++;
				}
				return;
			}

			throw exception("BitSwap : unsupport bit swap type length.");
		}
		static void writeString(MemoryStream *ms, const wstring &s)
		{
			auto count = s.length();
			writeUInt16(ms, (uint16_t)count);

			if (count == 0) return;

			auto size = (int)(count * sizeof(wchar_t));
			if (LittleEndian)
			{
				ms->write(&s[0], size);
			}
			else
			{
				vector<uint8_t> temp(size);
				bitswap(&temp[0], sizeof(wchar_t) , size);
				ms->write(&temp[0], size);
			}
		}
		static wstring readString(MemoryStream *ms, int max_ = 4096)
		{
			int count = readUInt16(ms);
			if (count == 0) return L"";

			if (count > max_)
			{
				throw exception("ReadString : too many string length.");
			}

			int size = count * sizeof(wchar_t);
			vector<uint8_t> temp(size);
			if (ms->read(&temp[0],size) != size)
			{
				throw exception("ReadString : can not read stream.");
			}

			if (!LittleEndian) bitswap(&temp[0], sizeof(char), size);
			
			wstring s(count,0x00);
			memcpy(&s[0], &temp[0], size);
			return s;
		}
		static inline uint16_t readUInt16(MemoryStream *ms) {
			uint16_t v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeUInt16(MemoryStream *ms, uint16_t v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline int16_t readInt16(MemoryStream *ms) {
			int16_t v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeInt16(MemoryStream* ms, int16_t v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline uint32_t readUInt32(MemoryStream *ms) {
			uint32_t v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeUInt32(MemoryStream *ms, uint32_t v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline int32_t readInt32(MemoryStream *ms) {
			int32_t v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeInt32(MemoryStream *ms, int32_t v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline float readFloat(MemoryStream *ms) {
			float v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeFloat(MemoryStream *ms, float v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline double readDouble(MemoryStream *ms) {
			double v;
			ms->read(&v, sizeof(v));
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			return v;
		}
		static inline void writeDouble(MemoryStream *ms, double v) {
			if (!LittleEndian) bitswap(&v, sizeof(v), sizeof(v));
			ms->write(&v, sizeof(v));
		}
		static inline bool readBool(MemoryStream *ms) {
			return readInt32(ms) == 1;
		}
		static inline void writeBool(MemoryStream *ms, bool b) {
			int32_t v = b ? 1 : 0;
			writeInt32(ms, v);
		}
		// head
		static int getStringHash(const wstring &s)
		{
			int hash = 0;
			auto len = (int)s.length();
			if (len > 256) len = 256;

			for (auto i = 0; i < len; i++)
			{
				hash += s[i] << (i & 7);
			}
			return hash;
		}
		static inline void writeHead(MemoryStream *ms, const wstring &s)
		{
			writeUInt32(ms, getStringHash(s));
		}
		static inline void readHead(MemoryStream *ms, const wstring &s) {
			if (readUInt32(ms) != getStringHash(s))
			{
				throw exception("ReadHead : head miss match.");
			}
		}
		// text bom
		enum class BomType { None, utf8, utf16LE, utf16BE, utf32LE, utf32BE };
		static BomType getBom(MemoryStream *ms)
		{
			auto p = ms->position();
			auto len = ms->length() - ms->position();

			uint8_t bom[4];
			bom[0] = 0x88;
			bom[1] = 0x88;
			bom[2] = 0x88;
			bom[3] = 0x88;

			if (len >= 1) bom[0] = ms->readbyte();
			if (len >= 2) bom[1] = ms->readbyte();
			if (len >= 3) bom[2] = ms->readbyte();
			if (len >= 4) bom[3] = 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;
		}
		static void skipBom(MemoryStream *ms, BomType encoding)
		{
			switch (encoding)
			{
			case BomType::utf8:
				ms->position(ms->position() + 3);
				break;
			case BomType::utf16LE:
			case BomType::utf16BE:
				ms->position(ms->position() + 2);
				break;
			case BomType::utf32LE:
			case BomType::utf32BE:
				ms->position(ms->position() + 4);
				break;
			}
		}
		static void readText(MemoryStream *ms, vector<wstring> &sl, int bytesize = -1, BomType defencoding = BomType::utf8)
		{
			sl.clear();

			auto bom = getBom(ms);
			skipBom(ms, bom);
			if (bom == BomType::None) bom = defencoding;

			if (bytesize < 0)
			{
				bytesize = (int)(ms->length() - ms->position());
			}

			vector<uint8_t> buf;
			if (bom == BomType::utf16LE)
			{
				buf.resize(bytesize);
				ms->read(&buf[0], bytesize);
				if (!LittleEndian) bitswap(&buf[0], sizeof(uint16_t), bytesize);
			}
			else if (bom == BomType::utf16BE)
			{
				buf.resize(bytesize);
				ms->read(&buf[0], bytesize);
				if (LittleEndian) bitswap(&buf[0], sizeof(uint16_t), bytesize);
			}
			else if (bom == BomType::utf8)
			{
				throw exception("ReadStringText : unsupport encode : utf8");
			}
			else
			{
				throw exception("ReadStringText : unsupport encode : utf32");
			}

			// split \r\n
			{
				int count = (int)buf.size() / 2;
				wstring s = L"";
				for (auto i = 0; i < count; i++)
				{
					char c = (wchar_t)(buf[i * 2 + 0] | (buf[i * 2 + 1] << 8));
					if (c == '\r') continue;    // ignore
					if (c == '\n')
					{
						sl.emplace_back(s);
						s = L"";
						continue;
					}
					s += c;
				}
				if (s != L"") sl.emplace_back(s);
			}
		}
		// file io
		static MemoryStream* loadFromFile(const wstring &filename) {
			ifstream fs(filename, ios::binary);
			if (!fs.is_open()) {
				return nullptr;
			}
			fs.seekg(0, ios::end);
			auto size = fs.tellg();
			fs.seekg(0, ios::beg);

			if (size > 1024 * 1024 * 128) {
				fs.close();
				return nullptr;
			}

			auto buf = (uint8_t*)malloc((int)size);
			fs.read((char*)&buf[0], size);
			fs.close();

			auto ms = new MemoryStream(buf, (int)size);
			return ms;
		}
	private:

	};
	class UnicodeConverter {
	public:
		static string utf16to8(const wstring &s16) {
			// convert utf32
			vector<int32_t> s32;
			int s32len = 0;
			{
				auto l = (int)s16.size();
				s32.resize(l);

				auto i = 0;
				while (i < l) {
					wchar_t c1, c2;
					c1 = 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;
							break;
						}
					}
					else {
						s32[s32len] = c1;
						i += 1;
						s32len++;
					}
				}
			}
			// convert utf8
			string s8;
			for (auto i = 0; i < s32len; i++) {
				int32_t c32 = s32[i];

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

				if (c32 < 128) {
					s8 += (char)c32;
				}
				else if (c32 < 2048) {
					s8 += char(0xC0 | (c32 >> 6));
					s8 += char(0x80 | (c32 & 0x3f));
				}
				else if (c32 < 65536) {
					s8 += char(0xE0 | (c32 >> 12));
					s8 += char(0x80 | ((c32 >> 6) & 0x3F));
					s8 += char(0x80 | (c32 & 0x3F));
				}
				else {
					s8 += char(0xF0 | (c32 >> 18));
					s8 += char(0x80 | ((c32 >> 12) & 0x3F));
					s8 += char(0x80 | ((c32 >> 6) & 0x3F));
					s8 += char(0x80 | (c32 & 0x3F));
				}
			}

			return s8;
		}
		static wstring utf8to16(const string &s8) {
			// convert utf32
			vector<int32_t> s32;
			int s32len = 0;
			{
				auto l = (int)s8.size();
				s32.resize(l);

				auto i = 0;
				while (i < l) {
					uint8_t c1 = s8[i];

					auto 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] = int32_t(s8[i+0]);
						s32len++;
						i += 1;
						break;
					case 2:
						if ((c1 & 0x1e) == 0) break;

						s32[s32len] = (int32_t(s8[i + 0] & 0x1f) << 6) | int32_t(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] = (int32_t(s8[i + 0] & 0x0f) << 12) | (int32_t(s8[i + 1] & 0x3f) << 6) | (int32_t(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] = (int32_t(s8[i + 0] & 0x07) << 18) | (int32_t(s8[i + 1] & 0x3f) << 12) | (int32_t(s8[i + 2] & 0x3f) << 6) | (int32_t(s8[i + 3] & 0x3f));
						s32len++;
						i += 4;
						break;
					}
				}
			}
			// convert utf16
			wstring s16;
			for (auto i = 0; i < s32len; i++) {
				int32_t c32 = s32[i];

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

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

			return s16;
		}
	};
}

#endif