// Copyright (C) 2003 Dolphin Project.

// This program 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, version 2.0.

// This program 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/

#ifndef _LINEAR_DISKCACHE
#define _LINEAR_DISKCACHE

#include "Common.h"
#include <fstream>
#include <functional>

// Update this to the current SVN revision every time you change shader generation code.
// We don't automatically get this from SVN_REV because that would mean regenerating the
// shader cache for every revision, graphics-related or not, which is simply annoying.
enum
{
	LINEAR_DISKCACHE_VER = 6964
};

// On disk format:
//header{
// u32 'DCAC';
// u32 version;  // svn_rev
// u16 sizeof(key_type);
// u16 sizeof(value_type);
//}

//key_value_pair{
// u32 value_size;
// key_type   key;
// value_type[value_size]   value;
//}

// Example reader:
//
//class LinearDiskCacheReader
//{
//public:
//	template <typename F>
//	void operator()(const K& key, u32 value_size, F get_data) const
//	{...}
//};

// Dead simple unsorted key-value store with append functionality.
// No random read functionality, all reading is done in OpenAndRead.
// Keys and values can contain any characters, including \0.
//
// Suitable for caching generated shader bytecode between executions.
// Not tuned for extreme performance but should be reasonably fast.
// Does not support keys or values larger than 2GB, which should be reasonable.
// Keys must have non-zero length; values can have zero length.

// K and V are some POD type
// K : the key type
// V : value array type
template <typename K, typename V>
class LinearDiskCache
{
public:
	// return number of read entries
	template <typename LinearDiskCacheReader>
	u32 OpenAndRead(const char* filename, LinearDiskCacheReader& reader)
	{
		using std::ios;

		// close any currently opened file
		Close();

		// try opening for reading/writing
		m_file.open(filename, ios::in | ios::out | ios::binary | ios::app);
		
		if (m_file.is_open() && ValidateHeader())
		{
			// good header, read some key/value pairs
			u32 num_entries = 0;

			u32 value_size;
			K key;
			while (Read(&value_size) && Read(&key))
			{
				std::streamoff const pos = m_file.tellg();

				// pass key and value_size to reader with callback function to read the data
				reader(key, value_size, [this, &value_size](V* data){ Read(data, value_size); });

				// seek past data (in case reader didn't read it for whatever reason)
				m_file.seekg(pos + (value_size * sizeof(V)), ios::beg);

				++num_entries;
			}
			m_file.clear();

			return num_entries;
		}

		// failed to open file for reading or bad header
		// close and recreate file
		Close();
		m_file.open(filename, ios::out | ios::trunc | ios::binary);
		WriteHeader();
		return 0;
	}
	
	void Sync()
	{
		m_file.flush();
	}

	void Close()
	{
		if (m_file.is_open())
			m_file.close();
		// clear any error flags
		m_file.clear();
	}

	// Appends a key-value pair to the store.
	void Append(const K &key, const V *value, u32 value_size)
	{
		// TODO: Should do a check that we don't already have "key"?
		Write(&value_size);
		Write(&key);
		Write(value, value_size);
	}

private:
	void WriteHeader()
	{
		Write(&m_header);
	}

	bool ValidateHeader()
	{
		char file_header[sizeof(Header)];

		return (Read(file_header, sizeof(Header))
			&& !memcmp((const char*)&m_header, file_header, sizeof(Header)));
	}

	template <typename D>
	bool Write(const D *data, u32 count = 1)
	{
		return m_file.write((const char*)data, count * sizeof(D)).good();
	}

	template <typename D>
	bool Read(const D *data, u32 count = 1)
	{
		return m_file.read((char*)data, count * sizeof(D)).good();
	}

	struct Header
	{
		Header()
			: id(*(u32*)"DCAC")
			, ver(LINEAR_DISKCACHE_VER)
			, key_t_size(sizeof(K))
			, value_t_size(sizeof(V))
		{}

		const u32 id, ver;
		const u16 key_t_size, value_t_size;

	} m_header;

	std::fstream m_file;
};

#endif  // _LINEAR_DISKCACHE
