enum {
	STRM_ERROR   =  0x20,
	STRM_READ    =  0x10,
	STRM_WRITE   =  0x08,
	STRM_SEEK    =  0x04,
	STRM_LOADING =  0x02,
	STRM_THROW   =  0x01,

	ERROR_NOT_ENOUGH_SPACE = -1,
	ERROR_LOADING_FAILED   = -2
};

struct StreamError {};
struct LoadingError : StreamError {};

enum EOLenum { EOL };

class Huge;

class Stream {
protected:
	int64  pos;
	byte  *buffer;
	byte  *ptr;
	byte  *rdlim;
	byte  *wrlim;

	unsigned style:6;
	unsigned errorcode:16;
	
	int      version = 0;

	enum {
		BEGINOFLINE = 0x02,
	};

	virtual   void  _Put(int w);
	virtual   int   _Term();
	virtual   int   _Get();
	virtual   void  _Put(const void *data, dword size);
	virtual   dword _Get(void *data, dword size);

private:
	int       _Get8();
	int       _Get16();
	int       _Get32();
	int64     _Get64();

public:
	virtual   void  Seek(int64 pos);
	virtual   int64 GetSize() const;
	virtual   void  SetSize(int64 size);
	virtual   void  Flush();
	virtual   void  Close();
	virtual   bool  IsOpen() const = 0;

	Stream();
	virtual  ~Stream();

	word      GetStyle() const       { return style; }
	
	void      SetVersion(int ver)    { version = ver; }
	int       GetVersion() const     { return version; }

	bool      IsError() const        { return style & STRM_ERROR; }
	bool      IsOK() const           { return !IsError(); }
	void      SetError(int c = 0)    { style |= STRM_ERROR; errorcode = c; }
#ifdef PLATFORM_WIN32
	void      SetLastError()         { SetError(GetLastError()); }
#endif
#ifdef PLATFORM_POSIX
	void      SetLastError()         { SetError(errno); }
#endif
	int       GetError() const       { return errorcode; }
	String    GetErrorText() const;
	void      ClearError()           { style = style & ~STRM_ERROR; errorcode = 0; }

	int64     GetPos() const         { return dword(ptr - buffer) + pos; }
	int64     GetLeft() const        { return GetSize() - GetPos(); }
	void      SeekEnd(int64 rel = 0) { Seek(GetSize() + rel); }
	void      SeekCur(int64 rel)     { Seek(GetPos() + rel); }

	bool      IsEof()                { return ptr >= rdlim && _Term() < 0; }

	void      Put(int c)             { if(ptr < wrlim) *ptr++ = c; else _Put(c); }
	int       Term()                 { return ptr < rdlim ? *ptr : _Term(); }
	int       Peek()                 { return Term(); }
	int       Get()                  { return ptr < rdlim ? *ptr++ : _Get(); }

	const byte *PeekPtr(int size = 1){ ASSERT(size > 0); return ptr + size <= rdlim ? ptr : NULL; }
	const byte *GetPtr(int size = 1) { ASSERT(size > 0); if(ptr + size <= rdlim) { byte *p = ptr; ptr += size; return p; }; return NULL; }
	byte       *PutPtr(int size = 1) { ASSERT(size > 0); if(ptr + size <= wrlim) { byte *p = ptr; ptr += size; return p; }; return NULL; }
	const byte *GetSzPtr(int& size)  { Term(); size = int(rdlim - ptr); byte *p = ptr; ptr += size; return p; }

	void      Put(const void *data, int size)  { ASSERT(size >= 0); if(size) { if(ptr && ptr + size <= wrlim) { memcpy8(ptr, data, size); ptr += size; } else _Put(data, size); } }
	int       Get(void *data, int size)        { ASSERT(size >= 0); if(ptr && ptr + size <= rdlim) { memcpy8(data, ptr, size); ptr += size; return size; } return _Get(data, size); }

	void      Put(const String& s)   { Put((const char *) s, s.GetLength()); }
	String    Get(int size);
	String    GetAll(int size);
	
	int       Skip(int size);
	
	void      LoadThrowing()         { style |= STRM_THROW; }
	void      LoadError();

	bool      GetAll(void *data, int size);

	void      Put64(const void *data, int64 size);
	int64     Get64(void *data, int64 size);
	bool      GetAll64(void *data, int64 size);

