// file      : web/apache/request -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
// license   : MIT; see accompanying LICENSE file

#ifndef WEB_APACHE_REQUEST
#define WEB_APACHE_REQUEST

#include <apr_strings.h>

#include <httpd/httpd.h>
#include <httpd/http_core.h>
#include <httpd/util_script.h>

#include <ios>
#include <chrono>
#include <memory>    // unique_ptr
#include <string>
#include <cassert>
#include <istream>
#include <ostream>
#include <streambuf>
#include <stdexcept>
#include <exception>
#include <algorithm> // move

#include <web/module>
#include <web/apache/stream>

namespace web
{
  namespace apache
  {
    class request: public web::request,
                   public web::response,
                   public write_state
    {
      friend class service;

      request (request_rec* rec) noexcept: rec_ (rec) {rec_->status = HTTP_OK;}

      // Flush of buffered content.
      //
      int
      flush ();

      // Get request body data stream.
      //
      virtual std::istream&
      content ()
      {
        if (!in_)
        {
          std::unique_ptr<std::streambuf> in_buf (
            new istreambuf (rec_, *this));

          in_.reset (new std::istream (in_buf.get ()));
          in_buf_ = std::move (in_buf);
          in_->exceptions (std::ios::failbit | std::ios::badbit);

          // Save form data now otherwise will not be available to do later
          // when data read from stream.
          //
          form_data ();
        }

        return *in_;
      }

      // Get request parameters.
      //
      virtual const name_values&
      parameters ()
      {
        if (!parameters_)
        {
          parameters_.reset (new name_values ());

          try
          {
            parse_parameters (rec_->args);
            parse_parameters (form_data ()->c_str ());
          }
          catch (const std::invalid_argument& )
          {
            throw invalid_request ();
          }
        }

        return *parameters_;
      }

      // Get request cookies.
      //
      virtual const name_values&
      cookies ();

      // Get response status code.
      //
      status_code status () const noexcept {return rec_->status;}

      // Set response status code.
      //
      virtual void
      status (status_code status)
      {
        if (status != rec_->status)
        {
          // Setting status code in exception handler is a common usecase
          // where no sense to throw but still need to signal apache a
          // proper status code.
          //
          if (get_write_state () && !std::current_exception ())
          {
            throw sequence_error ("::web::apache::request::status");
          }

          rec_->status = status;
          buffer_ = true;
          out_.reset ();
          out_buf_.reset ();
          ap_set_content_type (rec_, nullptr);
        }
      }

      // Set response status code, content type and get body stream.
      //
      virtual std::ostream&
      content (status_code status,
               const std::string& type,
               bool buffer = true);

      // Add response cookie.
      //
      virtual void
      cookie (const char* name,
              const char* value,
              const std::chrono::seconds* max_age = 0,
              const char* path = 0,
              const char* domain = 0,
              bool secure = false);

    private:
      using string_ptr = std::unique_ptr<std::string>;

      // Get application/x-www-form-urlencoded form data.
      //
      const string_ptr&
      form_data ();

      void
      parse_parameters (const char* args);

      static void
      mime_url_encode (const char* v, std::ostream& o);

      static std::string
      mime_url_decode (const char* b, const char* e, bool trim = false);

      bool
      get_write_state () const noexcept {return write_state_;}

      virtual void
      set_write_state ()
      {
        if (!write_state_)
        {
          // Preparing to write a response read and discard request
          // body if any.
          //
          int r = ap_discard_request_body (rec_);

          if (r != OK)
          {
            throw invalid_request (r);
          }

          write_state_ = true;
        }
      }

    private:

      request_rec* rec_;
      bool buffer_ {true};
      bool write_state_ {false};
      std::unique_ptr<std::streambuf> out_buf_;
      std::unique_ptr<std::ostream> out_;
      std::unique_ptr<std::streambuf> in_buf_;
      std::unique_ptr<std::istream> in_;
      std::unique_ptr<name_values> parameters_;
      std::unique_ptr<name_values> cookies_;
      string_ptr form_data_;
    };
  }
}

#include <web/apache/request.ixx>

#endif // WEB_APACHE_REQUEST