#ifndef _tvParser_ServerWorker_h_
#define _tvParser_ServerWorker_h_

#include <Core/Core.h>
#include <MtAlt/MtAlt.h>
#include "common.h"
using namespace Upp;

#ifdef _DEBUG
	#define STATIC_DEBUG const
#else
	#define STATIC_DEBUG static const
#endif

class ServerWorker : public CallbackThread
{
public:
	ServerWorker() :workerId(++workerCount) {InitMimes(); Start();}
	virtual ~ServerWorker() {Shutdown();}
	void Handle_(One<TcpSocket> socket) {Request(&ServerWorker::Handle, socket);}
	
	static void ClearWorkers_()
	{
		managerMutex.Enter();
		for (int i=0; i<workers.GetCount(); ++i)
			workers[i].Shutdown();
		managerMutex.Leave();

		managerMutex.Enter();
		workers.Clear();
		managerMutex.Leave();
	}
	
	template <class T> static ServerWorker *GetServerWorker_()
	{
		managerMutex.Enter();
		if (managerIndex.IsEmpty())
		{
			ServerWorker *out = &(workers.Add(new T()));
			managerMutex.Leave();
			return out;
		}
		
		ServerWorker *out = managerIndex[0];
		managerIndex.Remove(0);
		managerMutex.Leave();
		return out;
	}
	
	static String GetStats_()
	{
		String out;
		managerMutex.Enter();
		out = Format("workers total: %d, free: %d", workers.GetCount(), managerIndex.GetCount());
		managerMutex.Leave();
		return out;
	}

	template <class T> static void ServerThread(TcpSocket &server)
	{
		while (!isShutdown)
		{
			One<TcpSocket> socket = new TcpSocket();
			socket->Timeout(5000);
	
			Cout() << GetLanguageInfo().FormatTime(GetSysTime()) << "   " << ServerWorker::GetStats_() << "\n";
			fflush(stdout);
			
			bool isAccept;
			isAccept = isShutdown ? false : socket->Accept(server);
			if (!isAccept)
				continue;
			
			GetServerWorker_<T>()->Handle_(socket);
		}
	}
	
protected:
	String GetHeaderCookie(const HttpHeader &header, const String &cookieName)
	{
	    for (int i=0;i<header.fields.GetCount();++i)
	    {
			if (header.fields.GetKey(i) == "cookie")
			{
			    String field = header.fields[i];
			    Vector<String> v = Split(field, "; ");
			    for (int j=0; j<v.GetCount(); ++j)
			    {
				if (!v[j].StartsWith(cookieName))
				    continue;
				int eqI = v[j].Find('=');
				if (eqI < 0)
				    continue;
				return v[j].Mid(eqI+1);
			    }
			}
	    }
	    
	    return String();
	}
	
	bool HttpResponseKeepAlive(TcpSocket& socket, bool scgi, int code, const char *phrase,
	                  const char *content_type = NULL, const String& data = Null,
	                  const char *server = NULL,
	                  const VectorMap<String,String> &cookies = VectorMap<String,String>())
	{
		String r;
		r << (scgi ? "Status: " : "HTTP/1.1 ") << code << ' ' << phrase << "\r\n"
			"Date: " <<  WwwFormat(GetUtcTime()) << "\r\n"
			"Server: " << (server ? server : "U++ based server") << "\r\n"
			"Connection: keep-alive\r\n"
			"Access-Control-Allow-Origin: *\r\n"
		;
		if(data.GetCount())
			r << "Content-Length: " << data.GetCount() << "\r\n";
		if(content_type)
			r << "Content-Type: " << content_type << "\r\n";
	
	    Time t = GetSysTime();
		t.Set(t.Get() + 365*24*3600);
		
		for (int i=0; i<cookies.GetCount(); ++i)
			r << "Set-Cookie: " << cookies.GetKey(i) << "=" << cookies[i] << "; expires=" << WwwFormat(t) << "; path=/;\r\n";
		
		r << "\r\n";
		if(!socket.PutAll(r))
			return false;
		return data.GetCount() == 0 || socket.PutAll(data);
	}
	
	VectorMap<String,String> ParseUrlQueryString(const String& url)
	{
		VectorMap<String,String> out;
		const char *p = url;
		while(*p && *p != '\1' && *p != '?')
			p++;
		p = (*p == '?' ? p + 1 : ~url);
	
		while(*p)
		{
			const char *last = p;
			while(*p && *p != '=' && *p != '&')
				p++;
			String key = UrlDecode(last, p);
			if(*p == '=')
				p++;
			last = p;
			while(*p && *p != '&')
				p++;
			out.Add(key, UrlDecode(last, p));
			if(*p)
				p++;
		}
		return out;
	}

