//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002 Vincent Richard <vincent@vmime.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 3 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library.  Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//

#include "vmime/config.hpp"


#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP


#include "vmime/net/smtp/SMTPResponse.hpp"

#include "vmime/platform.hpp"
#include "vmime/utility/stringUtils.hpp"

#include "vmime/net/socket.hpp"
#include "vmime/net/timeoutHandler.hpp"
#include "vmime/net/tracer.hpp"

#include <cctype>


namespace vmime {
namespace net {
namespace smtp {


SMTPResponse::SMTPResponse(
	const shared_ptr <tracer>& tr,
	const shared_ptr <socket>& sok,
	const shared_ptr <timeoutHandler>& toh,
	const state& st
)
	: m_socket(sok),
	  m_timeoutHandler(toh),
	  m_tracer(tr),
	  m_responseBuffer(st.responseBuffer),
	  m_responseContinues(false) {

}


SMTPResponse::SMTPResponse(const SMTPResponse&)
	: vmime::object() {

	// Not used
}


int SMTPResponse::getCode() const {

	const int firstCode = m_lines[0].getCode();

	for (unsigned int i = 1 ; i < m_lines.size() ; ++i) {

		// All response codes returned must be equal
		// or else this in an error...
		if (m_lines[i].getCode() != firstCode) {
			return 0;
		}
	}

	return firstCode;
}


const SMTPResponse::enhancedStatusCode SMTPResponse::getEnhancedCode() const {

	return m_lines[m_lines.size() - 1].getEnhancedCode();
}


const string SMTPResponse::getText() const {

	string text = m_lines[0].getText();

	for (unsigned int i = 1 ; i < m_lines.size() ; ++i) {

		text += '\n';
		text += m_lines[i].getText();
	}

	return text;
}


// static
shared_ptr <SMTPResponse> SMTPResponse::readResponse(
	const shared_ptr <tracer>& tr,
	const shared_ptr <socket>& sok,
	const shared_ptr <timeoutHandler>& toh,
	const state& st
) {

	shared_ptr <SMTPResponse> resp =
		shared_ptr <SMTPResponse>(new SMTPResponse(tr, sok, toh, st));

	resp->readResponse();

	return resp;
}


void SMTPResponse::readResponse() {

	responseLine line = getNextResponse();
	m_lines.push_back(line);

	while (m_responseContinues) {
		line = getNextResponse();
		m_lines.push_back(line);
	}
}


const string SMTPResponse::readResponseLine() {

	string currentBuffer = m_responseBuffer;

	if (m_timeoutHandler) {
		m_timeoutHandler->resetTimeOut();
	}

	while (true) {

		// Get a line from the response buffer
		const size_t lineEnd = currentBuffer.find_first_of('\n');

		if (lineEnd != string::npos) {

			size_t actualLineEnd = lineEnd;

			if (actualLineEnd != 0 && currentBuffer[actualLineEnd - 1] == '\r') {  // CRLF case
				actualLineEnd--;
			}

			const string line(currentBuffer.begin(), currentBuffer.begin() + actualLineEnd);

			currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1);
			m_responseBuffer = currentBuffer;

			if (m_tracer) {
				m_tracer->traceReceive(line);
			}

			return line;
		}

		// Check whether the time-out delay is elapsed
		if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) {

			if (!m_timeoutHandler->handleTimeOut()) {
				throw exceptions::operation_timed_out();
			}

			m_timeoutHandler->resetTimeOut();
		}

		// Receive data from the socket
		string receiveBuffer;
		m_socket->receive(receiveBuffer);

		if (receiveBuffer.empty()) {   // buffer is empty
			m_socket->waitForRead();
			continue;
		}

		currentBuffer += receiveBuffer;
	}
}


const SMTPResponse::responseLine SMTPResponse::getNextResponse() {

	string line = readResponseLine();

	const int code = extractResponseCode(line);
	string text;

	m_responseContinues = (line.length() >= 4 && line[3] == '-');

	if (line.length() > 4) {
		text = utility::stringUtils::trim(line.substr(4));
	} else {
		text = "";
	}

	return responseLine(code, text, extractEnhancedCode(text));
}


// static
int SMTPResponse::extractResponseCode(const string& response) {

	int code = 0;

	if (response.length() >= 3) {

		code = (response[0] - '0') * 100
		     + (response[1] - '0') * 10
		     + (response[2] - '0');
	}

	return code;
}


// static
const SMTPResponse::enhancedStatusCode SMTPResponse::extractEnhancedCode(const string& responseText) {

	enhancedStatusCode enhCode;

	std::istringstream iss(responseText);
	iss.imbue(std::locale::classic());

	if (std::isdigit(iss.peek())) {

		iss >> enhCode.klass;

		if (iss.get() == '.' && std::isdigit(iss.peek())) {

			iss >> enhCode.subject;

			if (iss.get() == '.' && std::isdigit(iss.peek())) {

				iss >> enhCode.detail;
				return enhCode;
			}
		}
	}

	return enhancedStatusCode();   // no enhanced code found
}


const SMTPResponse::responseLine SMTPResponse::getLineAt(const size_t pos) const {

	return m_lines[pos];
}


size_t SMTPResponse::getLineCount() const {

	return m_lines.size();
}


const SMTPResponse::responseLine SMTPResponse::getLastLine() const {

	return m_lines[m_lines.size() - 1];
}


const SMTPResponse::state SMTPResponse::getCurrentState() const {

	state st;
	st.responseBuffer = m_responseBuffer;

	return st;
}



// SMTPResponse::responseLine

SMTPResponse::responseLine::responseLine(
	const int code,
	const string& text,
	const enhancedStatusCode& enhCode
)
	: m_code(code),
	  m_text(text),
	  m_enhCode(enhCode) {

}


SMTPResponse::responseLine::responseLine(const responseLine& other)
	: m_code(other.m_code),
	  m_text(other.m_text),
	  m_enhCode(other.m_enhCode) {

}


void SMTPResponse::responseLine::setCode(const int code) {

	m_code = code;
}


int SMTPResponse::responseLine::getCode() const {

	return m_code;
}


void SMTPResponse::responseLine::setEnhancedCode(const enhancedStatusCode& enhCode) {

	m_enhCode = enhCode;
}


const SMTPResponse::enhancedStatusCode SMTPResponse::responseLine::getEnhancedCode() const {

	return m_enhCode;
}


void SMTPResponse::responseLine::setText(const string& text) {

	m_text = text;
}


const string SMTPResponse::responseLine::getText() const {

	return m_text;
}


SMTPResponse::responseLine& SMTPResponse::responseLine::operator=(const responseLine& other) {

	m_code = other.m_code;
	m_text = other.m_text;
	m_enhCode = other.m_enhCode;

	return *this;
}


// SMTPResponse::enhancedStatusCode


SMTPResponse::enhancedStatusCode::enhancedStatusCode()
	: klass(0),
	  subject(0),
	  detail(0) {

}


SMTPResponse::enhancedStatusCode::enhancedStatusCode(const enhancedStatusCode& enhCode)
	: klass(enhCode.klass),
	  subject(enhCode.subject),
	  detail(enhCode.detail) {

}


SMTPResponse::enhancedStatusCode& SMTPResponse::enhancedStatusCode::operator=(const enhancedStatusCode& other) {

	klass = other.klass;
	subject = other.subject;
	detail = other.detail;

	return *this;
}


std::ostream& operator<<(std::ostream& os, const SMTPResponse::enhancedStatusCode& code) {

	os << code.klass << '.' << code.subject << '.' << code.detail;
	return os;
}


} // smtp
} // net
} // vmime


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP

