#include "ace/INet/HTTP_SessionBase.h"

#if !defined (__ACE_INLINE__)
#include "ace/INet/HTTP_SessionBase.inl"
#endif

#include "ace/INet/INet_Log.h"
#include "ace/INet/HTTP_StreamPolicy.h"
#include "ace/INet/String_IOStream.h"
#include "ace/INet/IOS_util.h"
#include "ace/INet/HTTP_URL.h"
#include "ace/INET_Addr.h"
#include "ace/String_Base.h"
#include <istream>
#include <ostream>

ACE_BEGIN_VERSIONED_NAMESPACE_DECL

namespace ACE
{
  namespace HTTP
  {

    SessionBase::SessionBase (u_short port, bool keep_alive)
      : port_ (port),
        reactive_ (false),
        in_stream_ (0),
        out_stream_ (0),
        http_timeout_ (DEFAULT_TIMEOUT),
        keep_alive_timeout_ (DEFAULT_KEEP_ALIVE_TIMEOUT),
        reconnect_timer_ (DEFAULT_KEEP_ALIVE_TIMEOUT),
        reconnect_countdown_ (&reconnect_timer_),
        keep_alive_ (keep_alive),
        needs_reconnect_ (false),
        cannot_reconnect_ (false),
        expects_response_body_ (false)
      {
        INET_TRACE ("ACE_HTTP_SessionBase - ctor");
      }

    SessionBase::SessionBase (u_short port,
                              const ACE_Time_Value& timeout,
                              bool keep_alive,
                              const ACE_Time_Value* alive_timeout)
      : port_ (port),
        reactive_ (false),
        in_stream_ (0),
        out_stream_ (0),
        http_timeout_ (timeout),
        keep_alive_timeout_ (DEFAULT_KEEP_ALIVE_TIMEOUT),
        reconnect_timer_ (DEFAULT_KEEP_ALIVE_TIMEOUT),
        reconnect_countdown_ (&reconnect_timer_),
        keep_alive_ (keep_alive),
        needs_reconnect_ (false),
        cannot_reconnect_ (false),
        expects_response_body_ (false)
      {
        INET_TRACE ("ACE_HTTP_SessionBase - ctor");
        if (keep_alive && alive_timeout)
          {
            this->keep_alive_timeout_ = *alive_timeout;
          }
      }

    SessionBase::~SessionBase ()
      {
        INET_TRACE ("ACE_HTTP_SessionBase - dtor");
        this->close_streams ();
      }

    bool SessionBase::connect (bool use_reactor)
      {
        INET_TRACE ("ACE_HTTP_SessionBase::connect");

        this->close ();

        unsigned long f_reactor = use_reactor ? ACE_Synch_Options::USE_REACTOR : 0;
        ACE_Synch_Options sync_opt (ACE_Synch_Options::USE_TIMEOUT | f_reactor,
                                    this->http_timeout_);

        return this->connect_i (sync_opt);
      }

    std::ostream& SessionBase::send_request (Request& request)
      {
        INET_TRACE ("ACE_HTTP_SessionBase::send_request");

        if (this->in_stream_)
          {
            delete this->in_stream_;
            this->in_stream_ = 0;
          }

        bool keep_alive = this->keep_alive ();
        if ((this->is_connected () && !keep_alive) || this->reconnect_needed ())
        {
          close();
          this->needs_reconnect_ = false;
        }

        if (this->out_stream_)
          {
            delete this->out_stream_;
            this->out_stream_ = 0;
          }

        if (!this->is_connected ())
          {
            if (this->cannot_reconnect_ || !this->connect(this->reactive_))
              {
                if (!this->cannot_reconnect_)
                  INET_ERROR (1, (LM_ERROR, DLINFO
                                  ACE_TEXT ("(%d) HTTP_SessionBase::send_request - ")
                                  ACE_TEXT ("reconnect failed\n"),
                                  ACE_OS::last_error ()));
                return ACE::IOS::Null::out_stream_;
              }
          }
        if (!keep_alive)
          request.set_keep_alive (false);
        if (!request.has_host ())
          {
            if (this->port_ == URL::HTTP_PORT)
              request.set_host (this->host_);
            else
              request.set_host (this->host_, this->port_);
          }

        this->expects_response_body_ = request.get_method() != Request::HTTP_HEAD;

        if (request.has_chunked_transfer_encoding ())
        {
          request.write (this->sock_stream ());
          ChunkedTransferStreamPolicy* pol;
          ACE_NEW_RETURN (pol,
                          ChunkedTransferStreamPolicy (),
                          ACE::IOS::Null::out_stream_);
          ACE_NEW_RETURN (this->out_stream_,
                          OStream (this->sock_stream (), pol),
                          ACE::IOS::Null::out_stream_);
        }
        else if (request.get_content_length () != Header::UNKNOWN_CONTENT_LENGTH)
        {
          ACE::IOS::CString_OStream cs;
          request.write (cs);
          FixedLengthStreamPolicy* pol;
          ACE_NEW_RETURN (pol,
                          FixedLengthStreamPolicy (cs.str ().length () + request.get_content_length ()),
                          ACE::IOS::Null::out_stream_);
          ACE_NEW_RETURN (this->out_stream_,
                          OStream (this->sock_stream (), pol),
                          ACE::IOS::Null::out_stream_);
          (*this->out_stream_) << cs.str ().c_str ();
        }
        else if (request.get_method () != Request::HTTP_PUT && request.get_method() != Request::HTTP_POST)
        {
          ACE::IOS::CString_OStream cs;
          request.write (cs);
          FixedLengthStreamPolicy* pol;
          ACE_NEW_RETURN (pol,
                          FixedLengthStreamPolicy (cs.str ().length ()),
                          ACE::IOS::Null::out_stream_);
          ACE_NEW_RETURN (this->out_stream_,
                          OStream (this->sock_stream (), pol),
                          ACE::IOS::Null::out_stream_);
          (*this->out_stream_) << cs.str ().c_str ();
        }
        else
        {
          ACE_NEW_RETURN (this->out_stream_,
                          OStream (this->sock_stream ()),
                          ACE::IOS::Null::out_stream_);
          request.write (*this->out_stream_);
        }
        // reset reconnect timer
        this->reconnect_timer_ = this->keep_alive_timeout_;
        this->reconnect_countdown_.start ();

        return *this->out_stream_;
      }

