///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#include <cppcms/config.h>
#include <set>

#ifndef CPPCMS_NO_CACHE
#include "cache_storage.h"
#include <booster/thread.h>
#include <iostream>



#if defined CPPCMS_WIN32 
# ifndef CPPCMS_NO_PREFOK_CACHE
#   define CPPCMS_NO_PREFOK_CACHE
# endif
#endif

#ifndef CPPCMS_NO_PREFOK_CACHE
# include "posix_util.h"
# include "shmem_allocator.h"
#endif

#include <map>
#include <string>
#include <list>
#include <limits>
#include <booster/auto_ptr_inc.h>
#include <time.h>
#include <cppcms/cstdint.h>

#include "hash_map.h"

#include <string.h>

namespace cppcms {
namespace impl {

template<typename String>
struct copy_traits {
	static String to_int(std::string const &other)
	{
		return String(other.c_str(),other.size());
	}
	static std::string to_std(String const &other)
	{
		return std::string(other.c_str(),other.size());
	}
};

template<>
struct copy_traits<std::string>
{
	static std::string to_int(std::string const &a) { return a; }
	static std::string to_std(std::string const &a) { return a; }
};

struct string_equal {
	template<typename S1,typename S2>
	bool operator()(S1 const &s1,S2 const &s2) const
	{
		return s1.size() == s2.size() && memcmp(s1.c_str(),s2.c_str(),s1.size()) == 0;
	}
};

#ifndef CPPCMS_NO_PREFOK_CACHE

struct process_settings {
	static shmem_control *process_memory;

	typedef ::cppcms::impl::mutex mutex_type;
	typedef ::cppcms::impl::shared_mutex shared_mutex_type;
	typedef mutex_type::guard lock_guard;
	typedef shared_mutex_type::shared_guard rdlock_guard;
	typedef shared_mutex_type::unique_guard wrlock_guard;
	typedef shmem_allocator<char,process_memory> allocator;
	
	static bool not_enough_memory()
	{
		return process_memory->max_available()  < process_memory->size() / 10;
	}
	
	static size_t size_limit()
	{
		return process_memory->size() / 20;
	}
	
	
	static void init(size_t size)
	{
		if(process_memory)
			return;
		process_memory=new shmem_control(size);
	}
	static const bool process_shared = true;
};

shmem_control *process_settings::process_memory=0;

#endif

struct thread_settings {
	typedef booster::mutex mutex_type;
	typedef booster::shared_mutex shared_mutex_type;
	typedef booster::unique_lock<booster::mutex> lock_guard;
	typedef booster::shared_lock<booster::shared_mutex> rdlock_guard;
	typedef booster::unique_lock<booster::shared_mutex> wrlock_guard;
	typedef std::allocator<char> allocator;
	static bool not_enough_memory() { return false; }
	static size_t size_limit() { return std::numeric_limits<size_t>::max(); }
	static const bool process_shared = false;
};


template<typename Setup>
class mem_cache : public base_cache {
	typedef typename Setup::mutex_type mutex_type;
	typedef typename Setup::shared_mutex_type shared_mutex_type;
	std::auto_ptr<mutex_type> lru_mutex;
	std::auto_ptr<shared_mutex_type> access_lock;
	typedef typename Setup::allocator allocator;
	typedef typename Setup::lock_guard lock_guard;
	typedef typename Setup::rdlock_guard rdlock_guard;
	typedef typename Setup::wrlock_guard wrlock_guard;
	typedef typename allocator::template rebind<mem_cache>::other this_allocator;

	bool not_enough_memory()
	{
		return Setup::not_enough_memory();
	}

	size_t size_limit()
	{
		return Setup::size_limit();
	}

	struct container;

	typedef std::basic_string<char,std::char_traits<char>,allocator > string_type;

	typedef hash_map<
			string_type,
			container,
			string_hash,
			string_equal,
			typename allocator::template rebind<std::pair<const string_type,container> >::other
		> map_type;

	typedef typename map_type::iterator pointer;