	size_t    Get(Huge& h, size_t size);
	bool      GetAll(Huge& h, size_t size);

	int       Get8()                 { return ptr < rdlim ? *ptr++ : _Get8(); }
	int       Get16()                { if(rdlim - ptr <= 1) return _Get16(); int q = Peek16(ptr); ptr += 2; return q; }
	int       Get32()                { if(rdlim - ptr <= 3) return _Get32(); int q = Peek32(ptr); ptr += 4; return q; }
	int64     Get64()                { if(rdlim - ptr <= 7) return _Get64(); int64 q = Peek64(ptr); ptr += 8; return q; }

	int       GetUtf8();

	String    GetLine();

	void      Put16(word q)          { if(wrlim - ptr > 1) { Poke16(ptr, q); ptr += 2; } else Put(&q, 2); }
	void      Put32(dword q)         { if(wrlim - ptr > 3) { Poke32(ptr, q); ptr += 4; } else Put(&q, 4); }
	void      Put64(int64 q)         { if(wrlim - ptr > 7) { Poke64(ptr, q); ptr += 8; } else Put(&q, 8); }

#ifdef CPU_LE
	int       Get16le()              { return Get16(); }
	int       Get32le()              { return Get32(); }
	int64     Get64le()              { return Get64(); }
	int       Get16be()              { return SwapEndian16(Get16()); }
	int       Get32be()              { return SwapEndian32(Get32()); }
	int64     Get64be()              { return SwapEndian64(Get64()); }

	void      Put16le(word q)        { Put16(q); }
	void      Put32le(dword q)       { Put32(q); }
	void      Put64le(int64 q)       { Put64(q); }
	void      Put16be(word q)        { Put16(SwapEndian16(q)); }
	void      Put32be(dword q)       { Put32(SwapEndian32(q)); }
	void      Put64be(int64 q)       { Put64(SwapEndian64(q)); }
#else
	int       Get16le()              { return SwapEndian16(Get16()); }
	int       Get32le()              { return SwapEndian32(Get32()); }
	int64     Get64le()              { return SwapEndian64(Get64()); }
	int       Get16be()              { return Get16(); }
	int       Get32be()              { return Get32(); }
	int64     Get64be()              { return Get64(); }

	void      Put16le(word q)        { Put16(SwapEndian16(q)); }
	void      Put32le(dword q)       { Put32(SwapEndian32(q)); }
	void      Put64le(int64 q)       { Put64(SwapEndian64(q)); }
	void      Put16be(word q)        { Put16(q); }
	void      Put32be(dword q)       { Put32(q); }
	void      Put64be(int64 q)       { Put64(q); }
#endif

	void      PutUtf8(int c);

	void      Put(const char *s);
	void      Put(int c, int count);
	void      Put0(int count)        { Put(0, count); }

	void      PutCrLf()              { Put16(MAKEWORD('\r', '\n')); }
#ifdef PLATFORM_WIN32
	void      PutEol()               { PutCrLf(); }
#else
	void      PutEol()               { Put('\n'); }
#endif

	Stream&   operator<<(EOLenum)    { PutEol(); return *this; }

	void      PutLine(const char *s);
	void      PutLine(const String& s);

	void      Put(Stream& s, int64 size = INT64_MAX, dword click = 4096);

//  Stream as serialization streamer
	void      SetLoading()                 { ASSERT(style & STRM_READ); style |= STRM_LOADING; }
	void      SetStoring()                 { ASSERT(style & STRM_WRITE); style &= ~STRM_LOADING; }
	bool      IsLoading() const            { return style & STRM_LOADING; }
	bool      IsStoring() const            { return !IsLoading(); }

	void      SerializeRaw(byte *data, int64 count);
	void      SerializeRaw(word *data, int64 count);
	void      SerializeRaw(int16 *data, int64 count);
	void      SerializeRaw(dword *data, int64 count);
	void      SerializeRaw(int *data, int64 count);
	void      SerializeRaw(uint64 *data, int64 count);
	void      SerializeRaw(float *data, int64 count);
	void      SerializeRaw(double *data, int64 count);

	String    GetAllRLE(int size);
	void      SerializeRLE(byte *data, int count);
	
