#pragma once

#include <psemek/io/stream.hpp>

#include <filesystem>
#include <cstdio>
#include <memory>

namespace psemek::io
{

	namespace detail
	{

#ifdef __EMSCRIPTEN__
		struct buffer_hook
		{
			static constexpr std::size_t buffer_size = 1 << 20; // 1 MB
			std::unique_ptr<char[]> buffer{new char[buffer_size]};

			void apply(FILE * file)
			{
				std::setvbuf(file, buffer.get(), _IOFBF, buffer_size);
			}
		};
#else
		struct buffer_hook
		{
			void apply(FILE *) {}
		};
#endif

	}

	struct file_istream
		: istream
	{
		file_istream() = default;

		file_istream(FILE * file);
		file_istream(std::filesystem::path const & path);

		file_istream(file_istream && s)
			: file_{s.file_}
		{
			s.file_ = nullptr;
		}

		file_istream & operator = (file_istream && s)
		{
			if (this != &s)
			{
				reset();
				file_ = s.file_;
				s.file_ = nullptr;
			}
			return *this;
		}

		void reset();

		std::size_t read(char * p, std::size_t size) override;

		bool finished() const override;

		~file_istream() override
		{
			reset();
		}

	private:
		FILE * file_ = nullptr;
		[[no_unique_address]] detail::buffer_hook buffer_hook_;
	};

	struct file_ostream
		: ostream
	{
		static constexpr unsigned append = 1;

		file_ostream() = default;

		file_ostream(FILE * file);
		file_ostream(std::filesystem::path const & path, unsigned flags = 0);

		file_ostream(file_ostream && s)
			: file_{s.file_}
		{
			s.file_ = nullptr;
		}

		file_ostream & operator = (file_ostream && s)
		{
			if (this != &s)
			{
				reset();
				file_ = s.file_;
				s.file_ = nullptr;
			}
			return *this;
		}

		void reset();

		std::size_t write(char const * p, std::size_t size) override;

		void flush() override;

		~file_ostream() override
		{
			reset();
		}

	private:
		FILE * file_ = nullptr;
		[[no_unique_address]] detail::buffer_hook buffer_hook_;
	};

}
