///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#include "tc_test_content.h"
#include <cppcms/service.h>
#include <cppcms/http_response.h>
#include <cppcms/json.h>
#include <cppcms/cache_interface.h>
#include <cppcms/url_mapper.h>
#include <booster/posix_time.h>
#include "dummy_api.h"
#include "test.h"

#include <iomanip>
#include <sstream>

#include <cppcms/serialization.h>

struct mydata : public cppcms::serializable { 
	int x;
	int y;
	mydata(int a=0,int b=0) : x(a), y(b) {}
	void serialize(cppcms::archive &a)
	{
		a & x & y;
	}
};


class test_app : public cppcms::application {
public:
	test_app(cppcms::service &srv) : 
		cppcms::application(srv),
		srv_(srv)
	{
	}
	~test_app()
	{
		release_context();
	}
	void set_context(bool gzip=false)
	{
		std::map<std::string,std::string> env;
		env["HTTP_HOST"]="www.example.com";
		env["SCRIPT_NAME"]="/foo";
		env["PATH_INFO"]="/bar";
		env["REQUEST_METHOD"]="GET";
		if(gzip)
			env["HTTP_ACCEPT_ENCODING"]="gzip, deflate";
		booster::shared_ptr<dummy_api> api(new dummy_api(srv_,env,output_));
		booster::shared_ptr<cppcms::http::context> cnt(new cppcms::http::context(api));
		assign_context(cnt);
		response().io_mode(cppcms::http::response::normal);
		output_.clear();
	}

	std::string str()
	{
		response().finalize();
		size_t from = output_.find("\r\n\r\n");
		std::string result;
		if(from==std::string::npos) {
			result = output_;
			gzip_ = false;
		}
		else {
			result = output_.substr(from+4);
			gzip_ = output_.substr(0,from).find("gzip")!=std::string::npos;
		}
		output_.clear();
		return result;
	}
	void test_basic()
	{
		std::cout << "- Page Basic" << std::endl;
		
		set_context(false);
		TEST(cache().has_cache());
		TEST(!cache().fetch_page("test"));
		response().out() << "test";
		cache().add_trigger("x");
		cache().store_page("test");
		TEST(str()=="test");
		
		set_context(false);
		TEST(cache_size()==1);
		TEST(cache().fetch_page("test"));
		TEST(str()=="test");
		
		set_context(false);
		cache().rise("x");
		TEST(cache_size() == 0);
		TEST(!cache().fetch_page("test"));
		cache().add_trigger("x");
		response().out() << "test2";
		cache().reset(); // reset works
		cache().store_page("test",2);
		TEST(str()=="test2");
		set_context(false);
		cache().rise("x");
		TEST(cache_size() == 1);
		TEST(cache().fetch_page("test"));
		TEST(str() == "test2");
		booster::ptime::millisleep(3000);
		set_context(false);
		TEST(cache().fetch_page("test")==false);
		cache().clear();
		release_context();
	}

	void test_gzip()
	{
		std::cout << "- Page Gzip" << std::endl;
		set_context(true);
		cache().clear();
		TEST(request().getenv("HTTP_ACCEPT_ENCODING") == "gzip, deflate");
		TEST(request().getenv(std::string("HTTP_ACCEPT_ENCODING")) == "gzip, deflate");
		cache().fetch_page("test");
		response().out() << "gzip";
		cache().store_page("test");
		TEST(str().substr(0,2)=="\x1f\x8b");
		TEST(gzip_);
		
		set_context(false);
	
		TEST(request().getenv("HTTP_ACCEPT_ENCODING") == "");
		TEST(cache_size() == 1);

		TEST(cache().fetch_page("test") ==false);
		response().out() << "gzip";
		cache().store_page("test");
		TEST(str() == "gzip");
		TEST(!gzip_);

		set_context(false);
		TEST(cache().fetch_page("test"));
		TEST(str()=="gzip");
		TEST(!gzip_);

		set_context(true);
		TEST(cache().fetch_page("test"));
		TEST(str().substr(0,2)=="\x1f\x8b");
		TEST(gzip_);

		set_context(false);
		TEST(cache_size()==2);
		cache().clear();
		TEST(cache_size()==0);
		release_context();
	}