	Stream&   SerializeRaw(byte *data)     { if(IsLoading()) *data = Get(); else Put(*data); return *this; }
	Stream&   SerializeRaw(word *data)     { if(IsLoading()) *data = Get16le(); else Put16le(*data); return *this; }
	Stream&   SerializeRaw(dword *data)    { if(IsLoading()) *data = Get32le(); else Put32le(*data); return *this; }
	Stream&   SerializeRaw(uint64 *data)   { if(IsLoading()) *data = Get64le(); else Put64le(*data); return *this; }

	Stream&   operator%(bool& d)           { byte b; if(IsStoring()) b = d; SerializeRaw(&b); d = !!b; return *this; }
	Stream&   operator%(char& d)           { return SerializeRaw((byte *)&d); }
	Stream&   operator%(signed char& d)    { return SerializeRaw((byte *)&d); }
	Stream&   operator%(unsigned char& d)  { return SerializeRaw((byte *)&d); }
	Stream&   operator%(short& d)          { return SerializeRaw((word *)&d); }
	Stream&   operator%(unsigned short& d) { return SerializeRaw((word *)&d); }
	Stream&   operator%(int& d)            { return SerializeRaw((dword *)&d); }
	Stream&   operator%(unsigned int& d)   { return SerializeRaw((dword *)&d); }
	Stream&   operator%(long& d)           { return SerializeRaw((dword *)&d); }
	Stream&   operator%(unsigned long& d)  { return SerializeRaw((dword *)&d); }
	Stream&   operator%(float& d)          { return SerializeRaw((dword *)&d); }
	Stream&   operator%(double& d)         { return SerializeRaw((uint64 *)&d); }
	Stream&   operator%(int64& d)          { return SerializeRaw((uint64 *)&d); }
	Stream&   operator%(uint64& d)         { return SerializeRaw((uint64 *)&d); }

	Stream&   operator%(String& s);
	Stream&   operator/(String& s);

	Stream&   operator%(WString& s);
	Stream&   operator/(WString& s);

	void      Pack(dword& i);
	Stream&   operator/(int& i);
	Stream&   operator/(unsigned int& i);
	Stream&   operator/(long& i);
	Stream&   operator/(unsigned long& i);
	
	Stream&   Serialize64(long& d)           { uint64 m = d; *this % m; d = (long) m; return *this; }
	Stream&   Serialize64(unsigned long& d)  { uint64 m = d; *this % m; d = (unsigned long) m; return *this; }

	void      Magic(dword magic = 0x7d674d7b);

	void      Pack(bool& a, bool& b, bool& c, bool& d, bool& e, bool& f, bool& g, bool& h);
	void      Pack(bool& a, bool& b, bool& c, bool& d, bool& e, bool& f, bool& g);
	void      Pack(bool& a, bool& b, bool& c, bool& d, bool& e, bool& f);
	void      Pack(bool& a, bool& b, bool& c, bool& d, bool& e);
	void      Pack(bool& a, bool& b, bool& c, bool& d);
	void      Pack(bool& a, bool& b, bool& c);
	void      Pack(bool& a, bool& b);

#ifdef DEPRECATED
	int       GetW()                 { return Get16(); }
	int       GetL()                 { return Get32(); }
	int       GetIW()                { return Get16le(); }
	int       GetIL()                { return Get32le(); }
	int       GetMW()                { return Get16be(); }
	int       GetML()                { return Get32be(); }
	void      PutW(int c)            { Put16(c); }
	void      PutL(int c)            { Put32(c); }
	void      PutIW(int c)           { Put16le(c); }
	void      PutIL(int c)           { Put32le(c); }
	void      PutMW(int c)           { Put16be(c); }
	void      PutML(int c)           { Put32be(c); }
	void      PutW(const char16 *s, int count)       { Put(s, count * 2); }
	dword     GetW(char16 *s, int count)             { return Get(s, count * 2) / 2; }
	bool      GetAllW(char16 *s, int count)          { return GetAll(s, count * 2); }
#endif
private: // No copy
	Stream(const Stream& s);
	void operator=(const Stream& s);
};

class StringStream : public Stream {
protected:
	virtual  void  _Put(int w);
	virtual  int   _Term();
	virtual  int   _Get();
	virtual  void  _Put(const void *data, dword size);
	virtual  dword _Get(void *data, dword size);

public:
	virtual  void  Seek(int64 pos);
	virtual  int64 GetSize() const;
	virtual  void  SetSize(int64 size);
	virtual  bool  IsOpen() const;

protected:
	bool           writemode;
	String         data;
	StringBuffer   wdata;
	dword          size;
	int            limit = INT_MAX;