	typedef std::list<
			pointer,
			typename allocator::template rebind<pointer>::other
		> pointer_list_type;

	typedef hash_map<
			string_type,
			pointer_list_type,
			string_hash,
			string_equal,
			typename allocator::template rebind<std::pair<const string_type,pointer_list_type> >::other
		> triggers_map_type;

	typedef std::pair<
			typename triggers_map_type::iterator,
			typename pointer_list_type::iterator
		> trigger_ptr_type;

	typedef std::list<
			trigger_ptr_type,
			typename allocator::template rebind<trigger_ptr_type>::other
		> triggers_list_type;

	typedef std::multimap<
			time_t,
			pointer,
			std::less<time_t>,
			typename allocator::template rebind<std::pair<const time_t,pointer> >::other 
		> timeout_mmap_type;

	struct container {
		string_type data;
		typedef typename map_type::iterator pointer;
		typename pointer_list_type::iterator lru;
		triggers_list_type triggers;
		typename timeout_mmap_type::iterator timeout;
		uint64_t generation;
	};

	map_type primary;
	triggers_map_type triggers;
	typedef typename triggers_map_type::iterator triggers_ptr;
	timeout_mmap_type timeout;
	typedef typename timeout_mmap_type::iterator timeout_ptr;
	pointer_list_type lru;
	typedef typename pointer_list_type::iterator lru_ptr;
	unsigned limit;
	size_t size;
	size_t triggers_count;
	int refs;
	uint64_t generation;

	string_type to_int(std::string const &other)
	{
		return copy_traits<string_type>::to_int(other);
	}
	std::string to_std(string_type const &other)
	{
		return copy_traits<string_type>::to_std(other);
	}

	void delete_node(pointer p)
	{
		lru.erase(p->second.lru);
		timeout.erase(p->second.timeout);
		typename triggers_list_type::iterator i;
		for(i=p->second.triggers.begin();i!=p->second.triggers.end();i++) {
			i->first->second.erase(i->second);
			triggers_count --;
			if(i->first->second.empty())
				triggers.erase(i->first);
		}
		primary.erase(p);
		size--;
	}

public:
	mem_cache(unsigned pages=0) : 
		lru_mutex(new mutex_type()),
		access_lock(new shared_mutex_type()),
		limit(pages),
		size(0),
		refs(0),
		generation(0)
	{
		nl_clear();
	}
	~mem_cache()
	{
	}
	void set_size(unsigned l) 
	{ 
		limit = l; 
		nl_clear();
	};
	virtual bool fetch(std::string const &key,std::string *a,std::set<std::string> *triggers,time_t *timeout_out,uint64_t *gen)
	{
		rdlock_guard lock(*access_lock);
		pointer p;
		time_t now;
		time(&now);

		if((p=primary.find(key))==primary.end() || p->second.timeout->first < now) {
			return false;
		}

		{ // Update LRU
			lock_guard lock(*lru_mutex);
			lru.erase(p->second.lru);
			lru.push_front(p);
			p->second.lru=lru.begin();
		}

		if(a)
			*a=to_std(p->second.data);

		if(triggers) {
			typename triggers_list_type::iterator tp;
			for(tp=p->second.triggers.begin();tp!=p->second.triggers.end();tp++) {
				triggers->insert(to_std(tp->first->first));
			}
		}

		if(timeout_out) {
			*timeout_out=p->second.timeout->first;
		}		

		if(gen)
			*gen=p->second.generation;

		return true;
	}
	virtual void rise(std::string const &trigger)
	{
		wrlock_guard lock(*access_lock);
		triggers_ptr p = triggers.find(trigger);
		if(p==triggers.end())
			return;
		std::list<pointer> kill_list;
		for(typename pointer_list_type::iterator it=p->second.begin();it!=p->second.end();++it) {
			kill_list.push_back(*it);
		}
		typename std::list<pointer>::iterator lptr;

		for(lptr=kill_list.begin();lptr!=kill_list.end();lptr++) {
			delete_node(*lptr);
		}
	}
	void nl_clear()
	{
		timeout.clear();
		lru.clear();
		primary.clear();
		primary.rehash(limit);
		triggers.clear();
		triggers.rehash(limit);
		size = 0;
		triggers_count = 0;
	}
	virtual void clear()
	{
		wrlock_guard lock(*access_lock);
		nl_clear();
	}
	virtual void stats(unsigned &keys,unsigned &triggers)
	{
		rdlock_guard lock(*access_lock);
		keys=size;
		triggers = triggers_count;
	}
	void check_limits()
	{
		pointer main=primary.end();
		time_t now;
		time(&now);

		while(size > 0 && (not_enough_memory() || (size>=limit && limit>0)))
		{
			if(!timeout.empty() && timeout.begin()->first<now) {
				main=timeout.begin()->second;
			}
			else if(!lru.empty()){
				main=*lru.rbegin();
			}
			else
				break;
			delete_node(main);
		}
	}
	virtual void remove(std::string const &key)
	{
		wrlock_guard lock(*access_lock);
		pointer p=primary.find(key);
		if(p==primary.end())
			return;
		delete_node(p);
	}

