#include "stdafx.h"

//#include <hash_map>
//#pragma warning(push, 1)
//#include <boost/filesystem/fstream.hpp>
//#include <boost/format.hpp>
//#pragma warning(disable: 4701)
//#include <boost/lexical_cast.hpp>
//#include <boost/date_time/posix_time/posix_time.hpp>
//#pragma warning(pop)
//#include <boost/date_time/c_local_time_adjustor.hpp>
//#include <boost/filesystem/operations.hpp>

//#include "socket.h"
//#include "main.h"
//#include "config.h"
//#include "http.h"
//#include "define.h"
//#include "utility.h"
//#include "chat.h"

using namespace std;
using boost::lexical_cast;
using namespace boost::posix_time;

string SJISToUTF8(const string& str);
string UTF8ToSJIS(const string& str);
hash_map<string, vector<char> >::iterator getFileCache(hash_map<string, vector<char> >& cache,
														const string& filename);
void mapHttpQuery(const string& src, map<string, string>& dest);
void decode(string& src);

/*!
	@brief Http Processor
*/
class HttpProcessor {
public:
	void setServer(TCPServer<TCPSocketBuffer<TimedTCPSocket>, HttpProcessor>* server) { server_ = server; }
	void acceptDetected() {}
	bool recvDetected(TCPSocketBuffer<TimedTCPSocket>& sockbuf);
	void disconnected() {}
	void dosomething(list<TCPSocketBuffer<TimedTCPSocket> >& /*sockets*/) {}
	void startProcessor() {}

	HttpProcessor() : server_(0) {}

private:
	bool parseProtocol(TCPSocketBuffer<TimedTCPSocket>& s);

	TCPServer<TCPSocketBuffer<TimedTCPSocket>, HttpProcessor>* server_;
	hash_map<string, vector<char> > filecache;
};

bool HttpProcessor::recvDetected(TCPSocketBuffer<TimedTCPSocket>& sockbuf)
{
	if (sockbuf.getBuffer().length() > 8192) {
		//MessageBox(NULL, "Connection killed.(too long data)", "o2on", MB_OK);
		return false;
	}
	if (strfound(sockbuf.getBuffer().find("\r\n\r\n"))) {
		return parseProtocol(sockbuf);
	}
	return true;
}

bool HttpProcessor::parseProtocol(TCPSocketBuffer<TimedTCPSocket>& s)
{
	string& buffer = s.getBuffer();
	do {
		size_t pos = 4 + buffer.find("\r\n\r\n");
		string request(buffer, 0, pos);
		buffer.erase(0, pos);

		// Methodを取得
		if (!strfound(pos = request.find(" "))) return false;
		string method(request, 0, pos);

		//URIを取得
		size_t pos2 = request.find(" ", ++pos);
		if (!strfound(pos2)) return false;
		string uri(request, pos, pos2 - pos);

		string header, body;

		if (method == "GET") {
			if (uri == "/reload") {
				// キャッシュをクリア
				filecache.clear();
				hash_map<string, vector<char> >().swap(filecache);
				uri = "/";
			}
			if (uri == "/") {
				// Redirect to /index.html
				header = "HTTP/1.1 302 Found\r\nLocation: /index.html\r\n";

			} else if (strfound(uri.find("..")) || strfound(uri.find("|"))) {
				// hehehe...
				header = "HTTP/1.1 404 Not Found\r\n";
				body = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1>The requested URL was not found on this server.</body></html>";

			} else {
				boost::filesystem::path htmldir("html"), file(htmldir / uri);
				hash_map<string, vector<char> >::iterator
					it(getFileCache(filecache, file.string()));
				if (it != filecache.end()) {
					header = "HTTP/1.1 200 OK\r\n";

					body.assign(it->second.begin(), it->second.end());

					if (strfound(file.leaf().find(".htm"))) {
						header += "Content-Type: text/html; charset=UTF-8\r\n";
						strreplace(body, "<!--$ver$-->", O2ON_VER_INFO);
						strreplace(body, "<!--$chat_connections$-->",
							lexical_cast<string>(pchatservent->numofConnection()));
						strreplace(body, "<!--$chat_nodes$-->",
							lexical_cast<string>(pchatservent->getProcessor().getNodessize()));

						if (uri.find("/chat/") == 0) {
							if (strfound(body.find("<!--$chat_messages$-->"))) {
								Lock lock(pchatservent->getProcessor().getMsgCS());
								const std::deque<ChatMessage>& msgs
									= pchatservent->getProcessor().getMessages();
								boost::filesystem::path resTemplatePath(htmldir / "chat/res.html");
								hash_map<string, vector<char> >::iterator it(
									getFileCache(filecache, resTemplatePath.string()));
								if (it == filecache.end()) {
									MessageBox(NULL, "/html/chat/res.html not found", "o2on", MB_OK);
									return true;
								}

								const vector<char>& resTemplate(it->second);
								string buf;
								for (std::deque<ChatMessage>::const_reverse_iterator it(
									msgs.rbegin()), end(msgs.rend());
									it != end;
									++it)
								{
									string restemp(resTemplate.begin(), resTemplate.end());
									strreplace(restemp, "<!--$chat_name$-->", it->getName());
									strreplace(restemp, "<!--$chat_msg$-->", it->getMessage());
									strreplace(restemp, "<!--$chat_time$-->",
										to_simple_string(
										boost::date_time::c_local_adjustor<ptime>::utc_to_local(
										from_time_t(it->getTime())).time_of_day()));
									buf += restemp;
								}

								//buf = SJISToUTF8(buf);
								strreplace(body, "<!--$chat_messages$-->", buf);
							}
						}
					}

				} else {
					header = "HTTP/1.1 404 Not Found\r\n";
					body = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1>The requested URL was not found on this server.</body></html>";
				}
			}
		} else if (method == "POST") {
			pos = request.find("Content-Length: ");
			if (!strfound(pos)) return false;
			pos += 16;
			pos2 = request.find("\r", pos);
			if (!strfound(pos2)) return false;
			unsigned contentLength;
			try {
				string tmp(request.substr(pos, pos2 - pos));
				contentLength = lexical_cast<unsigned>(request.substr(pos, pos2 - pos));
			} catch (boost::bad_lexical_cast&) {
				return false;
			}
			if (buffer.length() < contentLength) {
				// リクエストがまだすべて届いてないので制御を戻す
				buffer.insert(0, request);
				return true;
			}
			string postdata(buffer, 0, contentLength);
			buffer.erase(0, contentLength);
			map<string, string> postmap;
			mapHttpQuery(postdata, postmap);

			if (uri == "/chat/post.cgi") {
				decode(postmap["name"]);
				decode(postmap["message"]);
				if (!postmap["message"].empty()) {
					pchatservent->getProcessor().queueChatMessage(/*UTF8ToSJIS(*/postmap["name"]/*)*/,
						/*UTF8ToSJIS(*/postmap["message"]/*)*/);
				}
				header = "HTTP/1.1 302 Found\r\nLocation: index.html\r\n";

			} else if (uri == "/chat/node.cgi") {
				//
			} else {
				header = "HTTP/1.1 404 Not Found\r\n";
				body = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1>The requested URL was not found on this server.</body></html>";
			}

		} else {
			header = "HTTP/1.1 501 Not Implemented\r\n";
			body = "<html><head><title>501 Not Implemented</title></head><body><h1>Not Implemented</h1>The requested Method was not implemented in this server.</body></html>";
		}

		header += "Connection: Keep-Alive\r\n";
		header += boost::str(boost::format("Content-Length: %1%\r\n\r\n") % body.length());
		s.send(header.data(), header.length());
		if (!body.empty()) {
			s.send(body.data(), body.length());
		}
	} while (strfound(buffer.find("\r\n\r\n")));

	return true;
}

