#include "webinterface"
#include "balancer/balancer"

static void stop_backend_thread(pthread_t id) {
    Threadinfo info = Threadlist::info(id);
    msg("Stopping thread " << id << 
	" (backend socket " << info.backendfd() <<
	", client socket " << info.clientfd() + ")\n");
    close(info.backendfd());
    close(info.clientfd());
    Threadlist::deregister(id);
}    	

static unsigned str2uns (string const &s, string const &desc) {
    unsigned ret;

    if (sscanf (s.c_str(), "%u", &ret) < 1)
	throw Error("Bad " + desc);
    return (ret);
}

static double str2dbl (string const &s, string const &desc) {
    double ret;

    if (sscanf (s.c_str(), "%lf", &ret) < 0)
	throw Error("Bad " + desc);
    return (ret);
}

static pthread_t str2threadid (string const &s, string const &desc) {
    pthread_t ret;
    long long val;
    int convret;

    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
	convret = sscanf(s.c_str() + 2, "%llx", &val);
    else
	convret = sscanf(s.c_str(), "%lld", &val);
    if (convret < 1)
	throw Error("Bad " + desc);
    memcpy (&ret, &val, sizeof(ret));
    return (ret);
}

static unsigned backendindex (string const &s) {
    unsigned ret;

    ret = str2uns (s, "back end index");
    if (ret >= balancer.nbackends())
	throw Error("Back end index out of range");
    return (ret);
}

static unsigned headerindex (string const &s) {
    unsigned ret;

    ret = str2uns (s, "header index");
    if (ret >= config.nserverheaders())
	throw Error("Server header index out of range");
    return (ret);
}

bool str2bool (string const &s, string const &desc) {
    int i;
    bool ret;

    if (sscanf (s.c_str(), "%d", &i) > 0)
	ret = (i != 0);
    else if (s == "on" || s == "yes" || s == "true")
	ret = true;
    else if (s == "off" || s == "no" || s == "false")
	ret = false;
    else
	throw Error("Bad " + desc + " switch '" + s + "'");

    return (ret);
}

string decode (string const &s) {
    string ret;

    for (char const *cp = s.c_str(); cp && *cp;) {
	if (*cp == '%') {
	    int v;
	    cp++;
	    if (sscanf (cp, "%2x", &v)) {
		ret += static_cast<char>(v);
		cp += 2;
	    }else {
		ret += '%';
	    }
	} else if (*cp == '+') {
	    ret += ' ';
	} else {
	    ret += *cp;
	    cp++;
	}
    }

    // debugmsg ("Decoded: '" + s + "' into '" + ret + "'\n");

    return (ret);
}

