/*
 * (C) Copyright 1996-2017 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */


#include "eckit/log/Log.h"
#include "eckit/parser/Tokenizer.h"
#include "eckit/web/HttpHeader.h"
#include "eckit/net/TCPSocket.h"
#include "eckit/utils/Translator.h"

//-----------------------------------------------------------------------------

namespace eckit {

//-----------------------------------------------------------------------------


// Check the <A HREF=http://www.ics.uci.edu/pub/ietf/http/rfc1945.html">HTTP/1.0</A> syntax
// Check the <A HREF=http://src.doc.ic.ac.uk/computing/internet/rfc/rfc2068.txt">HTTP/1.1</A> syntax

const std::string WWW_Authenticate = "WWW-Authenticate";
const std::string Authorization    = "Authorization";
const std::string Content_Type     = "Content-Type";
const std::string Content_Length   = "Content-Length";
const std::string Location         = "Location";
const std::string DefaultType      = "application/x-www-form-urlencoded";
const std::string Retry_After      = "Retry-After";

bool HttpHeader::compare::operator()(const std::string& a, const std::string& b) const
{
	return strcasecmp(a.c_str(), b.c_str()) < 0;
}


HttpHeader::HttpHeader():
	version_("HTTP/1.0"),
	statusCode_(HttpError::OK),
	contentLength_(0),
	received_(false)
{
	header_[Content_Type] = "text/html";
	header_["Cache-Control"] = "no-cache";
	header_["MIME-Version"] = "1.0";
}

HttpHeader& HttpHeader::operator=(std::map<std::string, std::string, std::less<std::string> >& parsed)
{
	for (std::map<std::string, std::string, std::less<std::string> >::const_iterator i = parsed.begin();
	        i != parsed.end(); ++i)
		header_[(*i).first] = (*i).second;

	Map::const_iterator j = header_.find(Content_Length);

	if (j != header_.end())
		contentLength_ = atol(((*j).second).c_str());
	else
		contentLength_ = 0;

	return *this;
}

HttpHeader::~HttpHeader()
{
}

void HttpHeader::print(std::ostream& s) const
{

	const char* CRLF = "\r\n";

	// Status line + CRLF
	s << version_ << ' ' << statusCode_ << ' ';

	if (message_.empty()) {
		switch (statusCode_) {

		case HttpError::OK:
			s << "OK";
			break;

		case HttpError::CREATED:
			s << "Created";
			break;

		case HttpError::ACCEPTED:
			s << "Accepted";
			break;

		case HttpError::NO_CONTENT:
			s << "No Content";
			break;

		case HttpError::SEE_OTHER:
			s << "See Other";
			break;

		case HttpError::NOT_FOUND:
			s << "Not Found";
			break;

		case HttpError::NOT_IMPLEMENTED:
			s << "Not Implemented";
			break;

		case HttpError::INTERNAL_SERVER_ERROR:
			s << "Internal Server Error";
			break;

		case HttpError::BAD_REQUEST:
			s << "Bad Request";
			break;

		case HttpError::UNAUTHORIZED:
			s << "Unauthorized";
			break;
		}
	}
	else {
		s << message_;
	}

	s << CRLF;


	for (Map::const_iterator i = header_.begin(); i != header_.end(); ++i) {
		s << (*i).first <<  ": " << (*i).second << CRLF;
	}

	if (!received_) {
		s << Content_Length << ": " << contentLength_ + content_.size() << CRLF;
	}

	s << CRLF;


	long len = content_.size();
	const char *p  = static_cast<const char*>(content_.data());
	while (len-- > 0)
		s.put(*p++);
}

void HttpHeader::forward(const std::string& s)
{
	header_[Location] = s;
}

void HttpHeader::retryAfter(long s)
{
	std::ostringstream oss;
	oss << s;
	header_[Retry_After] = oss.str();
	Log::debug() << "HttpHeader::retryAfter " << s << std::endl;
	Log::debug() <<  *this << std::endl;
}

void HttpHeader::length(const long l)
{
	contentLength_ = l;
}

long HttpHeader::contentLength() const
{
	return contentLength_;
}

void HttpHeader::type(const std::string& s)
{
	header_[Content_Type] = s;
}

const std::string& HttpHeader::type() const
{
	Map::const_iterator i = header_.find(Content_Type);

	if ( i != header_.end() )
		return (*i).second;

	return DefaultType;
}

void HttpHeader::status(const long code, const std::string& message)
{
	statusCode_ = code;
	message_ = message;
}

void HttpHeader::authenticate(const std::string& login)
{
	header_[WWW_Authenticate] = ("Basic realm=\"" + login + "\"");
	status(HttpError::UNAUTHORIZED);
}

void HttpHeader::dontCache()
{
}

bool HttpHeader::authenticated() const
{
	Map::const_iterator i = header_.find(Authorization);

	if (i != header_.end())
	{
		const char *s = (*i).second.c_str();

		while (*s != ' ' && *s != '\t') s++;
		while (*s == ' ' || *s == '\t') s++;

		unsigned char b64[256];
		for (int j = 0; j < 256; j++) b64[j] = 64;

		for (unsigned char c = 'A'; c <= 'Z' ; c++) b64[c] = c - 'A';
		for (unsigned char c = 'a'; c <= 'z' ; c++) b64[c] = c - 'a' + 26;
		for (unsigned char c = '0'; c <= '9' ; c++) b64[c] = c - '0' + 52;

		b64['+'] = 62;
		b64['/'] = 63;

		const unsigned char *p = (const unsigned char*)s;

		std::string q;

		int n = 2;

		while (b64[*p] < 64 && b64[*(p + 1)] < 64)
		{
			q += char((b64[p[0]] << n) | (b64[p[1]] >> (6 - n)));
			n += 2;
			if (n == 8) {
				p++;
				n = 2;
			}
			p++;
		}

		std::cout << q << std::endl;


		Tokenizer parse(":");
		std::vector<std::string> v;
		parse(q, v);
		if (v.size() == 2 && v[0] == "mars" && v[1] == "clave")
		{
			Log::info() << "client authenticated " << q << " -> " << (*i).second << std::endl;
			return true;
		}

		Log::info() << "client denied " << q << " -> " << (*i).second << std::endl;
	}

	return false;
}

void HttpHeader::content(const char* p, long len)
{
	content_.openForWrite(0);
	content_.write(p, len);
	content_.close();
}

void HttpHeader::setHeader(const std::string& k, const std::string& v)
{
	header_[k] = v;
}

const std::string& HttpHeader::getHeader(const std::string& k) const
{
	return ((HttpHeader*)this)->header_[k];
}

std::string HttpHeader::content() const {
	return content_.str();
}

//-----------------------------------------------------------------------------

static std::string nextLine(TCPSocket& socket) {
	char c;
	std::string s;

	for (;;) {
		ASSERT(socket.read(&c, 1) == 1);
		if (c == '\r') {
			ASSERT(socket.read(&c, 1) == 1);
			ASSERT(c == '\n');
			return s;
		}

		s += c;
		ASSERT(s.length() < 32768);
	}
}

HttpHeader::HttpHeader(TCPSocket& socket):
	received_(true) {
	std::string line = nextLine(socket);

	size_t i = line.find_first_of(' ');
	ASSERT(i != std::string::npos);

	version_ = line.substr(0, i);

	line.erase(0, i + 1);

	i = line.find_first_of(' ');
	ASSERT(i != std::string::npos);
	statusCode_ = Translator<std::string, long>()(line.substr(0, i));

	line.erase(0, i + 1);
	message_ = line;


	line = nextLine(socket);
	while (!line.empty()) {

		size_t i = line.find_first_of(':');
		ASSERT(i != std::string::npos);

		std::string key = line.substr(0, i);
		while (!key.empty() && key[key.length() - 1 ] == ' ') {
			key.erase(key.length() - 1, 1);
		}

		std::string value = line.substr(i + 1, line.length());
		while (!value.empty() && value[0] == ' ') {
			value.erase(0, 1);
		}

		header_[key] = value;

		line = nextLine(socket);
	}
}

void HttpHeader::checkForStatus() const {
	if (statusCode_ != HttpError::OK) {
		throw HttpError(statusCode_, message_);
	}
}


//-----------------------------------------------------------------------------

} // namespace eckit

