#include <psemek/io/file_stream.hpp>
#include <psemek/util/system_error.hpp>
#include <psemek/util/enum.hpp>

#include <cstring>

namespace psemek::io
{

	namespace
	{

		void throw_fopen [[noreturn]] (std::filesystem::path const & path)
		{
			throw util::system_error(std::error_code{errno, std::system_category()}, "Failed to open " + path.string());
		}

#ifdef _WIN32
		wchar_t const * fopen_read_mode()
		{
			return L"rb";
		}

		wchar_t const * fopen_write_mode(unsigned flags)
		{
			switch (flags)
			{
			case 0: return L"wb";
			case file_ostream::append: return L"ab";
			}

			throw util::exception("Unknown file_ostream open flags");
		}

		FILE * safe_fopen(std::filesystem::path const & path, wchar_t const * mode)
		{
			auto f = _wfopen(path.c_str(), mode);
			if (!f) throw_fopen(path);
			return f;
		}
#else
		char const * fopen_read_mode()
		{
			return "rb";
		}

		char const * fopen_write_mode(unsigned flags)
		{
			switch (flags)
			{
			case 0: return "wb";
			case file_ostream::append: return "ab";
			}

			throw util::exception("Unknown file_ostream open flags");
		}

		FILE * safe_fopen(std::filesystem::path const & path, char const * mode)
		{
			auto f = std::fopen(path.c_str(), mode);
			if (!f) throw_fopen(path);
			return f;
		}
#endif

	}

	file_istream::file_istream(std::filesystem::path const & path)
		: file_{safe_fopen(path, fopen_read_mode())}
	{}

	void file_istream::reset()
	{
		if (file_)
			std::fclose(file_);
		file_ = nullptr;
	}

	std::size_t file_istream::read(char * p, std::size_t size)
	{
		if (!file_) throw null_istream{};
		return std::fread(p, 1, size, file_);
	}

	bool file_istream::finished() const
	{
		if (!file_) throw null_istream{};
		return std::feof(file_) != 0;
	}

	file_ostream::file_ostream(std::filesystem::path const & path, unsigned flags)
		: file_{safe_fopen(path, fopen_write_mode(flags))}
	{}

	void file_ostream::reset()
	{
		if (file_)
			std::fclose(file_);
		file_ = nullptr;
	}

	std::size_t file_ostream::write(char const * p, std::size_t size)
	{
		if (!file_) throw null_ostream{};
		return std::fwrite(p, 1, size, file_);
	}

	void file_ostream::flush()
	{
		if (!file_) throw null_ostream{};
		std::fflush(file_);
	}

}
