// Local configuration
#include "config.h"

// Implementation
#include "Connection.hpp"

namespace UI {
namespace Util {
namespace Http {

void Connection::destroy()
{
	if (!externalStream_) delete stream_;
	delete socket_;
}

Connection::Connection(std::iostream & stream)
	:socket_(0)
	,stream_(&stream)
	,externalStream_(true)
{}

Connection::Connection(int fd, bool closeFd,
                       long int rcvToSeconds, long int rcvToMicroseconds,
                       long int sndToSeconds, long int sndToMicroseconds,
                       bool noBlock)
	:socket_(new Socket(fd, closeFd))
	,stream_(new FDTypeStream<Socket>(socket_->getFd()))
	,externalStream_(false)
{
	try
	{
		socket_->
			setRcvTimeout(rcvToSeconds, rcvToMicroseconds).
			setSndTimeout(sndToSeconds, sndToMicroseconds).
			setUnblock(noBlock);
	}
	catch (...)
	{
		destroy();
		throw;
	}
}

Connection::Connection(std::string const & host, unsigned int port,
                       long int rcvToSeconds, long int rcvToMicroseconds,
                       long int sndToSeconds, long int sndToMicroseconds,
                       bool noBlock)
	:socket_(new INetSocket(host, port))
	,stream_(new FDTypeStream<INetSocket>(socket_->getFd()))
	,externalStream_(false)
{
	try
	{
		socket_->
			setRcvTimeout(rcvToSeconds, rcvToMicroseconds).
			setSndTimeout(sndToSeconds, sndToMicroseconds).
			setUnblock(noBlock).
			connect();
	}
	catch (...)
	{
		destroy();
		throw;
	}
}

#ifndef WIN32
Connection::Connection(std::string const & path,
                       long int rcvToSeconds, long int rcvToMicroseconds,
                       long int sndToSeconds, long int sndToMicroseconds,
                       bool noBlock)
	:socket_(new UnixSocket(path))
	,stream_(new FDTypeStream<UnixSocket>(socket_->getFd()))
	,externalStream_(false)
{
	try
	{
		socket_->
			setRcvTimeout(rcvToSeconds, rcvToMicroseconds).
			setSndTimeout(sndToSeconds, sndToMicroseconds).
			setUnblock(noBlock).
			connect();
	}
	catch (...)
	{
		destroy();
		throw;
	}
}
#endif

Connection::~Connection()
{
	destroy();
}

std::string Connection::getId()     const { return socket_->getId(); }
std::string Connection::getPeerId() const { return socket_->getPeerId(); }
std::string Connection::getConnId() const { return "[" + getId() + "]<->[" + getPeerId() + "]"; }

std::iostream const & Connection::s() const { return *stream_; }
std::iostream       & Connection::s()       { return *stream_; }

// private: buf must be pre allocated to hold size bytes.
std::streamsize Connection::readBlock(char * const buf, std::streamsize const & size, bool doThrow)
{
	s().read(buf, size);
	if (doThrow && s().gcount() != size)
	{
		UI_THROW("Incomplete block read: " + tos(s().gcount()) + "/" + tos(size) + " bytes");
	}
	return s().gcount();
}

std::string Connection::readLine() { return getlineCRLF(s()); }

std::streamsize Connection::readBlock(std::string & block, std::streamsize const & size, bool doThrow)
{
	block.resize(size);
	return readBlock(&block[0], size, doThrow);
}

std::string Connection::readBlock(std::streamsize const & size)
{
	std::string block;
	block.resize(size);
	readBlock(&block[0], size);
	return block;
}

std::vector<char> Connection::readBlockVec(std::streamsize const & size)
{
	std::vector<char> block(size);
	readBlock(&block[0], size);
	return block;
}

Header & Connection::readHeader(Header & header)
{
	std::string line(readLine());
	while (line != "")
	{
		header.add(line);
		line = readLine();
	}
	return header;
}

Header Connection::readHeader()
{
	Header header;
	return readHeader(header);
}

Connection & Connection::writeLine(std::string const & line)
{
	s() << line << "\r\n";
	return *this;
}

Connection & Connection::write(std::string const & block)
{
	s().write(&block[0], block.size());
	return *this;
}

Connection & Connection::write(StatusLine const & statusLine)
{
	writeLine(statusLine.get());
	return *this;
}

Connection & Connection::write(RequestLine const & requestLine)
{
	writeLine(requestLine.get());
	return *this;
}

Connection & Connection::write(HeaderField const & oHeaderField, std::string const & prefix)
{
	writeLine(prefix + oHeaderField.get());
	return *this;
}

Connection & Connection::write(Header const & header, std::string const & prefix)
{
	for (Header::const_iterator i(header.begin()); i != header.end(); ++i)
	{
		write(*i, prefix);
	}
	return *this;
}

std::string Connection::simpleHttpRequest(std::string const & body)
{
	write(RequestLine());
	write(Header().add("Content-Length", tos(body.size())));
	writeLine();
	write(body);
	s().flush();

	StatusLine status(readLine());
	Header header(readHeader());
	if (header.exists("Content-Length"))
	{
		std::string body(readBlock(header.get<std::streamsize>("Content-Length")));
		return body;
	}
	return "";
}

}}}
