/**
  @file
  @author Stefan Frings
*/

#ifndef HTTPREQUEST_H
#define HTTPREQUEST_H

#include <QByteArray>
#include <QHostAddress>
#include <QTcpSocket>
#include <QMap>
#include <QMultiMap>
#include <QSettings>
#include <QTemporaryFile>
#include <QUuid>
#include "httpglobal.h"

namespace stefanfrings {

/**
  This object represents a single HTTP request. It reads the request
  from a TCP socket and provides getters for the individual parts
  of the request.
  <p>
  The following config settings are required:
  <code><pre>
  maxRequestSize=16000
  maxMultiPartSize=1000000
  </pre></code>
  <p>
  MaxRequestSize is the maximum size of a HTTP request. In case of
  multipart/form-data requests (also known as file-upload), the maximum
  size of the body must not exceed maxMultiPartSize.
*/

class DECLSPEC HttpRequest {
    Q_DISABLE_COPY(HttpRequest)
    friend class HttpSessionStore;

public:

    /** Values for getStatus() */
    enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort};

    /**
      Constructor.
      @param settings Configuration settings
    */
    HttpRequest(const QSettings* settings);

    /**
      Destructor.
    */
    virtual ~HttpRequest();

    /**
      Read the HTTP request from a socket.
      This method is called by the connection handler repeatedly
      until the status is RequestStatus::complete or RequestStatus::abort.
      @param socket Source of the data
    */
    void readFromSocket(QTcpSocket *socket);

    /**
      Get the status of this reqeust.
      @see RequestStatus
    */
    RequestStatus getStatus() const;

    /** Get the method of the HTTP request  (e.g. "GET") */
    QByteArray getMethod() const;

    /** Get the decoded path of the HTPP request (e.g. "/index.html") */
    QByteArray getPath() const;

    /** Get the raw path of the HTTP request (e.g. "/file%20with%20spaces.html") */
    const QByteArray& getRawPath() const;

    /** Get the version of the HTPP request (e.g. "HTTP/1.1") */
    QByteArray getVersion() const;

    /**
      Get the value of a HTTP request header.
      @param name Name of the header, not case-senitive.
      @return If the header occurs multiple times, only the last
      one is returned.
    */
    QByteArray getHeader(const QByteArray& name) const;

    /**
      Get the values of a HTTP request header.
      @param name Name of the header, not case-senitive.
    */
    QList<QByteArray> getHeaders(const QByteArray& name) const;

    /**
     * Get all HTTP request headers. Note that the header names
     * are returned in lower-case.
     */
    QMultiMap<QByteArray,QByteArray> getHeaderMap() const;

    /**
      Get the value of a HTTP request parameter.
      @param name Name of the parameter, case-sensitive.
      @return If the parameter occurs multiple times, only the last
      one is returned.
    */
    QByteArray getParameter(const QByteArray& name) const;

    /**
      Get the values of a HTTP request parameter.
      @param name Name of the parameter, case-sensitive.
    */
    QList<QByteArray> getParameters(const QByteArray& name) const;

    /** Get all HTTP request parameters. */
    QMultiMap<QByteArray,QByteArray> getParameterMap() const;

    /** Get the HTTP request body.  */
    QByteArray getBody() const;

    /**
      Decode an URL parameter.
      E.g. replace "%23" by '#' and replace '+' by ' '.
      @param source The url encoded strings
      @see QUrl::toPercentEncoding for the reverse direction
    */
    static QByteArray urlDecode(const QByteArray source);

    /**
      Get an uploaded file. The file is already open. It will
      be closed and deleted by the destructor of this HttpRequest
      object (after processing the request).
      <p>
      For uploaded files, the method getParameters() returns
      the original fileName as provided by the calling web browser.
    */
    QTemporaryFile* getUploadedFile(const QByteArray fieldName) const;

    /**
      Get the value of a cookie.
      @param name Name of the cookie
    */
    QByteArray getCookie(const QByteArray& name) const;

    /** Get all cookies. */
    QMap<QByteArray,QByteArray>& getCookieMap();

    /**
      Get the address of the connected client.
      Note that multiple clients may have the same IP address, if they
      share an internet connection (which is very common).
     */
    QHostAddress getPeerAddress() const;

private:

    /** Request headers */
    QMultiMap<QByteArray,QByteArray> headers;

    /** Parameters of the request */
    QMultiMap<QByteArray,QByteArray> parameters;

    /** Uploaded files of the request, key is the field name. */
    QMap<QByteArray,QTemporaryFile*> uploadedFiles;

    /** Received cookies */
    QMap<QByteArray,QByteArray> cookies;

    /** Storage for raw body data */
    QByteArray bodyData;

    /** Request method */
    QByteArray method;

    /** Request path (in raw encoded format) */
    QByteArray path;

    /** Request protocol version */
    QByteArray version;

    /**
      Status of this request. For the state engine.
      @see RequestStatus
    */
    RequestStatus status;

    /** Address of the connected peer. */
    QHostAddress peerAddress;

    /** Maximum size of requests in bytes. */
    int maxSize;

    /** Maximum allowed size of multipart forms in bytes. */
    int maxMultiPartSize;

    /** Current size */
    int currentSize;

    /** Expected size of body */
    int expectedBodySize;

    /** Name of the current header, or empty if no header is being processed */
    QByteArray currentHeader;

    /** Boundary of multipart/form-data body. Empty if there is no such header */
    QByteArray boundary;

    /** Temp file, that is used to store the multipart/form-data body */
    QTemporaryFile* tempFile;

    /** Parse the multipart body, that has been stored in the temp file. */
    void parseMultiPartFile();

    /** Sub-procedure of readFromSocket(), read the first line of a request. */
    void readRequest(QTcpSocket* socket);

    /** Sub-procedure of readFromSocket(), read header lines. */
    void readHeader(QTcpSocket* socket);

    /** Sub-procedure of readFromSocket(), read the request body. */
    void readBody(QTcpSocket* socket);

    /** Sub-procedure of readFromSocket(), extract and decode request parameters. */
    void decodeRequestParams();

    /** Sub-procedure of readFromSocket(), extract cookies from headers */
    void extractCookies();

    /** Buffer for collecting characters of request and header lines */
    QByteArray lineBuffer;

};

} // end of namespace

#endif // HTTPREQUEST_H