void Webinterface::answer(Httpbuffer req) {
    if (req.requestmethod() != Httpbuffer::m_get)
	throw Error("Only request method GET supported");

    // If web interface authentication is in effect, then we need the creds
    // for all requests.
    if (config.webinterface_auth() != "") {
	// Incoming auth headers have the format:
	// Authorization: Basic BASE64-ENCODED-UN:PW
	string given_auth = req.headerval("Authorization:");
	if (given_auth.length() > 6)
	    given_auth = given_auth.substr(6);
	if (base64_decode(given_auth) != config.webinterface_auth()) {
	    string resp =
		"HTTP/1.0 401 Authorization Required\r\n"
		"WWW-Authenticate: Basic realm=\"Crossroads Web Interface\"\r\n"
		"Content-Length: 0\r\n"
		"\r\n";
	    Netbuffer buf(resp);
	    buf.netwrite(cfd, config.client_write_timeout());
	    return;
	}
    }

    string uri = req.requesturi();

    // Status overview
    if (uri == "/") {
	answer_status();
	return;
    }

    // XSLT request
    if (uri == "/xslt") {
	answer_xslt();
	return;
    }

    // /favicon.ico requests (those pesky browsers)
    if (uri == "/favicon.ico") {
	string resp =
	    "HTTP/1.0 404 Not Found\r\n"
	    "Content-Length: 0\r\n"
	    "\r\n";
	Netbuffer buf(resp);
	buf.netwrite(cfd, config.client_write_timeout());
	return;
    }

    if (uri[0] == '/')	
	uri = uri.substr(1);    
    vector<string> parts = str2parts (uri, '/');
    for (unsigned i = 0; i < parts.size(); i++)
	parts[i] = decode(parts[i]);

    // server/buffersize/VALUE
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "buffersize") {
	unsigned sz = str2uns (parts[2], "buffer size");
	if (sz < 1)
	    throw Error("Buffer size may not be less than 1");
	config.buffersize(sz);
	answer_status();
	return;
    }

    // /server/webinterfaceauth/
    // /server/webinterfaceauth/USER:PASS
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "webinterfaceauth") {
	config.webinterface_auth(parts[2]);
	answer_status();
	return;
    }

    // /server/maxconnections/
    // /server/maxconnections/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "maxconnections") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "server weight");
	config.maxconn(num);
	answer_status();
	return;
    }

    // /server/addxrversion/BOOLEAN
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "addxrversion") {
	config.addxrversion (str2bool (parts[2], "addxrversion"));
	answer_status();
	return;
    }

    // /server/addxforwardedfor/BOOLEAN
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "addxforwardedfor") {
	config.addxforwardedfor (str2bool (parts[2], "addxforwardedfor"));
	answer_status();
	return;
    }

    // /server/stickyhttp/BOOLEAN
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "stickyhttp") {
	config.stickyhttp (str2bool(parts[2], "stickyhttp"));
	answer_status();
	return;
    }

    // /server/replacehostheader/BOOLEAN
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "replacehostheader") {
	config.replacehostheader (str2bool(parts[2], "replacehostheader"));
	answer_status();
	return;
    }

    // /server/newheader/NEWHEADER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "newheader") {
	config.addserverheader(parts[2]);
	answer_status();
	return;
    }

    // /server/changeheader/NR
    // /server/changeheader/NR/VALUE
    if (parts.size() == 4 &&
	parts[0] == "server" && parts[1] == "changeheader") {
	unsigned ind = headerindex(parts[2]);
	if (parts[3] == "")
	    config.removeserverheader(ind);
	else
	    config.changeserverheader(ind, parts[3]);
	answer_status();
	return;
    }

    // /server/verbose/BOOLEAN
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "verbose") {
	config.verbose(str2bool(parts[2], "verbose"));
	answer_status();
	return;
    }

    // /server/debug/VERBOSE
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "debug") {
	config.debug(str2bool(parts[2], "debug"));
	answer_status();
	return;
    }

    // /server/logtrafficdir
    // /server/logtrafficdir/VALUE
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "logtrafficdir") {
	config.dumpdir(parts[2]);
	answer_status();
	return;
    }

    // /server/clientreadtimeout
    // /server/clientreadtimeout/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "clientreadtimeout") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "client read timeout");
	config.client_read_timeout(num);
	answer_status();
	return;
    }

    // /server/clientwritetimeout
    // /server/clientwritetimeout/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "clientwritetimeout") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "client write timeout");
	config.client_write_timeout(num);
	answer_status();
	return;
    }

    // /server/backendreadtimeout
    // /server/backendreadtimeout/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "backendreadtimeout") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "back end read timeout");
	config.backend_read_timeout(num);
	answer_status();
	return;
    }

    // /server/backendwritetimeout
    // /server/backendwritetimeout/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "backendwritetimeout") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "back end write timeout");
	config.backend_write_timeout(num);
	answer_status();
	return;
    }

    // /server/dnscachetimeout
    // /server/dnscachetimeout/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "dnscachetimeout") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "DNS cache timeout");
	config.dnscachetimeout(num);
	answer_status();
	return;
    }

    // /server/wakeupinterval
    // /server/wakeupinterval/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "wakeupinterval") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "wakeup interval");
	if (num)
	    config.checkupsec(0);
	config.wakeupsec(num);
	answer_status();
	return;
    }

    // /server/checkupinterval
    // /server/checkupinterval/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "checkupinterval") {
	unsigned num = 0;
	if (parts[2] != "")
	    num = str2uns (parts[2], "checkup interval");
	if (num)
	    config.wakeupsec(0);
	config.checkupsec(num);
	answer_status();
	return;
    }

    // /server/timeinterval/SECS
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "timeinterval") {
	unsigned num = str2uns(parts[2], "time interval");
	if (num < 1)
	    throw Error("Time interval may not be less than 1");
	config.connrate_time(num);
	answer_status();
	return;
    }

    // /server/hardmaxconnrate/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "hardmaxconnrate") {
	config.hardmaxconnrate(str2uns(parts[2], "hard maxconnrate"));
	answer_status();
	return;
    }

    // /server/softmaxconnrate/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "softmaxconnrate") {
	config.softmaxconnrate(str2uns(parts[2], "soft maxconnrate"));
	answer_status();
	return;
    }

    // /server/defertime/NUMBER
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "defertime") {
	unsigned num = str2uns(parts[2], "defer time");
	if (num < 1)
	    throw Error("Defer time may not be less than 1");
	config.defertime(num);
	answer_status();
	return;
    }

    // /server/closesocketsfast/BOOL
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "closesocketsfast") {
	config.fastclose(str2bool(parts[2], "close sockets fast"));
	answer_status();
	return;
    }

    // /server/addallowfrom/ADDRESS
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "addallowfrom") {
	config.addallow(parts[2]);
	answer_status();
	return;
    }

    // /server/allowfrom/NR
    // /server/allowfrom/NR/ADDRESS
    if (parts.size() == 4 &&
	parts[0] == "server" && parts[1] == "allowfrom") {
	unsigned ind = str2uns(parts[2], "allowfrom index");
	if (parts[3] != "")
	    config.changeallow(parts[3], ind);
	else
	    config.deleteallow(ind);
	answer_status();
	return;
    }

    // /server/adddenyfrom/ADDRESS
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "adddenyfrom") {
	config.adddeny(parts[2]);
	answer_status();
	return;
    }

    // /server/denyfrom/NR
    // /server/denyfrom/NR/ADDRESS
    if (parts.size() == 4 &&
	parts[0] == "server" && parts[1] == "denyfrom") {
	unsigned ind = str2uns(parts[2], "denyfrom index");
	if (parts[3] != "")
	    config.changedeny(parts[3], ind);
	else
	    config.deletedeny(ind);
	answer_status();
	return;
    }

    // /server/hardmaxconnexcess/
    // /server/hardmaxconnexcess/PROGRAM
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "hardmaxconnexcess") {
	config.hardmaxconnexcess(parts[2]);
	answer_status();
	return;
    }

    // /server/softmaxconnexcess/
    // /server/softmaxconnexcess/PROGRAM
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "softmaxconnexcess") {
	config.softmaxconnexcess(parts[2]);
	answer_status();
	return;
    }

    // /server/onstart/
    // /server/onstart/PROGRAM
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "onstart") {
	config.onstart(parts[2]);
	answer_status();
	return;
    }

    // /server/onend/
    // /server/onend/PROGRAM
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "onend") {
	config.onend(parts[2]);
	answer_status();
	return;
    }

    // /server/onfail/
    // /server/onfail/PROGRAM
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "onfail") {
	config.onfail(parts[2]);
	answer_status();
	return;
    }

    // /server/addbackend/IP:PORT
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "addbackend") {
	vector<string> address = str2parts(parts[2], ':');
	if (address.size() != 2)
	    throw Error("When adding back ends, the address must be IP:PORT");
	Backend b;
	b.server(address[0]);
	b.port(str2uns(address[1], "back end port"));
	balancer.addbackend(b, false, false, false);
	answer_status();
	return;
    }

    // /server/deletebackend/NR
    if (parts.size() == 3 &&
	parts[0] == "server" && parts[1] == "deletebackend") {
	balancer.deletebackend(backendindex(parts[2]));
	answer_status();
	return;
    }

    // /server/type/http, /server/type/tcp
    if (parts.size() == 3 && parts[0] == "server" && parts[1] == "type") {
	config.stype(parts[2]);
	answer_status();
	return;
    }

    // /backend/NR/weight/NUMBER
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "weight") {
	unsigned ind = backendindex(parts[1]);
	unsigned num = str2uns (parts[3], "back end weight");
	if (num < 1)
	    throw Error("Weight may not be less than 1");
	balancer.backend(ind).weight(num);
	answer_status();
	return;
    }

    // /backend/NR/maxconnections/NUMBER
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "maxconnections") {
	unsigned ind = backendindex(parts[1]);
	unsigned num = str2uns (parts[3], "back end maxconnections");
	balancer.backend(ind).maxconn(num);
	answer_status();
	return;
    }

    // /backend/NR/loadavg/FLOAT
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "loadavg") {
	unsigned ind = backendindex(parts[1]);
	double fnum = str2dbl (parts[3], "back end loadavg");
	balancer.backend(ind).loadavg(fnum);
	answer_status();
	return;
    }

    // /backend/NR/hostmatch/EXPRESSION
    // /backend/NR/hostmatch
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "hostmatch") {
	unsigned ind = backendindex(parts[1]);
	balancer.backend(ind).hostmatch(parts[3]);
	answer_status();
	return;
    }

    // /backend/NR/urlmatch/EXPRESSION
    // /backend/NR/urlmatch
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "urlmatch") {
	unsigned ind = backendindex(parts[1]);
	balancer.backend(ind).urlmatch(parts[3]);
	answer_status();
	return;
    }

    // /backend/NR/up/BOOL
    if (parts.size() == 4 && parts[0] == "backend" && parts[2] == "up") {
	unsigned ind = backendindex(parts[1]);
	balancer.backend(ind).up(str2bool(parts[3], "up"));
	answer_status();
	return;
    }

    // /backend/NR/backendcheck/
    // /backend/NR/backendcheck/VALUE
    if (parts.size() == 4 &&
	parts[0] == "backend" && parts[2] == "backendcheck") {
	unsigned ind = backendindex(parts[1]);
	BackendCheck check;
	if (parts[3] != "")
	    check.parse(parts[3]);
	balancer.backend(ind).backendcheck(check);
	answer_status();
	return;
    }

    // /backend/NR/stopconnections
    if (parts.size() == 3 &&
	parts[0] == "backend" && parts[2] == "stopconnections") {
	unsigned ind = backendindex(parts[1]);
	bool done = false;
	while (!done) {
	    done = true;
	    for (Threadmap::iterator it = Threadlist::map().begin();
		 it != Threadlist::map().end();
		 it++) {
		pthread_t thread_id = (*it).first;
		Threadinfo thread_info = (*it).second;
		if (thread_info.backend() == (int)ind) {
		    stop_backend_thread(thread_id);
		    done = false;
		    break;
		}
	    }
	}
	answer_status();
	return;
    }

    // /thread/kill/VALUE
    if (parts.size() == 3 && parts[0] == "thread" && parts[1] == "kill") {
	pthread_t id = str2threadid(parts[2], "thread id");
	stop_backend_thread(id);
	answer_status();
	return;
    }

    throw Error("No action for URI '/" + uri + "'");
}