	void HttpResponseKeepAliveStaticFile_(TcpSocket& socket, const String &filename, const char *server = NULL,
		const VectorMap<String,String> &cookies = VectorMap<String,String>())
	{
		static RWMutex cacheMutex;
		static VectorMap<String,String> cache;
	
		String data;
		cacheMutex.EnterRead();
		int cacheI = cache.Find(filename);
		if (cacheI < 0)
		{
			cacheMutex.LeaveRead();
			data = LoadFile("static/"+filename);
			cacheMutex.EnterWrite();
			cache.Add(filename, data);
			cacheMutex.LeaveWrite();
		}
		else
		{
			data = cache[cacheI];
			cacheMutex.LeaveRead();
		}
		HttpResponseKeepAlive(socket, false, 200, "OK", GetMime(filename), data, server, cookies);
	}
	
	static String GetMime(String file)
	{
		static const String MIME_UNKNOWN = "application/octet-stream";
		int dotI = file.ReverseFind('.');
		if (dotI < 0 || dotI == file.GetLength()-1)
			return MIME_UNKNOWN;
		String ext = file.Mid(dotI+1);
		int mimeI = mimes.Find(ToLower(ext));
		if (mimeI < 0)
			return MIME_UNKNOWN;
		return mimes[mimeI];
	}

	template<class T> void AddHandler(const String &prefix, void (T::*cb)(const String &,HttpHeader &,TcpSocket &))
	{
		One<HandlerDescriptor<T> >desc;
		desc.Create();
		desc->prefix = prefix;
		desc->cb = cb;
		handlers.Add(desc.Detach());
	}
	
private:
	void Handle(One<TcpSocket> socket)
	{
		static const String METHOD_GET = "GET";
		static const String METHOD_POST = "POST";
		static const dword  TIMEOUT_IDLE = 5000;
		
		dword t0 = GetTickCount();
		while (!isShutdown)
		{
			if (socket->IsEof() || socket->IsAbort() || socket->IsError() || !socket->IsOpen())
				break;
			
			dword t = GetTickCount();
			if (socket->Peek() == -1)
			{
				if (t - t0 > TIMEOUT_IDLE)
					break;
				
				continue;
			}
			
			t0 = t;
			HttpHeader http;
			http.Read(*socket);
			String request;
			
			String url = http.GetURI();
			int qI = url.Find('?');
			if (qI < 0)
				qI = url.Find('&');
			if (qI > 0)
				url = url.Left(qI);
			Time time = GetSysTime();
			RLOG(
				Format(
				"%04d-%02d-%02d %02d:%02d:%02d.%03d ", 
					time.year, time.month, time.day, time.hour, time.minute, time.second, static_cast<int>(GetTickCount() % 1000)
					//,http.addrInfo.host ? http.addrInfo.host : "", http.addrInfo.port ? http.addrInfo.port : ""
					) 
				+ url
				);
			
			bool processed = false;
			for (int i=0; i<handlers.GetCount(); ++i)
			{
				if (url.StartsWith(handlers[i].prefix))
				{
					(&handlers[i])->Execute(this,request,http,*socket);
					processed = true;
					break;
				}
			}
			if (!processed)
			{
				socket->WaitWrite();
				LOG("### 404: "+request);
				HttpResponse(*socket, false, 404, "Resource not found");
				break;
			}
			
			break; //nginx proxy support
		}
		
		socket->Close();
		managerMutex.Enter();
		managerIndex.Add(this);
		managerMutex.Leave();
	}
	
	struct HandlerDescriptor0
	{ String prefix; virtual void Execute(ServerWorker *, const String &,HttpHeader &,TcpSocket &) {} };
	
	template<class OBJECT> struct HandlerDescriptor : public HandlerDescriptor0
	{
		void (OBJECT::*cb)(const String &,HttpHeader &,TcpSocket &);
		virtual void Execute(ServerWorker *o, const String &s,HttpHeader &h,TcpSocket &ts)
		{
			OBJECT *obj = dynamic_cast<OBJECT *>(o);
			ASSERT(obj);
			(obj->*cb)(s,h,ts);
		}
	};
	Array<HandlerDescriptor0> handlers;

	int workerId;
	static int workerCount;
	static Mutex managerMutex;
	static Index<ServerWorker *> managerIndex;
	static Array<ServerWorker> workers;

	static VectorMap<String,String> mimes;
	static void InitMimes()
	{
		static bool init = false;
		//ONCELOCK
		{
			if (init)
				return;
	
			init = true;
		}
		
		mimes.Add("htm","text/html; charset=\"utf-8\"");
		mimes.Add("html","text/html; charset=\"utf-8\"");
		mimes.Add("xml","application/xml");
		mimes.Add("xsl","application/xml");
		mimes.Add("js","text/javascript");
		mimes.Add("json","application/json");
		mimes.Add("bmp","image/bmp");
		mimes.Add("svg","image/svg");
		mimes.Add("gif","image/gif");
		mimes.Add("ico","image/ico");
		mimes.Add("jpeg","image/jpeg");
		mimes.Add("jpg","image/jpeg");
		mimes.Add("png","image/png");
		mimes.Add("tif","image/tiff");
		mimes.Add("tiff","image/tiff");
		mimes.Add("xmp","image/x-xpixmap");
		mimes.Add("mp3","audio/mpeg");
		mimes.Add("zip","application/zip");
		mimes.Add("txt","text/plain");
		mimes.Add("css","text/css");
	}
};

#endif
