#include "stdafx.h"
#include "FileStream.h"
#include "Core/Str.h"
#include "Core/StrBuf.h"
#include "OS/IORequest.h"
#include "Core/SystemError.h"
#include "Url.h"

namespace storm {

#if defined(WINDOWS)

	static void openFileRaw(Str *name, bool input, os::Handle &out, Str *&error) {
		// TODO: Allow duplicating this handle so that we can clone duplicate it later?

		out = CreateFile(name->c_str(),
						input ? GENERIC_READ : GENERIC_WRITE,
						FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL,
						input ? OPEN_EXISTING : CREATE_ALWAYS,
						FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
						NULL);
		if (!out) {
			error = systemErrorMessage(name->engine(), GetLastError());
		}
	}

	static void copyFilePtr(os::Handle to, os::Handle from) {
		LARGE_INTEGER pos;
		pos.QuadPart = 0;
		SetFilePointerEx(from.v(), pos, &pos, FILE_CURRENT);
		SetFilePointerEx(to.v(), pos, NULL, FILE_BEGIN);
	}

	static os::Handle copyFile(os::Handle h, Str *name, bool input) {
		if (h) {
			os::Handle r;
			Str *error = null;
			openFileRaw(name, input, r, error);
			if (error)
				throw OpenError(error, name, input);
			copyFilePtr(r, h);
			return r;
		} else {
			return os::Handle();
		}
	}

#elif defined(POSIX)

	static void openFileRaw(Str *name, bool input, os::Handle &out, Str *&error) {
		int flags = O_CLOEXEC | O_NONBLOCK;
		if (input)
			flags |= O_RDONLY;
		else
			flags |= O_CREAT | O_TRUNC | O_WRONLY;

		int mode = 0666;

		// We specify the handle AT_FDCWD to get the regular 'open' semantics for relative paths.
		os::IORequest r(os::IORequest::OPENAT, os::Handle(AT_FDCWD), os::Thread::current());
		r.strParam(name->utf8_str());
		r.openParams(flags);
		r.modeParam(mode);

		out = r.submit();

		// Interestingly enough, we can open (but not read from) directories on Linux. Detect that.
		if (out) {
			struct stat info;
			// Note: This should be fairly cheap - we just opened the file after all. As such, don't
			// bother with async IO here.
			if (fstatat(out.v(), "", &info, AT_EMPTY_PATH) == 0 && S_ISDIR(info.st_mode)) {
				::close(out.v());
				out = os::Handle();
				r.result = -EACCES;
			}
		}

		if (!out) {
			error = systemErrorMessage(name->engine(), -r.result);
		}
	}

	static os::Handle copyFile(os::Handle h, Str *name, bool input) {
		UNUSED(name);
		UNUSED(input);

		return dup(h.v());
	}

#else
#error "Please implement file IO for your platform!"
#endif

	static os::Handle openFile(Str *name, bool input) {
		os::Handle handle;
		Str *error = null;
		openFileRaw(name, input, handle, error);

		if (error)
			throw new (name) OpenError(error, name, input);

		return handle;
	}

	FileIStream::FileIStream(Str *name) : HandleRIStream(openFile(name, true)), name(name) {}

	FileIStream::FileIStream(const FileIStream &o) : HandleRIStream(copyFile(o.handle, o.name, true)), name(o.name) {}

	void FileIStream::toS(StrBuf *to) const {
		*to << L"File input from " << name;
	}


	FileOStream::FileOStream(Str *name) : HandleOStream(openFile(name, false)), name(name) {}

	FileOStream::FileOStream(const FileOStream &o) : HandleOStream(copyFile(o.handle, o.name, false)), name(o.name) {}

	void FileOStream::toS(StrBuf *to) const {
		*to << L"File output to " << name;
	}

}
