/**
 *
 *  AccessLogger.h
 *
 */

#pragma once

#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/plugins/Plugin.h>
#include <trantor/utils/AsyncFileLogger.h>
#include <vector>

namespace drogon
{
namespace plugin
{
/**
 * @brief This plugin is used to print all requests to the log.
 *
 * The json configuration is as follows:
 *
 * @code
   {
      "name": "drogon::plugin::AccessLogger",
      "dependencies": [],
      "config": {
            "log_path": "./",
            "log_format": "",
            "log_file": "access.log",
            "log_size_limit": 0,
            "use_local_time": true,
            "log_index": 0,
            // "show_microseconds": true,
            // "custom_time_format": "",
            // "use_real_ip": false
      }
   }
   @endcode
 *
 * log_format: a format string for access logging, there are several
 * placeholders that represent particular data.
 *     $date: the time when the log was printed.
 *     $request_date: the time when the request was created.
 *     $request_path|$path: the path of the request.
 *     $request_query|$query: the query string of the request.
 *     $request_url|$url: the URL of the request, equals to
 *                   $request_path+"?"+$request_query.
 *     $request_version|$version: the http version string.
 *     $request: the full request line.
 *     $remote_addr: the remote address
 *     $local_addr: the local address
 *     $request_len|$body_bytes_received: the content length of the request.
 *     $method: the HTTP method of the request.
 *     $thread: the current thread number.
 *     $response_len|$body_bytes_sent: the content length of the response.
 *     $http_[header_name]: the header of the request.
 *     $cookie_[cookie_name]: the cookie of the request.
 *     $upstream_http_[header_name]: the header of the response sent to the
 *                                   client.
 *     $status_code: the status code of the response.
 *     $status: the status code and string of the response.
 *     $processing_time: request processing time in seconds with a microseconds
 *                       resolution; time elapsed between the request object was
 *                       created and response object was created.
 * @note If the format string is empty or not configured, a default value of
 * "$request_date $method $url [$body_bytes_received] ($remote_addr -
 * $local_addr) $status $body_bytes_sent $processing_time" is applied.
 *
 * log_path: Log file path, empty by default,in which case,logs are output to
 * the regular log file (or stdout based on the log configuration).
 *
 * log_file: The access log file name, 'access.log' by default. if the file name
 * does not contain a extension, the .log extension is used.
 *
 * log_size_limit: 0 bytes by default, when the log file size reaches
 * "log_size_limit", the log file is switched. Zero value means never switch
 *
 * max_files: 0 by default, when the number of old log files exceeds max_files,
 * the oldest log file will be deleted. 0 means never delete.
 *
 * log_index: The index of log output, 0 by default.
 *
 * show_microseconds: Whether print microsecond in time. True by default.
 *
 * custom_time_format: Provide a custom format for time. If not provided or
 * empty, the default format is "%Y%m%d %H:%M:%S", with microseonds followed if
 * show_microseconds is true. For detailed information about formats, please
 * refer to cpp reference about strftime().
 *
 * use_real_ip: Log the real ip of peer. This option only takes effects when
 * set to true and RealIpResolver is enabled. False by default.
 *
 * Enable the plugin by adding the configuration to the list of plugins in the
 * configuration file.
 *
 */
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
{
  public:
    AccessLogger()
    {
    }
    void initAndStart(const Json::Value &config) override;
    void shutdown() override;

  private:
    trantor::AsyncFileLogger asyncFileLogger_;
    int logIndex_{0};
    bool useLocalTime_{true};
    bool showMicroseconds_{true};
    bool useCustomTimeFormat_{false};
    std::string timeFormat_;
    static bool useRealIp_;

    using LogFunction = std::function<void(trantor::LogStream &,
                                           const drogon::HttpRequestPtr &,
                                           const drogon::HttpResponsePtr &)>;
    std::vector<LogFunction> logFunctions_;
    void logging(trantor::LogStream &stream,
                 const drogon::HttpRequestPtr &req,
                 const drogon::HttpResponsePtr &resp);
    void createLogFunctions(std::string format);
    LogFunction newLogFunction(const std::string &placeholder);
    std::map<std::string, LogFunction> logFunctionMap_;
    //$request_path
    static void outputReqPath(trantor::LogStream &,
                              const drogon::HttpRequestPtr &,
                              const drogon::HttpResponsePtr &);
    //$request_query
    static void outputReqQuery(trantor::LogStream &,
                               const drogon::HttpRequestPtr &,
                               const drogon::HttpResponsePtr &);
    //$request_url
    static void outputReqURL(trantor::LogStream &,
                             const drogon::HttpRequestPtr &,
                             const drogon::HttpResponsePtr &);
    //$version
    static void outputVersion(trantor::LogStream &,
                              const drogon::HttpRequestPtr &,
                              const drogon::HttpResponsePtr &);
    //$request
    static void outputReqLine(trantor::LogStream &,
                              const drogon::HttpRequestPtr &,
                              const drogon::HttpResponsePtr &);
    //$date
    void outputDate(trantor::LogStream &,
                    const drogon::HttpRequestPtr &,
                    const drogon::HttpResponsePtr &) const;
    //$request_date
    void outputReqDate(trantor::LogStream &,
                       const drogon::HttpRequestPtr &,
                       const drogon::HttpResponsePtr &) const;
    //$remote_addr
    static void outputRemoteAddr(trantor::LogStream &,
                                 const drogon::HttpRequestPtr &,
                                 const drogon::HttpResponsePtr &);
    //$local_addr
    static void outputLocalAddr(trantor::LogStream &,
                                const drogon::HttpRequestPtr &,
                                const drogon::HttpResponsePtr &);
    //$request_len $body_bytes_received
    static void outputReqLength(trantor::LogStream &,
                                const drogon::HttpRequestPtr &,
                                const drogon::HttpResponsePtr &);
    //$response_len $body_bytes_sent
    static void outputRespLength(trantor::LogStream &,
                                 const drogon::HttpRequestPtr &,
                                 const drogon::HttpResponsePtr &);
    //$method
    static void outputMethod(trantor::LogStream &,
                             const drogon::HttpRequestPtr &,
                             const drogon::HttpResponsePtr &);
    //$thread
    static void outputThreadNumber(trantor::LogStream &,
                                   const drogon::HttpRequestPtr &,
                                   const drogon::HttpResponsePtr &);
    //$http_[header_name]
    static void outputReqHeader(trantor::LogStream &stream,
                                const drogon::HttpRequestPtr &req,
                                const std::string &headerName);
    //$cookie_[cookie_name]
    static void outputReqCookie(trantor::LogStream &stream,
                                const drogon::HttpRequestPtr &req,
                                const std::string &cookie);
    //$upstream_http_[header_name]
    static void outputRespHeader(trantor::LogStream &stream,
                                 const drogon::HttpResponsePtr &resp,
                                 const std::string &headerName);
    //$status
    static void outputStatusString(trantor::LogStream &,
                                   const drogon::HttpRequestPtr &,
                                   const drogon::HttpResponsePtr &);
    //$status_code
    static void outputStatusCode(trantor::LogStream &,
                                 const drogon::HttpRequestPtr &,
                                 const drogon::HttpResponsePtr &);
    //$processing_time
    static void outputProcessingTime(trantor::LogStream &,
                                     const drogon::HttpRequestPtr &,
                                     const drogon::HttpResponsePtr &);
    //$upstream_http_content-type $upstream_http_content_type
    static void outputRespContentType(trantor::LogStream &,
                                      const drogon::HttpRequestPtr &,
                                      const drogon::HttpResponsePtr &);
};
}  // namespace plugin
}  // namespace drogon