    std::ostream& SessionBase::request_stream ()
      {
        if (this->out_stream_)
          return *this->out_stream_;
        else
          return ACE::IOS::Null::out_stream_;
      }

    std::ostream& SessionBase::request_stream (
        ACE::IOS::StreamInterceptor& interceptor)
      {
        if (this->out_stream_)
          {
            this->out_stream_->set_interceptor (interceptor);
            return *this->out_stream_;
          }
        else
          return ACE::IOS::Null::out_stream_;
      }

    std::istream& SessionBase::receive_response (Response& response)
      {
        INET_TRACE ("ACE_HTTP_SessionBase::receive_response");

        if (this->in_stream_)
          {
            INET_ERROR (1, (LM_ERROR, DLINFO
                            ACE_TEXT ("HTTP_Session::receive_response - ")
                            ACE_TEXT ("invalid invocation without send_request\n")));
            // receive_response called second time without
            // new send_request in between
            return ACE::IOS::Null::in_stream_;
          }

        if (this->out_stream_)
          {
            delete this->out_stream_;
            this->out_stream_ = 0;
          }

        this->sock_stream ().flush ();

        do
        {
          response.clear ();
          if (!response.read (this->sock_stream ()))
            {
              INET_ERROR (1, (LM_ERROR, DLINFO
                              ACE_TEXT ("(%d) HTTP_Session::receive_response - ")
                              ACE_TEXT ("failed to read response\n"),
                              ACE_OS::last_error ()));
              return ACE::IOS::Null::in_stream_;
            }
        }
        while (response.get_status ().get_status() == Status::HTTP_CONTINUE);

        this->needs_reconnect_ = this->keep_alive () && !response.has_keep_alive ();

        if (!this->expects_response_body_)
          {
            FixedLengthStreamPolicy* pol;
            ACE_NEW_RETURN (pol,
                            FixedLengthStreamPolicy (0),
                            ACE::IOS::Null::in_stream_);
            ACE_NEW_RETURN (this->in_stream_,
                            IStream (this->sock_stream (), pol),
                            ACE::IOS::Null::in_stream_);
          }
        else if (response.has_chunked_transfer_encoding ())
          {
            ChunkedTransferStreamPolicy* pol;
            ACE_NEW_RETURN (pol,
                            ChunkedTransferStreamPolicy (),
                            ACE::IOS::Null::in_stream_);
            ACE_NEW_RETURN (this->in_stream_,
                            IStream (this->sock_stream (), pol),
                            ACE::IOS::Null::in_stream_);
          }
        else if (response.get_content_length () != Header::UNKNOWN_CONTENT_LENGTH)
          {
            FixedLengthStreamPolicy* pol;
            ACE_NEW_RETURN (pol,
                            FixedLengthStreamPolicy (response.get_content_length ()),
                            ACE::IOS::Null::in_stream_);
            ACE_NEW_RETURN (this->in_stream_,
                            IStream (this->sock_stream (), pol),
                            ACE::IOS::Null::in_stream_);
          }
        else
          {
            ACE_NEW_RETURN (this->in_stream_,
                            IStream (this->sock_stream ()),
                            ACE::IOS::Null::in_stream_);
          }

        return *this->in_stream_;
      }

    std::istream& SessionBase::response_stream ()
      {
        if (this->in_stream_)
          return *this->in_stream_;
        else
          return ACE::IOS::Null::in_stream_;
      }

    std::istream& SessionBase::response_stream (
        ACE::IOS::StreamInterceptor& interceptor)
      {
        if (this->in_stream_)
          {
            this->in_stream_->set_interceptor (interceptor);
            return *this->in_stream_;
          }
        else
          return ACE::IOS::Null::in_stream_;
      }

    void SessionBase::close ()
      {
        INET_TRACE ("ACE_HTTP_SessionBase::close");

        this->close_streams ();

        this->close_i ();
      }

  }
}

ACE_END_VERSIONED_NAMESPACE_DECL