	void           InitReadMode();
	void           SetWriteBuffer();
	void           SetReadMode();
	void           SetWriteMode();

public:
	void        Open(const String& data);
	void        Create();
	void        Reserve(int n);

	String      GetResult();
	operator    String()                              { return GetResult(); }
	
	void        Limit(int sz)                         { limit = sz; }
	
	struct LimitExc : public StreamError {};

	StringStream()                           { Create(); }
	StringStream(const String& data)         { Open(data); }
};

class MemStream : public Stream {
protected:
	virtual   void  _Put(const void *data, dword size);
	virtual   dword _Get(void *data, dword size);

public:
	virtual   void  Seek(int64 pos);
	virtual   int64 GetSize() const;
	virtual   bool  IsOpen() const;

public:
	void Create(void *data, int64 size);

	MemStream();
	MemStream(void *data, int64 size);
};

class MemReadStream : public MemStream {
public:
	void Create(const void *data, int64 size);

	MemReadStream(const void *data, int64 size);
	MemReadStream();
};

class BlockStream : public Stream {
protected:
	virtual  void  _Put(int w);
	virtual  int   _Term();
	virtual  int   _Get();
	virtual  void  _Put(const void *data, dword size);
	virtual  dword _Get(void *data, dword size);

public:
	virtual  void  Seek(int64 pos);
	virtual  int64 GetSize() const;
	virtual  void  SetSize(int64 size);
	virtual  void  Flush();

private:
	int           pagesize;
	int64         pagemask;
	int64         pagepos;
	bool          pagedirty;

	int64         streamsize;

	void          SetPos(int64 p);
	void          SyncSize();
	bool          SyncPage();
	bool          SyncPos();
	void          ReadData(void *data, int64 at, int size);

protected:
	virtual  dword Read(int64 at, void *ptr, dword size);
	virtual  void  Write(int64 at, const void *data, dword size);
	virtual  void  SetStreamSize(int64 size);

public:
	enum {
		READ, CREATE, APPEND, READWRITE,
		MODEMASK = 0x3,

		NOWRITESHARE = 0x10,
		SHAREMASK = 0x70,
#ifdef DEPRECATED
		DELETESHARE = 0x20,
		NOREADSHARE = 0x40,
#endif
	};

	dword     GetBufferSize() const           { return pagesize; }
	void      SetBufferSize(dword newsize);
	int64     GetStreamSize() const           { return streamsize; }

	BlockStream();
	virtual ~BlockStream();

protected:
	void     Reset();
	void     OpenInit(dword mode, int64 file_size);
};

class FileStream : public BlockStream {
protected:
	virtual  void  SetStreamSize(int64 size);
	virtual  dword Read(int64 at, void *ptr, dword size);
	virtual  void  Write(int64 at, const void *data, dword size);

public:
	virtual  void  Close();
	virtual  bool  IsOpen() const;

protected:
#ifdef PLATFORM_WIN32
	HANDLE    handle;
	static bool OpenHandle(const char *name, dword mode, HANDLE& handle, int64& fsz);
#else
	int       handle;
	static bool OpenHandle(const char *name, dword mode, int& handle, int64& fsz, mode_t tmode);
#endif

	void      SetPos(int64 pos);
	void      Init(int64 size);
	
	friend class FileMapping;

public:
	operator  bool() const                 { return IsOpen(); }
	FileTime  GetTime() const;

#ifdef PLATFORM_WIN32
	void      SetTime(const FileTime& tm);
	bool      Open(const char *filename, dword mode);

	FileStream(const char *filename, dword mode);
#endif

#ifdef PLATFORM_POSIX
	bool      Open(const char *filename, dword mode, mode_t acm = 0644);
	FileStream(const char *filename, dword mode, mode_t acm = 0644);
	FileStream(int std_handle);
#endif

	FileStream();
	~FileStream();

#ifdef PLATFORM_WIN32
	HANDLE    GetHandle() const            { return handle; }
#endif
#ifdef PLATFORM_POSIX
	int       GetHandle() const            { return handle; }
#endif
};

