/*
	Copyright 2014 Michele "King_DuckZ" Santullo

	This file is part of CloonelJump.

	CloonelJump 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.

	CloonelJump 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 CloonelJump.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "physicsfswrapper.hpp"
#include "physfs.h"
#include <sstream>
#include <stdexcept>
#include <ciso646>
#include <cassert>

namespace cloonel {
	namespace {
		///---------------------------------------------------------------------
		///---------------------------------------------------------------------
		PHYSFS_File* OpenPhysFSFile (const char* parPath, PhysicsFSFile::OpenMode parMode) {
			switch (parMode) {
			case PhysicsFSFile::OpenMode_Read:
				return PHYSFS_openRead(parPath);
			case PhysicsFSFile::OpenMode_Write:
				return PHYSFS_openWrite(parPath);
			case PhysicsFSFile::OpenMode_Append:
				return PHYSFS_openAppend(parPath);
			default:
				return nullptr;
			}
		}
	} //unnamed namespace

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	PhysicsFSWrapper::PhysicsFSWrapper (const char* parBasePath) {
		if (not PHYSFS_init(parBasePath)) {
			std::ostringstream oss;
			oss << "Error during PhysicsFS initialization: " << PHYSFS_getLastError();
			throw std::runtime_error(oss.str());
		}
	}

	///-------------------------------------------------------------------------
	///Note that the deinit function could fail, but since we're in a dtor we
	///are not checking in its return value as we can't throw anyways.
	///-------------------------------------------------------------------------
	PhysicsFSWrapper::~PhysicsFSWrapper() noexcept {
		const bool succeeded = static_cast<bool>(PHYSFS_deinit());
		assert(succeeded);
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	void PhysicsFSWrapper::Append (const char* parRelativePath, const char* parMountPoint) {
		assert(parRelativePath);
		assert(parMountPoint);
		if (not PHYSFS_mount(parRelativePath, parMountPoint, 1)) {
			std::ostringstream oss;
			oss << "Error while adding \"" << parRelativePath <<
				"\" to PhysicsFS' mount point \"" << parMountPoint << "\": ";
			oss << PHYSFS_getLastError();
			throw std::runtime_error(oss.str());
		}
	}


	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	PhysicsFSFile::PhysicsFSFile (const char* parPath, OpenMode parMode, const char* parDescCategory, bool parBuffered) :
		m_handle(OpenPhysFSFile(parPath, parMode))
	{
		if (not m_handle) {
			std::ostringstream oss;
			oss << "Error opening " << parDescCategory << " file: \"" <<
			oss << parPath << "\": ";
			oss << PHYSFS_getLastError();
			throw std::runtime_error(oss.str());
		}

		if (parBuffered) {
			//TODO: try to guess a buffer size
			PHYSFS_setBuffer(static_cast<PHYSFS_File*>(m_handle), 1024);
		}
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	PhysicsFSFile::~PhysicsFSFile() noexcept {
		if (IsOpen()) {
			PHYSFS_close(static_cast<PHYSFS_File*>(m_handle));
		}
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	bool PhysicsFSFile::IsOpen() const noexcept {
		return (m_handle != nullptr);
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	int64_t PhysicsFSFile::Read (void* parBuff, uint32_t parSize, uint32_t parCount) {
		return PHYSFS_read(static_cast<PHYSFS_File*>(m_handle), parBuff, parSize, parCount);
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	int64_t PhysicsFSFile::Write (void* parBuff, uint32_t parSize, uint32_t parCount) {
		return PHYSFS_write(static_cast<PHYSFS_File*>(m_handle), parBuff, parSize, parCount);
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	bool PhysicsFSFile::IsEof() const noexcept {
		return PHYSFS_eof(static_cast<PHYSFS_File*>(const_cast<void*>(m_handle)));
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	int64_t PhysicsFSFile::Tell() const {
		return PHYSFS_tell(static_cast<PHYSFS_File*>(const_cast<void*>(m_handle)));
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	int PhysicsFSFile::Seek (uint64_t parPos) {
		return PHYSFS_seek(static_cast<PHYSFS_File*>(m_handle), parPos);
	}

	///-------------------------------------------------------------------------
	///-------------------------------------------------------------------------
	void PhysicsFSFile::Flush() noexcept {
		PHYSFS_flush(static_cast<PHYSFS_File*>(m_handle));
	}
} //namespace cloonel
