#ifndef DATA_H
#define DATA_H

#include <stdexcept>
#include <windows.h>

#include "pl.h"
#include "util.h"
#include "wcharptr.h"
#include "win.h"

/* Fetch data from the web. */
void FetchData();
void WaitFetchData(bool* bDone) noexcept;

/* The structs ending with A are written as-is to disk. As such, they
 * should be regarded as immutable. If the format needs to be changed
 * in the future, then new structs ending with B should be added. */

/* Note that unsigned chars are 1-byte-aligned, shorts and wchar_t are
 * 2-byte-aligned and ints are 4-byte-aligned (on x86 Windows). */

/* Basic episode data presented in episode list view. */
struct ElvDataA
{
	unsigned char version = 'a';
	unsigned char rating = 0;
	unsigned char bWatched = 0;
	unsigned char bTVOriginal = 0;
	wchar_t sRating[4] = {0};
	wchar_t siEp[6] = {0};
	wchar_t title[128] = {0};
};

/* Extra episode data presented in data list view. */
struct DlvDataA
{
	unsigned char version = 'a';
	unsigned char pad[3] = {0};
	wchar_t date[32] = {0};
	wchar_t source[48] = {0};
	wchar_t screenwriter[48] = {0};
	wchar_t hint[128] = {0};
	wchar_t wiki[192] = {0};
};

/* Configuration. */
struct CfgA
{
	unsigned char version = 'a';
	unsigned char bViewWatched = 1;
	unsigned char bViewTVOriginal = 1;
	signed char iSortCol = 1;
	unsigned short cEp = 4096;
	unsigned short iFocus = 0;
	unsigned short heightDlv = 0;
	wchar_t limitScreenwriter[64] = {0};
	wchar_t root[260] = {0};
	wchar_t glob[64] = {0};
	wchar_t url[192] = {0};
};

/* Variable template for obtaining the version of a given struct. */
template <typename T>
constexpr inline unsigned char Version = T().version;

template <typename T, typename = int>
constexpr inline bool HasVersion = false;

template <typename T>
constexpr inline bool HasVersion<T, decltype((void) T::version, 0)> = true;

/* FileView objects manage a memory-mapped file. The view buffer may
 * be treated as an array of a given type T. Note that reading and
 * writing a view can throw structured exceptions. We ignore these. */
template <typename T>
struct FileView
{
	static FileView Initialized(const wchar_t* filename, size_t c, size_t cInit = 0)
	{
		bool bExisting = GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES;
		FileView fv(filename, c);

		/* If file didn't exist, initialize it with defaults. */
		if (!bExisting) {
			T t;
			cInit = cInit? cInit: c;
			for (size_t i = 0; i < cInit; i++)
				memcpy(fv+i, &t, sizeof(T));
		}

		return fv;
	}

	FileView(const wchar_t* filename, size_t c) : c(c)
	{
		hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE,
		    0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (hf == INVALID_HANDLE_VALUE) {
			if (GetLastError() == ERROR_FILE_NOT_FOUND) {
				hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE,
				    0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
				if (hf == INVALID_HANDLE_VALUE)
					throw Win32Error();
			} else
				throw Win32Error();
		}

		LARGE_INTEGER cbMap;
		cbMap.QuadPart = c*sizeof(T);
		hm = CreateFileMapping(hf, nullptr, PAGE_READWRITE,
		    cbMap.HighPart, cbMap.LowPart, nullptr);
		if (!hm)
			throw Win32Error();

		view = reinterpret_cast<T*>(MapViewOfFile(hm, FILE_MAP_ALL_ACCESS, 0, 0, 0));
		if (!view)
			throw Win32Error();
	}

	~FileView()
	{
		FlushViewOfFile(view, 0);
		UnmapViewOfFile(view);
		CloseHandle(hm);
		CloseHandle(hf);
	}

	/* Access element by index, performing bounds check and, if
	 * applicable for T, version validation as well as
	 * initialization, if needed. */
	T& At(size_t i)
	{
		if (i >= c)
			throw std::out_of_range("index larger than buffer");

		T& t = view[i];

		if constexpr (HasVersion<T>) {
			if (t.version == 0) {
				T t_;
				memcpy(&t, &t_, sizeof(T));
			} else if (t.version != Version<T>)
				throw std::runtime_error("invalid struct version");
		}

		/* TODO: Use a custom exception type that informs the
		 * user of possible data corruption. */

		return t;
	}

	inline operator T*() noexcept
	{
		return view;
	}

	inline T* operator ->() noexcept
	{
		return view;
	}

	HANDLE hf;
	HANDLE hm;
	T* view;
	size_t c;
};

inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept
{
	WcharPtr title, wiki, date, source, hint;
	const int r = Pl("episode_data","fetch_episode_data",iEp,&title,&wiki,&date,&source,&hint);
	if (title) Wcscpy(e.title, title);
	if (wiki) Wcscpy(d.wiki, wiki);
	if (date) Wcscpy(d.date, date);
	if (source) Wcscpy(d.source, source);
	if (hint) Wcscpy(d.hint, hint);
	return r;
}

inline bool FromProlog(const int iEp, ElvDataA& e) noexcept
{
	if (WcharPtr title; Pl("episode_data","episode_title",iEp,&title))
		Wcscpy(e.title, title);
	else
		return false;

	int rating;
	if (Pl("episode_data","episode_rating",iEp,&rating)) {
		e.rating = rating;
		Swprintf(e.sRating, L"%d", e.rating);
	}

	if (Pl("episode_data","tv_original",iEp))
		e.bTVOriginal = true;

	if (Pl("track_episodes","watched",iEp))
		e.bWatched = true;

	Swprintf(e.siEp, L"%d", iEp);

	return true;
}

inline void FromProlog(const int iEp, DlvDataA& d) noexcept
{
	if (WcharPtr wiki; Pl("episode_data","episode_wiki",iEp,&wiki))
		Wcscpy(d.wiki, wiki);
	if (WcharPtr screenwriter; Pl("episode_data","episode_datum",iEp,"Screenwriter",&screenwriter))
		Wcscpy(d.screenwriter, screenwriter);
	if (WcharPtr date; Pl("episode_data","episode_datum",iEp,"Date",&date))
		Wcscpy(d.date, date);
	if (WcharPtr source; Pl("episode_data","episode_datum",iEp,"Source",&source))
		Wcscpy(d.source, source);
	if (WcharPtr hint; Pl("episode_data","episode_datum",iEp,"Hint",&hint))
		Wcscpy(d.hint, hint);
}

#endif