// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WRESOURCE_H_
#define WRESOURCE_H_

#include <Wt/WObject>
#include <Wt/WGlobal>
#include <Wt/WSignal>

#include <iostream>

namespace boost {
  class recursive_mutex;
}

namespace Wt {

  class WebRequest;
  class WebResponse;
  class WebSession;

  namespace Http {
    class Request;
    class Response;
    class ResponseContinuation;
  }

/*! \class WResource Wt/WResource Wt/WResource
 *  \brief An object which can be rendered in the HTTP protocol.
 *
 * <h3>Usage</h3>
 *
 * Besides the main page, other objects may be rendered as additional
 * resources, for example documents or images. Classes such as WAnchor
 * or WImage can use a resource instead of a URL to provide their
 * contents. Whenever the resource has changed, you should call the
 * setChanged() method. setChanged() will make sure that the browser will
 * use a new version of the resource by generating a new URL, and emits the
 * dataChanged() signal to make those that refer to the resource aware
 * that they should update their references to the new URL.
 *
 * You can help the browser to start a suitable helper application to
 * handle the downloaded resource, or suggest to the user a suitable
 * filename for saving the resource, by setting an appropriate file
 * name using suggestFileName().
 *
 * To serve resources that you create on the fly, you need to specialize
 * this class and implement handleRequest().
 * 
 * \if cpp
 * Example for a custom resource implementation:
 * \code
 * class MyResource : public Wt::WResource
 * {
 * public:
 *   MyResource(Wt::WObject *parent = 0)
 *     : Wt::WResource(parent)
 *   {
 *     suggestFileName("data.txt");
 *   }
 *
 *   ~MyResource() {
 *     beingDeleted(); // see "Concurrency issues" below.
 *   }
 *
 * protected:
 *   void handleRequest(const Wt::Http::Request& request,
 *                      const Wt::Http::Response& response) {
 *     response.setMimeType("plain/text");
 *     response.out() << "I am a text file." << std::endl;
 *   }
 * };
 * \endcode
 * \endif
 *
 * <h3>Concurrency issues</h3>
 *
 * Because of the nature of the web, a resource may be requested one
 * time or multiple times at the discretion of the browser, and
 * therefore your resource should in general not have any side-effects
 * except for what is needed to render its own contents. Unlike event
 * notifications to a %Wt application, resource requests are not
 * serialized, but are handled concurrently. You need to grab the
 * application lock if you want to access or modify other widget state
 * from within the resource. When deleting a resource, any pending
 * request is cancelled first. For this mechanism to work you need to
 * specialize the destructor and call beingDeleted(). This method may
 * safely be called multiple times (i.e. from within each destructor
 * in the hierachy).
 *
 * <h3>Continuations for asynchronous I/O</h3>
 *
 * With respect to I/O, the current strategy is to cache the whole
 * response first in a buffer and use async I/O to push the data to
 * the client, in order to free the thread while waiting for I/O on a
 * possibly slow link. You do not necessarily have to provide all
 * output at once, instead you can obtain a Http::ResponseContinuation
 * object for a response, construct the response piecewise. A new
 * request() will be made to continue the response.
 *
 * \if cpp
 * Example for a custom resource implementation using continuations:
 * \code
 * class MyResource : public Wt::WResource
 * {
 * public:
 *   MyResource(int iterations, Wt::WObject *parent = 0)
 *     : Wt::WResource(parent)
 *     : iterations_(iterations)
 *   {
 *     suggestFileName("data.txt");
 *   }
 *
 *   ~MyResource() {
 *     beingDeleted();
 *   }
 *
 * protected:
 *   void handleRequest(const Wt::Http::Request& request,
 *                      const Wt::Http::Response& response) {
 *     // see if this request is for a continuation:
 *     Wt::Http::ResponseContinuation *continuation = request.continuation();
 *
 *     // calculate the current start
 *     int iteration = continuation ? boost::any_cast<int>(continuation->data()) : 0;
 *     if (iteration == 0)
 *       response.setMimeType("plain/text");
 *
 *     int last = std::min(iterations_, iteration + 100);
 *     for (int i = iteration; i < last; ++i)
 *       response.out() << "Data item " << i << std::endl;
 *
 *     // if we have not yet finished, create a continuation to serve more
 *     if (last < iterations_)
 *       continuation = response.createContinuation();
 *       // remember what to do next
 *       continuation->setData(last);
 *     }
 *   }
 *
 * private:
 *   int iterations_;
 * };
 * \endcode
 * \endif
 *
 * \sa WAnchor, WImage
 */
class WT_API WResource : public WObject
{
public:
  /*! \brief Creates a new resource.
   */
  WResource(WObject* parent = 0);

