
#include <string.h>
#include <stdio.h>
#include <system_error>
#include <Spectre/System/File.h>

namespace sp {

File::File() :
m_handle	(NULL)
{
}

File::File(const std::string& filename, Access access, unsigned int mode) :
m_handle	(NULL)
{
	open(filename, access, mode);
}

File::~File()
{
	close();
}

bool File::open(const std::string& filename, Access access, unsigned int mode)
{
	std::string m;

	close();

	if (access == Access::READ) {
		m = "rb";
	} else if (access == Access::WRITE) {
		if (mode & TRUNCATE) {
			m = "wb";
		} else {
			// NOTE: this is append mode. but stdio does only
			// have "a" and "w". and "w" will truncate.
			// So we need to handle append with fseek() later.
			m = "ab";
		}
	}
	// Read + Write
	else if (mode & TRUNCATE) {
			m = "wb+";
	} else if (mode & APPEND) {
			m = "ab+";
	} else {
			m = "rb+";
	}

	// HACK. stdio does not have a mode for strictly create new files
	// independant of access (read/write). So we will fake it by open the
	// file in append mode and close it. creating the file if did not exist.
	if (mode & CREATE && (access == Access::WRITE || access == Access::READ_WRITE)) {
		m_handle = fopen(filename.c_str(), "a");
		if (!m_handle) {
			m_error.assign(errno, std::generic_category());
			return false;
		}
		fclose(m_handle);
	}

	m_handle = fopen(filename.c_str(), m.c_str());

	if (m_handle) {

		// Hack, if we opened the file in "a" mode, but did not
		// have the append flag. seek to the beginning.
		if (m == "ab" && mode & ~APPEND) {
			fseek(m_handle, 0, SEEK_SET);
		}
		return true;
	}

	m_error.assign(errno, std::generic_category());
	return false;
}

bool File::isOpen() const
{
	if (m_handle == NULL) {
		m_error = std::errc::bad_file_descriptor;
		return false;
	}
	return true;
}

void File::close()
{
	if (m_handle) {
		fclose(m_handle);
		m_handle = NULL;
	}
	m_error.clear();
}

std::string File::getErrorMessage() const
{
	return m_error.message();
}

size_t File::pos()
{
	if (isOpen()) {
		return ftell(m_handle);
	}
	return -1;
}

bool File::set(long int offset, bool from_end)
{
	if (isOpen()) {
		return fseek(m_handle, offset, from_end ? SEEK_END : SEEK_SET) == 0;
	}
	return false;
}

bool File::seek(long int offset)
{
	if (isOpen()) {
		return fseek(m_handle, offset, SEEK_CUR) == 0;
	}
	return false;
}

size_t File::size() const
{
	size_t s = 0;
	while(::getc(m_handle) != EOF) s++;
	::rewind(m_handle);
	return s;
}

size_t File::read(void *ptr, size_t size)
{
	if (isOpen()) {
		return fread(ptr, 1, size, m_handle);
	}
	return -1;
}

size_t File::readString(std::string &str)
{
	if (isOpen()) {
		for(;;) {
			int c = ::getc(m_handle);
			if (c == EOF || c == '\0') {
				break;
			}
			str.push_back(c);
		}
		return str.length();
	}
	return -1;
}

size_t File::write(const void *ptr, size_t size)
{
	if (isOpen()) {
		return fwrite(ptr, 1, size, m_handle);
	}
	return -1;
}

bool File::flush()
{
	if (isOpen()) {
		return fflush(m_handle) == 0;
	}
	return false;
}

} // namespace sp
