/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2014-2017 Phusion Holding B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Phusion Holding B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#ifndef _PASSENGER_APP_RESPONSE_H_
#define _PASSENGER_APP_RESPONSE_H_

#include <psg_sysqueue.h>
#include <boost/cstdint.hpp>
#include <boost/atomic.hpp>
#include <sys/uio.h>
#include <ServerKit/Hooks.h>
#include <ServerKit/Client.h>
#include <ServerKit/HeaderTable.h>
#include <ServerKit/HttpHeaderParserState.h>
#include <ServerKit/HttpChunkedBodyParserState.h>
#include <MemoryKit/palloc.h>
#include <DataStructures/LString.h>

namespace Passenger {
namespace Core {


class HttpHeaderParser;

class AppResponse {
public:
	enum HttpState {
		/** The headers are still being parsed. */
		PARSING_HEADERS,
		/** Internal state used by the parser. Users should never see this state. */
		PARSED_HEADERS,
		/** The headers have been parsed, and there is no body. */
		COMPLETE,
		/** The headers have been parsed, and we are now receiving/parsing the body,
		 * whose length is specified by Content-Length. */
		PARSING_BODY_WITH_LENGTH,
		/** The headers have been parsed, and we are now receiving/parsing the body,
		 * which has the chunked transfer-encoding. */
		PARSING_CHUNKED_BODY,
		/** The headers have been parsed, and we are now receiving/parsing the body,
		 * which ends when EOF is encountered on the app socket. */
		PARSING_BODY_UNTIL_EOF,
		/** The headers have been parsed, and the connection has been upgraded. */
		UPGRADED,
		/** A 100-Continue status line has been encountered. */
		ONEHUNDRED_CONTINUE,
		/** An error occurred. */
		ERROR
	};

	// Enum values are deliberately chosen so that hasRequestBody() can be branchless.
	enum BodyType {
		/** The message has no body. */
		RBT_NO_BODY = 0,
		/** The connection has been upgraded. */
		RBT_UPGRADE = 1,
		/** The message body's size is determined by the Content-Length header. */
		RBT_CONTENT_LENGTH = 2,
		/** The message body's size is determined by the chunked Transfer-Encoding. */
		RBT_CHUNKED = 4,
		/** The message body's size is equal to the stream's size. */
		RBT_UNTIL_EOF = 8
	};

	boost::uint8_t httpMajor;
	boost::uint8_t httpMinor;
	HttpState httpState: 5;
	bool wantKeepAlive: 1;
	bool oneHundredContinueSent: 1;
	BodyType bodyType;

	boost::uint16_t statusCode;

	union {
		// If httpState == PARSING_HEADERS
		ServerKit::HttpHeaderParserState *headerParser;
		// If httpState == PARSING_CHUNKED_BODY
		ServerKit::HttpChunkedBodyParserState chunkedBodyParser;
	} parserState;
	ServerKit::HeaderTable headers;
	ServerKit::HeaderTable secureHeaders;

	union {
		/** Length of the message body. Only use when httpState != ERROR. */
		union {
			// If bodyType == RBT_CONTENT_LENGTH. Guaranteed to be > 0.
			boost::uint64_t contentLength;
			// If bodyType == RBT_CHUNKED
			bool endChunkReached;
			// If bodyType == PARSING_BODY_UNTIL_EOF
			bool endReached;
		} bodyInfo;

		/** If a request parsing error occurred, the error code is stored here.
		 * Only use if httpState == ERROR.
		 */
		int parseError;
	} aux;
	boost::uint64_t bodyAlreadyRead;

	LString *date;
	LString *setCookie;
	LString *cacheControl;
	LString *expiresHeader;
	LString *lastModifiedHeader;

	/* If the response is eligible for turbocaching, then the buffers
	 * that contain the part of the response that can be cached, will be
	 * stored here.
	 */
	struct iovec *headerCacheBuffers;
	unsigned int nHeaderCacheBuffers;

	/* If the response is eligible for turbocaching, then all response mbufs
	 * will be stored here, so that we can store it in the response cache
	 * at the end of the response.
	 */
	LString bodyCacheBuffer;


	AppResponse()
		: headers(16),
		  secureHeaders(0),
		  bodyAlreadyRead(0)
	{
		parserState.headerParser = NULL;
		aux.bodyInfo.contentLength = 0; // Sets the entire union to 0.
	}

	const char *getHttpStateString() const {
		switch (httpState) {
		case PARSING_HEADERS:
			return "PARSING_HEADERS";
		case PARSED_HEADERS:
			return "PARSED_HEADERS";
		case COMPLETE:
			return "COMPLETE";
		case PARSING_BODY_WITH_LENGTH:
			return "PARSING_BODY_WITH_LENGTH";
		case PARSING_CHUNKED_BODY:
			return "PARSING_CHUNKED_BODY";
		case PARSING_BODY_UNTIL_EOF:
			return "PARSING_BODY_UNTIL_EOF";
		case UPGRADED:
			return "UPGRADED";
		case ONEHUNDRED_CONTINUE:
			return "ONEHUNDRED_CONTINUE";
		case ERROR:
			return "ERROR";
		default:
			return "UNKNOWN";
		}
	}

	const char *getBodyTypeString() const {
		switch (bodyType) {
		case RBT_NO_BODY:
			return "NO_BODY";
		case RBT_UPGRADE:
			return "UPGRADE";
		case RBT_CONTENT_LENGTH:
			return "CONTENT_LENGTH";
		case RBT_UNTIL_EOF:
			return "RBT_UNTIL_EOF";
		case RBT_CHUNKED:
			return "CHUNKED";
		default:
			return "UNKNOWN";
		}
	}

	bool bodyFullyRead() const {
		switch (bodyType) {
		case RBT_NO_BODY:
			return true;
		case RBT_UPGRADE:
			return false;
		case RBT_CONTENT_LENGTH:
			return bodyAlreadyRead >= aux.bodyInfo.contentLength;
		case RBT_CHUNKED:
			return aux.bodyInfo.endChunkReached;
		case RBT_UNTIL_EOF:
			return aux.bodyInfo.endReached;
		default:
			return false;
		}
	}

	bool hasBody() const {
		return bodyType & (RBT_CONTENT_LENGTH | RBT_CHUNKED | RBT_UNTIL_EOF);
	}

	bool upgraded() const {
		return bodyType == RBT_UPGRADE;
	}

	bool begun() const {
		return (int) httpState >= COMPLETE;
	}

	bool canKeepAlive() const {
		return wantKeepAlive && bodyFullyRead();
	}
};


} // namespace Core
} // namespace Passenger

#endif /* _PASSENGER_APP_RESPONSE_H_ */
