#ifndef _mailersrv_PersistentMap_h_
#define _mailersrv_PersistentMap_h_

#include <Core/Core.h>
#include <MtAlt/MtAlt.h>
using namespace Upp;

template<class STORAGE> class OpenSyncStorage : public STORAGE
{
public:
	OpenSyncStorage()
		:changed(false)
	{
	}
	
	inline void EnterRead()  {storageMutex.EnterRead(); }
	inline void LeaveRead()  {storageMutex.LeaveRead(); }
	inline void EnterWrite() {storageMutex.EnterWrite();}
	inline void LeaveWriteSetChanged(bool _changed = true)
	                         {storageMutex.LeaveWrite(); changed = _changed;}

protected:
	volatile bool  changed;

private:
	RWMutex        storageMutex;
};

template<class STORAGE> class OpenPersistentStorage : public OpenSyncStorage<STORAGE>
{
public:
	OpenPersistentStorage(const String &_filename, int *version = NULL)
		:filename(_filename)
		,shutdown(false)
	{
		if (!version)
			LoadFromFile(*this, filename);
		else
		{
			String filenameBase = filename;
			String filenameCur = filenameBase + '.'  + FormatInt(*version);
			int versionOld = *version;
			while ((*version) >= 0)
			{
				LOG(String("Attempt loading ")+" version: "+FormatInt(*version));
				try
				{
					filenameCur = filenameBase + '.'  + FormatInt(*version);
					if (LoadFromFile(*this, filenameCur))
					{
						LOG(String("Loaded ")+" version: "+FormatInt(*version));
						break;
					}
				}
				catch (...)
				{
				}
				--(*version);
			}
			*version = versionOld;
			filename = filenameCur = filenameBase + '.'  + FormatInt(*version);
		}
		writerThread.Run(callback(this, &OpenPersistentStorage<STORAGE>::WriterThreadWork));
	}
	
	virtual ~OpenPersistentStorage()
	{
		Shutdown();
		writerThread.Wait();
	}
	
	void Shutdown()
	{
		shutdown = true;
	}
		
private:
	void WriterThreadWork()
	{
		while (!shutdown)
		{
			if (!OpenSyncStorage<STORAGE>::changed)
			{
				Sleep(200);
				continue;
			}
			if (shutdown && !OpenSyncStorage<STORAGE>::changed)
				break;
			if (!shutdown)
				Sleep(200 + Random(200));
			OpenSyncStorage<STORAGE>::EnterRead();
			FileMove(filename, filename+".old");
			StoreToFile(*this, filename);
			OpenSyncStorage<STORAGE>::changed = false;
			OpenSyncStorage<STORAGE>::LeaveRead();
			if (shutdown)
				break;
		}
	}
	
	Thread         writerThread;
	bool           shutdown;
	String         filename;
};

template<class STORAGE> class PersistentStorage : public OpenSyncStorage<STORAGE>
{
public:
	PersistentStorage(const String &_filename)
		:filename(_filename)
		,shutdown(false)
	{
		LoadFromFile(storage, filename);
		writerThread.Run(callback(this, &PersistentStorage<STORAGE>::WriterThreadWork));
	}
	
	virtual ~PersistentStorage()
	{
		Shutdown();
		writerThread.Wait();
	}
	
	void Shutdown()
	{
		shutdown = true;
	}
	
protected:
	STORAGE storage;
		
private:
	void WriterThreadWork()
	{
		while (!shutdown)
		{
			if (!OpenSyncStorage<STORAGE>::changed)
			{
				Sleep(200);
				continue;
			}
			if (shutdown && !OpenSyncStorage<STORAGE>::changed)
				break;
			if (!shutdown)
				Sleep(200 + Random(200));
			OpenSyncStorage<STORAGE>::EnterRead();
			FileMove(filename, filename+".old");
			StoreToFile(storage, filename);
			OpenSyncStorage<STORAGE>::changed = false;
			OpenSyncStorage<STORAGE>::LeaveRead();
			if (shutdown)
				break;
		}
	}
	
	Thread         writerThread;
	bool           shutdown;
	String         filename;
};