	void test_objects()
	{
		std::cout << "- Data Object" << std::endl;
		set_context(false);

		{
			std::cout << "-- With Triggers Full API" << std::endl;
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d(1,2);
				std::set<std::string> t1,t2;
				t1.insert("k1");
				t2.insert("k2");
				cache().store_frame("foo","bar",t1);
				cache().store_data("dat",d,t2);
				TEST(tr.detach().size()==4);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp));
				TEST(cache().fetch_data("dat",d));
				TEST(d.x==1  && d.y==2);
				TEST(tmp=="bar");
				std::set<std::string> tg=tr.detach();
				TEST(tg.size()==4);
				TEST(tg.count("foo")==1);
				TEST(tg.count("dat")==1);
				TEST(tg.count("k1")==1);
				TEST(tg.count("k2")==1);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp,true));
				TEST(cache().fetch_data("dat",d,true));
				TEST(d.x==1  && d.y==2);
				TEST(tmp=="bar");
				TEST(tr.detach().size()==0);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d(4,5);
				cache().store_frame("foo","baz",-1);
				cache().store_data("dat",d,-1);
				TEST(tr.detach().size()==2);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp));
				TEST(cache().fetch_data("dat",d));
				TEST(d.x==4  && d.y==5);
				TEST(tmp=="baz");
				std::set<std::string> tg=tr.detach();
				TEST(tg.size()==2);
				TEST(tg.count("foo")==1);
				TEST(tg.count("dat")==1);
			}
		}
		cache().clear();
		{
			std::cout << "-- Without Triggers" << std::endl;
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d(1,2);
				cache().store_frame("foo","bar",std::set<std::string>(),-1,true);
				cache().store_data("dat",d,std::set<std::string>(),-1,true);
				TEST(tr.detach().size()==0);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp,true));
				TEST(cache().fetch_data("dat",d,true));
				TEST(d.x==1  && d.y==2);
				TEST(tmp=="bar");
				TEST(tr.detach().size()==0);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp));
				TEST(cache().fetch_data("dat",d));
				TEST(d.x==1  && d.y==2);
				TEST(tmp=="bar");
				TEST(tr.detach().size()==2);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d(4,5);
				cache().store_frame("foo","baz",-1,true);
				cache().store_data("dat",d,-1,true);
				TEST(tr.detach().size()==0);
			}
			{
				cache().reset();
				cppcms::triggers_recorder tr(cache());
				mydata d;
				std::string tmp;
				TEST(cache().fetch_frame("foo",tmp,true));
				TEST(cache().fetch_data("dat",d,true));
				TEST(d.x==4  && d.y==5);
				TEST(tmp=="baz");
				TEST(tr.detach().size()==0);
			}
		}
		cache().clear();
		{
			std::cout << "-- Timeouts" << std::endl;
			{
				cache().reset();
				mydata d1(1,2);
				mydata d2(4,5);
				cache().store_frame("foo1","baz1",std::set<std::string>(),2,true);
				cache().store_data("dat1",d1,std::set<std::string>(),2,true);
				cache().store_frame("foo2","baz2",2,true);
				cache().store_data("dat2",d2,2,true);
			}
			{
				cache().reset();
				mydata d1,d2;
				std::string t1,t2;
				TEST(cache().fetch_frame("foo1",t1,true));
				TEST(cache().fetch_frame("foo2",t2,true));
				TEST(cache().fetch_data("dat1",d1,true));
				TEST(cache().fetch_data("dat2",d2,true));
				TEST(d1.x==1  && d1.y==2);
				TEST(d2.x==4  && d2.y==5);
				TEST(t1=="baz1");
				TEST(t2=="baz2");
			}
			booster::ptime::millisleep(3000);
			{
				cache().reset();
				mydata d1,d2;
				std::string t1,t2;
				TEST(!cache().fetch_frame("foo1",t1,true));
				TEST(!cache().fetch_frame("foo2",t2,true));
				TEST(!cache().fetch_data("dat1",d1,true));
				TEST(!cache().fetch_data("dat2",d2,true));
			}
		}


	}


	unsigned cache_size()
	{
		unsigned keys,triggers;
		TEST(cache().stats(keys,triggers));
		return keys;
	}
	
private:
	std::string output_;
	bool gzip_;
	cppcms::service &srv_;
};

void test_independent(cppcms::service &srv)
{
	std::cout << "- Context independent cache test\n";
	// normal ops
	cppcms::cache_interface cache(srv);
	cache.clear();
	unsigned n=0,m;
	TEST(cache.stats(n,m));
	TEST(n==0);
	cache.store_frame("a","b");
	TEST(cache.stats(n,m));
	TEST(n==1);
	std::string tmp;
	TEST(cache.fetch_frame("a",tmp));
	TEST(tmp=="b");
	cache.rise("a");
	TEST(!cache.fetch_frame("a",tmp));
	TEST(cache.stats(n,m));
	TEST(n==0);
	// Does not crash without a context
	TEST(cache.fetch_page("x") == false);
	cache.store_page("y");
}


int main()
{
	try {
		cppcms::json::value cfg;
		cfg["cache"]["backend"]="thread_shared";
		cfg["cache"]["limit"]=100;
		cppcms::service srv(cfg);
		test_app app(srv);

		app.set_context();

		app.test_basic();
		app.test_gzip();
		app.test_objects();
		test_independent(srv);
	}
	catch(std::exception const &e)
	{
		std::cerr << "Fail " << e.what() << std::endl;
		return 1;
	}
	std::cout << "Ok" << std::endl;
	return 0;
}



