/*
	This file is part of Fennix Kernel.

	Fennix Kernel is free software: you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Fennix Kernel is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <algorithm>
#include <string>
#include <locale>
#include <ios>

namespace std
{
	template <class CharT, class Traits = std::char_traits<CharT>>
	class basic_streambuf
	{
	public:
		typedef CharT char_type;
		typedef Traits::char_type traits_type;
		typedef Traits::int_type int_type;
		typedef Traits::pos_type pos_type;
		typedef Traits::off_type off_type;

	private:
		char_type *_eback;
		char_type *_gptr;
		char_type *_egptr;
		char_type *_pbase;
		char_type *_pptr;
		char_type *_epptr;

	public:
		virtual ~basic_streambuf();

#pragma region Locales

		std::locale pubimbue(const std::locale &loc)
		{
			return imbue(loc);
		}

		std::locale getloc() const
		{
			return imbue(std::locale());
		}

#pragma endregion Locales

#pragma region Positioning

		basic_streambuf<CharT, Traits> *pubsetbuf(char_type *s, std::streamsize n)
		{
			return setbuf(s, n);
		}

		pos_type pubseekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = ios_base::in | ios_base::out)
		{
			return seekoff(off, dir, which);
		}

		pos_type pubseekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
		{
			return seekpos(pos, which);
		}

		int pubsync()
		{
			return sync();
		}

#pragma endregion Positioning

#pragma region Get Area

		std::streamsize in_avail()
		{
			return egptr() - gptr();
		}

		int_type snextc()
		{
			return gptr() == egptr() ? Traits::eof() : Traits::to_int_type(*gptr());
		}

		int_type sgetc()
		{
			return gptr() == egptr() ? underflow() : Traits::to_int_type(*gptr());
		}

		std::streamsize sgetn(char_type *s, std::streamsize count)
		{
			std::streamsize copied = 0;
			while (copied < count)
			{
				if (gptr() == egptr())
				{
					if (underflow() == Traits::eof())
					{
						break;
					}
				}
				*s++ = *gptr();
				++copied;
			}
			return copied;
		}

#pragma endregion Get Area

#pragma region Put Area

		int_type sputc(char_type ch)
		{
			if (pptr() == epptr())
			{
				if (overflow(Traits::to_int_type(ch)) == Traits::eof())
				{
					return Traits::eof();
				}
			}
			else
			{
				*pptr() = ch;
				pbump(1);
			}
			return Traits::to_int_type(ch);
		}

		std::streamsize sputn(const char_type *s, std::streamsize count)
		{
			return xsputn(s, count);
		}

#pragma endregion Put Area

#pragma region Putback

		int_type sputbackc(char_type c)
		{
			if (gptr() == eback() || !Traits::eq(c, gptr()[-1]))
			{
				return Traits::eof();
			}
			gbump(-1);
			return Traits::to_int_type(c);
		}

		int_type sungetc()
		{
			if (gptr() == eback())
			{
				return Traits::eof();
			}
			gbump(-1);
			return Traits::to_int_type(*gptr());
		}

#pragma endregion Putback

	protected:
		basic_streambuf()
			: _eback(nullptr),
			  _gptr(nullptr),
			  _egptr(nullptr),
			  _pbase(nullptr),
			  _pptr(nullptr),
			  _epptr(nullptr)
		{
		}

		basic_streambuf(const basic_streambuf &rhs)
			: _eback(rhs._eback),
			  _gptr(rhs._gptr),
			  _egptr(rhs._egptr),
			  _pbase(rhs._pbase),
			  _pptr(rhs._pptr),
			  _epptr(rhs._epptr)
		{
		}

		basic_streambuf &operator=(const basic_streambuf &other)
		{
			if (this == &other)
				return *this;

			_eback = other._eback;
			_gptr = other._gptr;
			_egptr = other._egptr;
			_pbase = other._pbase;
			_pptr = other._pptr;
			_epptr = other._epptr;
			return *this;
		}

		void swap(basic_streambuf &other)
		{
			std::swap(_eback, other._eback);
			std::swap(_gptr, other._gptr);
			std::swap(_egptr, other._egptr);
			std::swap(_pbase, other._pbase);
			std::swap(_pptr, other._pptr);
			std::swap(_epptr, other._epptr);
		}

#pragma region Locales

		virtual void imbue(const std::locale &loc) = 0;

#pragma endregion Locales

#pragma region Positioning

		virtual basic_streambuf<CharT, Traits> *setbuf(char_type *s, std::streamsize n) = 0;
		virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = ios_base::in | ios_base::out) = 0;
		virtual pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) = 0;
		virtual int sync() = 0;

#pragma endregion Positioning

#pragma region Get Area

		virtual std::streamsize showmanyc() = 0;
		virtual int_type underflow() = 0;
		virtual int_type uflow() = 0;
		virtual std::streamsize xsgetn(char_type *s, std::streamsize count) = 0;

		char_type *eback() const
		{
			return _eback;
		}

		char_type *gptr() const
		{
			return _gptr;
		}

		char_type *egptr() const
		{
			return _egptr;
		}

		void gbump(int count)
		{
			_gptr += count;
		}

		void setg(char_type *gbeg, char_type *gcurr, char_type *gend)
		{
			_eback = gbeg;
			_gptr = gcurr;
			_egptr = gend;
		}

#pragma endregion Get Area

#pragma region Put Area

		virtual std::streamsize xsputn(const char_type *s, std::streamsize count) = 0;
		virtual int_type overflow(int_type ch = Traits::eof()) = 0;

		char_type *pbase() const
		{
			return _pbase;
		}

		char_type *pptr() const
		{
			return _pptr;
		}

		char_type *epptr() const
		{
			return _epptr;
		}

		void pbump(int count)
		{
			_pptr += count;
		}

		void setp(char_type *pbeg, char_type *pend)
		{
			_pbase = pbeg;
			_pptr = pbeg;
			_epptr = pend;
		}

#pragma endregion Put Area

#pragma region Putback

		virtual int_type pbackfail(int_type c = Traits::eof()) = 0;

#pragma endregion Putback
	};

	typedef basic_streambuf<char> streambuf;
	typedef basic_streambuf<wchar_t> wstreambuf;
}