template<class K, class V> class PersistentMap : public PersistentStorage<VectorMap<K,V> >
{
public:
	PersistentMap(const String &filename)
		:PersistentStorage<VectorMap<K,V> >(filename)
	{
	}
	
	virtual ~PersistentMap()
	{
	}
	
	void Shutdown()
	{
		PersistentStorage<VectorMap<K,V> >::Shutdown();
	}
	
	void Add(const K &k, const V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		PersistentStorage<VectorMap<K,V> >::storage.Add(k,v);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	void AddPick(const K &k, pick_ V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		PersistentStorage<VectorMap<K,V> >::storage.AddPick(k,v);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	bool Get(const K &k, V &v)
	{
		bool out = false;
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i >= 0)
		{
			out = true;
			v = PersistentStorage<VectorMap<K,V> >::storage[i];
		}
		PersistentStorage<VectorMap<K,V> >::LeaveRead();
		return out;
	}
	
	bool GetFirst(K &k, V &v)
	{
		bool out = false;
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i >= 0)
		{
			out = true;
			k = PersistentStorage<VectorMap<K,V> >::storage.GetKey(i);
			v = PersistentStorage<VectorMap<K,V> >::storage[i];
		}
		PersistentStorage<VectorMap<K,V> >::LeaveRead();
		return out;
	}
	
	void Put(const K &k, const V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i < 0)
			PersistentStorage<VectorMap<K,V> >::storage.Add(k,v);
		else
			PersistentStorage<VectorMap<K,V> >::storage[i] = v;
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	void PutPick(const K &k, pick_ V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i < 0)
			PersistentStorage<VectorMap<K,V> >::storage.AddPick(k,v);
		else
			PersistentStorage<VectorMap<K,V> >::storage[i] = v;
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	void CheckAdd(const K &k, const V &vDefault = V())
	{
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i >= 0)
		{
			PersistentStorage<VectorMap<K,V> >::LeaveRead();
			return;
		}
		PersistentStorage<VectorMap<K,V> >::LeaveRead();

		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		PersistentStorage<VectorMap<K,V> >::storage.Add(k,vDefault);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	void GetAdd(const K &k, V &v, const V &vDefault = V())
	{
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i >= 0)
		{
			v = PersistentStorage<VectorMap<K,V> >::storage[i];
			PersistentStorage<VectorMap<K,V> >::LeaveRead();
			return;
		}
		PersistentStorage<VectorMap<K,V> >::LeaveRead();

		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		v = PersistentStorage<VectorMap<K,V> >::storage.Add(k,vDefault);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
	}
	
	int GetCount()
	{
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		int out = PersistentStorage<VectorMap<K,V> >::storage.GetCount();
		PersistentStorage<VectorMap<K,V> >::LeaveRead();
		
		return out;
	}

	bool Find(const K &k)
	{
		PersistentStorage<VectorMap<K,V> >::EnterRead();
		bool out = (PersistentStorage<VectorMap<K,V> >::storage.Find(k) >= 0);
		PersistentStorage<VectorMap<K,V> >::LeaveRead();
		return out;
	}
	
	bool Remove(const K &k)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		int i = PersistentStorage<VectorMap<K,V> >::storage.Find(k);
		if (i < 0)
		{
			PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged(false);
			return false;
		}
		
		PersistentStorage<VectorMap<K,V> >::storage.Remove(i);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
		return true;
	}
	
	bool GetRemoveFirst(K &k, V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		if (PersistentStorage<VectorMap<K,V> >::storage.IsEmpty())
		{
			PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged(false);
			return false;
		}

		k = PersistentStorage<VectorMap<K,V> >::storage.GetKey(0);
		v = PersistentStorage<VectorMap<K,V> >::storage[0];
		PersistentStorage<VectorMap<K,V> >::storage.Remove(0);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
		return true;
	}
	
	bool GetRemoveRandom(K &k, V &v)
	{
		PersistentStorage<VectorMap<K,V> >::EnterWrite();
		if (PersistentStorage<VectorMap<K,V> >::storage.IsEmpty())
		{
			PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged(false);
			return false;
		}

		int i = Random() % PersistentStorage<VectorMap<K,V> >::storage.GetCount();
		k = PersistentStorage<VectorMap<K,V> >::storage.GetKey(0);
		v = PersistentStorage<VectorMap<K,V> >::storage[0];
		PersistentStorage<VectorMap<K,V> >::storage.Remove(0);
		PersistentStorage<VectorMap<K,V> >::LeaveWriteSetChanged();
		return true;
	}
};

#endif
