/*
 * Message.hpp
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#ifndef CORE_HTTP_MESSAGE_HPP
#define CORE_HTTP_MESSAGE_HPP

#include <string>
#include <vector>
#include <algorithm>
#include <stdint.h>

#include <boost/bind/bind.hpp>
#include <boost/utility.hpp>

using namespace boost::placeholders;

namespace RSTUDIO_BOOST_NAMESPACE {
namespace asio {
   class const_buffer;
}
}

#include "Header.hpp"

namespace rstudio {
namespace core {

class Error;
class FilePath;
   
namespace http {

// encodings
extern const char * const kBrotliEncoding;
extern const char * const kGzipEncoding;
extern const char * const kDeflateEncoding;
extern const char * const kTransferEncoding;
extern const char * const kChunkedTransferEncoding;

class Response;
   
class Message : boost::noncopyable
{
public:
   Message() : httpVersionMajor_(1), httpVersionMinor_(1) {}
   virtual ~Message() {}
   // COPYING: boost::noncopyable

public:
   int httpVersionMajor() const { return httpVersionMajor_; } 
   int httpVersionMinor() const { return httpVersionMinor_; }
   void setHttpVersion(int httpVersionMajor, int httpVersionMinor);
   
   bool isHttp10() const 
   { 
      return httpVersionMajor() == 1 && httpVersionMinor() == 0;
   }

   std::string contentType() const;
   void setContentType(const std::string& contentType);
   
   uintmax_t contentLength() const;
   void setContentLength(uintmax_t contentLength);
  
   bool containsHeader(const std::string& name) const;
   std::string headerValue(const std::string& name) const;

   // add a header to the message
   void addHeader(const Header& header);
   void addHeader(const std::string& name, const std::string& value);
   void addHeaders(const std::vector<Header>& headers);
   
   // replace the existing value of a header (won't ever insert a new header)
   void replaceHeader(const std::string& name, const std::string& value);

   // set the value of a header (replace existing, or add new if necessary
   void setHeaderLine(const std::string& line);
   void setHeader(const Header& header);
   void setHeader(const std::string& name, const std::string& value);
   void setHeader(const std::string& name, int value);
   void setHeader(const std::string& name, uintmax_t value);

   void removeHeader(const std::string& name);

   const Headers& headers() const  { return headers_; }
   
   const std::string& body() const { return body_; }
   
   void reset();
   
   std::vector<boost::asio::const_buffer> toBuffers(
                              const Header& overrideHeader = Header()) const;

   std::vector<boost::asio::const_buffer> headerBuffers(
                              const Header& overrideHeader = Header()) const;
   
protected:
   // body_ is protected so that sub-classes set it directly (facilitating the 
   // RVO for potentially large buffers). note this means that you MUST always
   // remember to call setContentLength after setting the body!
   std::string body_;
   
   void appendSpaceBuffer(
         std::vector<boost::asio::const_buffer>& buffers) const;
   
   void appendHttpVersionBuffers(
         std::vector<boost::asio::const_buffer>& buffers) const;

   void assign(const Message& message, const Headers& extraHeaders)
   {
      body_ = message.body_;
      httpVersionMajor_ = message.httpVersionMajor_;
      httpVersionMinor_ = message.httpVersionMinor_;
      headers_ = message.headers_;
      overrideHeader_ = message.overrideHeader_;
      httpVersion_ = message.httpVersion_;

      std::for_each(extraHeaders.begin(), extraHeaders.end(),
                    boost::bind(&Message::setExtraHeader, this, _1));
   }

private:
   
   virtual void appendFirstLineBuffers(
         std::vector<boost::asio::const_buffer>& buffers) const = 0;

   virtual void resetMembers() = 0;

   void setExtraHeader(const Header& header)
   {
      // multiple Set-Cookie directives are allowed on the message
      if (header.name == "Set-Cookie")
         addHeader(header);
      else
         setHeader(header);
   }
   
private:

   // IMPORTANT NOTE: when adding data members be sure to update
   // the implementation of the assign method!!!!!


   int httpVersionMajor_;
   int httpVersionMinor_;
   std::vector<Header> headers_;
   
   // storage for override header (used by toBuffers to override a header
   // when asking for the message bytes)
   mutable Header overrideHeader_;
   
   // string storage for integer members (need for to_buffers)
   mutable std::string httpVersion_;

   // grant friendship to subclasses and parsers so they can 
   // directly manipulate fields
   friend class Response; // done to ensure body_ can be assigned to directly
                          // with no intermediate std::string copies made
   friend class RequestParser;
   friend class ResponseParser;
};

std::ostream& operator << (std::ostream& stream, const Message& m);

} // namespace http
} // namespace core 
} // namespace rstudio

#endif // CORE_HTTP_MESSAGE_HPP