  /*! \brief Destroys the resource.
   *
   * When specializing a resource, you MUST call beingDeleted() from
   * within the specialized destructor, in order to stop any further
   * requests to the resource.
   */
  ~WResource();

  /*! \brief Suggests a filename to the user for the data streamed by this
   *         resource.
   *
   * For resources, intended to be downloaded by the user, suggest a
   * name used for saving. The filename extension may also help the
   * browser to identify the correct program for opening the resource.
   */
  void suggestFileName(const std::string& name);

  /*! \brief Returns the suggested file name.
   *
   * \sa suggestFileName();
   */
  const std::string& suggestedFileName() const { return suggestedFileName_; }

  /*! \brief Generates a new URL for this resource and emits the changed signal
   *
   * This does not work when the resource is deployed at an internal path using
   * setInternalPath().
   */
  void setChanged();

  /*! \brief Sets an internal path for this resource.
   *
   * Using this method you can deploy the resource at a fixed path. Unless
   * you deploy using cookies for session tracking (not recommended), a
   * session identifier will be appended to the path.
   *
   * You should use internal paths that are different from internal paths
   * handled by your application (WApplication::setInternalPath()), if you
   * do not want a conflict between these two when the browser does not use
   * AJAX (and thus url fragments for its internal paths).
   *
   * The default value is empty. By default the URL for a resource is
   * unspecified and a URL will be generated by the library. 
   *
   * The internal path for a static resource is the deployment path
   * specified using WServer::addResource().
   */
  void setInternalPath(const std::string& path);

  /*! \brief Returns the internal path.
   *
   * \sa setInternalPath().
   */
  std::string internalPath() const { return internalPath_; }

  /*! \brief Generates an URL for this resource.
   *
   * Generates a new url that refers to this resource. The url is
   * unique to assure that it is not cached by the web browser, and
   * can thus be used to refer to a new "version" of the resource,
   * which can be indicated by emitting the dataChanged() signal.
   *
   * The old urls are not invalidated by calling this method.
   */
  const std::string &generateUrl();

  /*! \brief Returns the current URL for this resource.
   *
   * Returns the url that refers to this resource.
   */
  const std::string &url() const;

  /*! \brief Signal emitted when the data presented in this resource
   *         has changed.
   *
   * Widgets that reference the resource (such as anchors and images) will
   * make sure the new data is rendered.
   *
   * It is better to call setChanged() than to emit this signal. setChanged
   * generates a new URL for this resource to avoid caching problems and then
   * emits this signal.
   */
  Signal<void>& dataChanged() { return dataChanged_; }

  /*! \brief Stream the resource to a stream.
   *
   * This is a convenience method to serialize to a stream (for
   * example a file stream).
   */
  void write(WT_BOSTREAM& out,
	     const Http::ParameterMap& parameters = Http::ParameterMap(),
	     const Http::UploadedFileMap& files = Http::UploadedFileMap());

protected:
  /*! \brief Prepares the resource for deletion.
   *
   * When specializing a resource, you MUST call beingDeleted() from
   * within the specialized destructor, in order to stop any further
   * requests to the resource.
   */
  void beingDeleted();

  /*! \brief Handles a request.
   *
   * Reimplement this method so that a proper response is generated
   * for the given request. From the \p request object you can
   * access request parameters and whether the request is a
   * continuation request. In the \p response object, you should
   * set the mime type and stream the output data.
   *
   * A request may also concern a continuation, indicated in
   * Http::Request::continuation(), in which case the next part for a
   * previously created continuation should be served.
   *
   * While handling a request, which may happen at any time together
   * with event handling, the library makes sure that the resource is
   * not being concurrently deleted, but multiple requests may happend
   * simultaneously for a single resource.
   */
  virtual void handleRequest(const Http::Request& request,
			     Http::Response& response) = 0;

private:
#ifndef WT_TARGET_JAVA
  boost::shared_ptr<boost::recursive_mutex> mutex_;
#endif // WT_TARGET_JAVA

  Signal<void> dataChanged_;

  bool        beingDeleted_;

  std::vector<Http::ResponseContinuation *> continuations_;

  void doContinue(Http::ResponseContinuation *continuation);
  void handle(WebRequest *webRequest, WebResponse *webResponse,
	      Http::ResponseContinuation *continuation = 0);

  std::string suggestedFileName_;
  std::string currentUrl_;
  std::string internalPath_;

  friend class Http::ResponseContinuation;
  friend class WebSession;
  friend class WebController;
  friend class Configuration;
};

}

#endif // WRESOURCE_H_