unsigned int __stdcall http_thread(void* /*data*/)
{
	unsigned short server_port;
	try {
		server_port = lexical_cast<unsigned short>(getIniString("o2on", "http_port", ""));
	} catch (boost::bad_lexical_cast&) {
		server_port = 50000;
	}

	// Http Processor
	HttpProcessor httpprocessor;

	// Http用server起動
	TCPServer<TCPSocketBuffer<TimedTCPSocket>, HttpProcessor>
		httpserver(httpprocessor, 32);

	if (!httpserver.start(server_port, 0x7f000001)) {
		MessageBox(NULL, "Httpポートのオープンに失敗しました\n設定を変更して再起動してください", "o2on", MB_OK);
		return 0;
	}

	while (do_thread) {
		httpserver.process();
	}

	return 0;
}

string SJISToUTF8(const string& str) {
	if (str.empty())
		return string();

	vector<wchar_t> wbuf(MultiByteToWideChar(CP_ACP, 0, str.data(), str.length(), NULL, 0));
	MultiByteToWideChar(CP_ACP, 0, str.data(), str.length(), &wbuf[0], wbuf.size());
	vector<char> buf(WideCharToMultiByte(CP_UTF8, 0, &wbuf[0], wbuf.size(), NULL, 0, NULL, NULL));
	WideCharToMultiByte(CP_UTF8, 0, &wbuf[0], wbuf.size(), &buf[0], buf.size(), NULL, NULL);
	return string(buf.begin(), buf.end());
}

string UTF8ToSJIS(const string& str) {
	if (str.empty())
		return string();

	vector<wchar_t> wbuf(MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), NULL, 0));
	MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), &wbuf[0], wbuf.size());
	vector<char> buf(WideCharToMultiByte(CP_ACP, 0, &wbuf[0], wbuf.size(), NULL, 0, NULL, NULL));
	WideCharToMultiByte(CP_ACP, 0, &wbuf[0], wbuf.size(), &buf[0], buf.size(), NULL, NULL);
	return string(buf.begin(), buf.end());
}

hash_map<string, vector<char> >::iterator getFileCache(hash_map<string, vector<char> >& cache,
														const string& filename)
{
	hash_map<string, vector<char> >::iterator it(cache.find(filename));
	if (it != cache.end()) {	// cacheされていたら
		return it;
	} else {
		ifstream ifs(filename.c_str(), ios::binary);
		if (ifs.fail()) {
			return cache.end();
		}
		cache.insert(hash_map<string, vector<char> >::value_type(filename, vector<char>()));
		it = cache.find(filename);
		it->second.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
		return it;
	}
}

void decode(string& src)
{
	// 要件等
	if (src.empty()) return;
	string dest;
	string::const_iterator it(src.begin()), end(src.end());
	while (it != end) {
		if (*it == '%') {
			if (++it == end) break;
			char a = *it;
			a += (a >= 'A') ? (- 'A' + 10) : (- '0');
			if (++it == end) break;
			char b = *it;
			b += (b >= 'A') ? (- 'A' + 10) : (- '0');
			dest += static_cast<char>((a << 4) | b);
		} else if (*it == '+') {
			dest += ' ';
		} else {
			dest += *it;
		}
		++it;
	}
	src = dest;
}

void mapHttpQuery(const string& src, map<string, string>& dest)
{
	istringstream iss(src);
	while (iss) {
		string key;
		if (!getline(iss, key, '=')) break;
		string value;
		if (!getline(iss, value, '&')) break;
		dest.insert(pair<string, string>(key, value));
	}
}