class FileOut : public FileStream {
public:
#ifdef PLATFORM_POSIX
	bool Open(const char *fn, mode_t acm = 0644);
#endif
#ifdef PLATFORM_WIN32
	bool Open(const char *fn);
#endif

	FileOut(const char *fn)                { Open(fn); }
	FileOut()                              {}
};

class FileAppend : public FileStream {
public:
	bool Open(const char *fn)         { return FileStream::Open(fn, FileStream::APPEND|FileStream::NOWRITESHARE); }

	FileAppend(const char *fn)        { Open(fn); }
	FileAppend()                      {}
};

class FileIn : public FileStream {
public:
	bool Open(const char *fn)         { return FileStream::Open(fn, FileStream::READ); }

	FileIn(const char *fn)            { Open(fn); }
	FileIn()                          {}
};

class SizeStream : public Stream {
protected:
	virtual void  _Put(int w);
	virtual void  _Put(const void *data, dword size);

public:
	virtual int64 GetSize() const;
	virtual bool  IsOpen() const;

protected:
	byte    h[256];

public:
	operator int64() const            { return GetSize(); }

	void     Open()                   { ptr = buffer; ClearError(); }

	SizeStream();
};

class CompareStream : public Stream {
protected:
	virtual  void  _Put(int w);
	virtual  void  _Put(const void *data, dword size);

public:
	virtual  void  Seek(int64 pos);
	virtual  int64 GetSize() const;
	virtual  void  SetSize(int64 size);
	virtual  void  Close();
	virtual  bool  IsOpen() const;
	virtual  void  Flush();

private:
	Stream  *stream;
	bool     equal;
	int64    size;
	byte     h[1024];

	void     Compare(int64 pos, const void *data, int size);

public:
	void     Open(Stream& aStream);

	bool     IsEqual()                         { Flush(); return equal; }
	operator bool()                            { Flush(); return equal; }

	CompareStream();
	CompareStream(Stream& aStream);
};

class OutStream : public Stream {
	byte *h;

protected:
	virtual  void  _Put(int w);
	virtual  void  _Put(const void *data, dword size);
	virtual  bool  IsOpen() const;

	virtual  void  Out(const void *data, dword size) = 0;

public:
	virtual  void  Close();
	
	void     Flush();
	
	OutStream();
	~OutStream();
};

class TeeStream : public OutStream {
protected:
	virtual  void  Out(const void *data, dword size);

private:
	Stream& a;
	Stream& b;

public:
	TeeStream(Stream& a, Stream& b) : a(a), b(b) {}
	~TeeStream()                                 { Close(); }
};

String LoadStream(Stream& in);
bool   SaveStream(Stream& out, const String& data);

int64 CopyStream(Stream& dest, Stream& src, int64 count = INT64_MAX);

void    CoutUTF8();
Stream& Cout();
Stream& Cerr();
String  ReadStdIn();
String  ReadSecret();
void    EnableEcho(bool b = true);
void    DisableEcho();

Stream& NilStream();

String LoadFile(const char *filename);
bool   SaveFile(const char *filename, const String& data);

template <class T>
inline Stream& operator%(Stream& s, T& x)
{
	x.Serialize(s);
	return s;
}

inline Stream& operator<<(Stream& s, const char *x)
{
	s.Put(x);
	return s;
}

inline Stream& operator<<(Stream& s, char *x)
{
	s.Put(x);
	return s;
}

inline Stream& operator<<(Stream& s, const String &x)
{
	s.Put(x);
	return s;
}

inline Stream& operator<<(Stream& s, char x)
{
	s.Put((int) x);
	return s;
}

inline Stream& operator<<(Stream& s, const void *x)
{
	s << FormatPtr(x);
	return s;
}

inline Stream& operator<<(Stream& s, void *x)
{
	s << FormatPtr(x);
	return s;
}

template <class T>
inline Stream& operator<<(Stream& s, const T& x) {
	s << AsString(x);
	return s;
}

// ------

Stream& Pack16(Stream& s, int& i);
Stream& Pack16(Stream& s, int& i1, int& i2);
Stream& Pack16(Stream& s, int& i1, int& i2, int& i3);
Stream& Pack16(Stream& s, int& i1, int& i2, int& i3, int& i4);
Stream& Pack16(Stream& s, int& i1, int& i2, int& i3, int& i4, int& i5);

int     StreamHeading(Stream& stream, int ver, int minver, int maxver, const char* tag);