	void add_trigger(pointer p,std::string const &key)
	{
		std::pair<string_type,pointer_list_type> tr(to_int(key),pointer_list_type());
		std::pair<triggers_ptr,bool> r=triggers.insert(tr);
		triggers_ptr it = r.first;
		it->second.push_front(p);
		p->second.triggers.push_back(trigger_ptr_type(it,it->second.begin()));
		triggers_count++;
	}

	virtual void store(	std::string const &key,
				std::string const &a,
				std::set<std::string> const &triggers_in,
				time_t timeout_in,
				uint64_t const *gen)
	{
		string_type ar;
		try {
			string_type tmp = to_int(a);
			ar.swap(tmp);
		}
		catch(std::bad_alloc const &) {
			return;
		}

		wrlock_guard lock(*access_lock);
		try {
			pointer main;
			main=primary.find(key);
			if(main!=primary.end())
				delete_node(main);
			if(size > size_limit())
				return;
			check_limits();
			string_type int_key = to_int(key);

			std::pair<pointer,bool> res=primary.insert(std::pair<string_type,container>(int_key,container()));
			size ++;
			main=res.first;
			container &cont=main->second;
			cont.data.swap(ar);
			if(gen)
				cont.generation=*gen;
			else
				cont.generation=generation++;
			lru.push_front(main);
			cont.lru=lru.begin();
			cont.timeout=timeout.insert(std::pair<time_t,pointer>(timeout_in,main));
			if(triggers_in.find(key)==triggers_in.end()){
				add_trigger(main,key);
			}
			std::set<std::string>::const_iterator si;
			for(si=triggers_in.begin();si!=triggers_in.end();si++) {
				add_trigger(main,*si);
			}
		}
		catch(std::bad_alloc const &e)
		{
			nl_clear();
		}
	}
	virtual void add_ref()
	{
		if(Setup::process_shared)
			return;
		wrlock_guard lock(*access_lock);
		refs++;
	}
	virtual bool del_ref()
	{
		// This object should not be deleted because it is created only once
		// and exists in memory
		if(Setup::process_shared)
			return false;
		wrlock_guard lock(*access_lock);
		refs--;
		if(refs==0)
			return true;
		return false;
	}

	void *operator new(size_t /*n*/) 
	{
		return this_allocator().allocate(1);
	}

	void operator delete(void *p)
	{
		this_allocator().deallocate(reinterpret_cast<mem_cache *>(p),1);
	}

}; // mem cache


booster::intrusive_ptr<base_cache> thread_cache_factory(unsigned items)
{
	return new mem_cache<thread_settings>(items);
}

#if !defined(CPPCMS_NO_PREFOK_CACHE)
booster::intrusive_ptr<base_cache> process_cache_factory(size_t memory,unsigned items)
{
	process_settings::init(memory);
	return new mem_cache<process_settings>(items);
}
#endif

} // impl 
} // cppcms

#endif // CPPCMS_NO_PREFOK_CACHE

