/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


#ifndef PAGESPEED_OPT_LOGGING_LOG_RECORD_H_
#define PAGESPEED_OPT_LOGGING_LOG_RECORD_H_

#include <map>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest_prod.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/image_types.pb.h"
#include "pagespeed/opt/logging/enums.pb.h"
#include "pagespeed/opt/logging/logging_proto.h"
#include "pagespeed/opt/logging/logging_proto_impl.h"

// TODO(gee):  Hmm, this sort of sucks.
// If your .cc file needs to use the types declared in logging_proto.h,
// you must also include pagespeed/opt/logging/logging_proto_impl.h
// See that header file for an explanation of why this is necessary.

namespace net_instaweb {

class AbstractMutex;
class RequestTimingInfo;

// This class is a wrapper around a protobuf used to collect logging
// information. It also provides a simple aggregation mechanism for
// collecting the ids of applied rewriters.
//
// Care and feeding of log records:
//  (1) All logging must be done through log records. No class should
//      have static members of any logging proto class. Log records
//      can either create the logging protos, or will take ownership of them.
//  (2) All access and manipulation of log data must be guarded by the log
//      record's mutex. Commonly repeated logging operations should be factored
//      into functions in this class (and be so guarded therein).
//  (3) In most cases, log records should be created and owned by request
//      contexts.

// Subclasses may wrap some other type of protobuf; they must still provide
// access to a LoggingInfo instance, however.
class AbstractLogRecord  {
 public:
  // Construct a AbstractLogRecord with a new LoggingInfo proto and caller-
  // supplied mutex. This class takes ownership of the mutex.
  explicit AbstractLogRecord(AbstractMutex* mutex);
  virtual ~AbstractLogRecord();

  // For compatibility with older logging methods, returns a comma-joined string
  // concatenating the sorted coalesced rewriter ids of APPLIED_OK entries in
  // the rewriter_info array. Each id will appear once in the string if any
  // number of successful rewrites for that id have been logged.
  GoogleString AppliedRewritersString();

  // Create a new rewriter logging submessage for |rewriter_id|, returning a
  // pointer to it for later access. Note that this can return NULL if the
  // size of rewriter_info has grown too large. It is the caller's
  // responsibility to handle this safely.
  RewriterInfo* NewRewriterInfo(const char* rewriter_id);

  // Creates a new rewriter logging submessage for |rewriter_id|,
  // and sets status it.
  void SetRewriterLoggingStatus(
      const char* rewriter_id, RewriterApplication::Status status);

  // Creates a new rewriter logging submessage for |rewriter_id|,
  // sets status and the url index.
  void SetRewriterLoggingStatus(
      const char* rewriter_id, const GoogleString& url,
      RewriterApplication::Status status) {
    SetRewriterLoggingStatusHelper(rewriter_id, url, status);
  }

  // Log the HTML level status for a filter.  This should be called only once
  // per filter, at the point where it is determined the filter is either
  // active or not.
  void LogRewriterHtmlStatus(const char* rewriter_id,
                             RewriterHtmlApplication::Status status);

  // Log the status of a rewriter application on a resource.
  // TODO(gee): I'd really prefer rewriter_id was an enum.
  void LogRewriterApplicationStatus(
      const char* rewriter_id, RewriterApplication::Status status);

  // TODO(gee): Deprecate raw access to proto.
  // Return the LoggingInfo proto wrapped by this class. Calling code must
  // guard any reads and writes to this using mutex().
  virtual LoggingInfo* logging_info() = 0;

  // TODO(huibao): Rename LogImageBackgroundRewriteActivity() to make it clear
  // that it will log even when the rewriting finishes in the line-of-request.

  // Log image rewriting activity, which may not finish when the request
  // processing is done. The outcome is a new log record with request type
  // set to "BACKGROUND_REWRITE".
  void LogImageBackgroundRewriteActivity(
      RewriterApplication::Status status,
      const GoogleString& url,
      const char* id,
      int original_size,
      int optimized_size,
      bool is_recompressed,
      ImageType original_image_type,
      ImageType optimized_image_type,
      bool is_resized,
      int original_width,
      int original_height,
      bool is_resized_using_rendered_dimensions,
      int resized_width,
      int resized_height);

  // Atomically sets is_html_response in the logging proto.
  void SetIsHtml(bool is_html);

  // Updated the cohort info to set the found to true for the given
  // property.
  virtual void AddFoundPropertyToCohortInfo(
      int page_type, const GoogleString& cohort,
      const GoogleString& property) = 0;

  // Updated the cohort info to set the retrieved to true for the given
  // property.
  virtual void AddRetrievedPropertyToCohortInfo(
      int page_type, const GoogleString& cohort,
      const GoogleString& property) = 0;

  // Updates the cohort info to update the cache key state.
  virtual void SetCacheStatusForCohortInfo(
      int page_type, const GoogleString& cohort,
      bool found, int key_state) = 0;

  // Mutex-guarded log mutation convenience methods. The rule of thumb is that
  // if a single-field update to a logging proto occurs multiple times, it
  // should be factored out into a method on this class.
  void SetIsOriginalResourceCacheable(bool cacheable);

  // Log a RewriterInfo for the image rewrite filter.
  virtual void LogImageRewriteActivity(
      const char* id,
      const GoogleString& url,
      RewriterApplication::Status status,
      bool is_image_inlined,
      bool is_critical_image,
      bool is_url_rewritten,
      int size,
      bool try_low_res_src_insertion,
      bool low_res_src_inserted,
      ImageType low_res_image_type,
      int low_res_data_size) = 0;

  // TODO(gee): Change the callsites.
  void LogJsDisableFilter(const char* id, bool has_pagespeed_no_defer);

  void LogLazyloadFilter(const char* id,
                         RewriterApplication::Status status,
                         bool is_blacklisted, bool is_critical);

  // Mutex-guarded log-writing operations. Derived classes should override
  // *Impl methods. Returns false if the log write attempt failed.
  bool WriteLog();

  // Return the mutex associated with this instance. Calling code should
  // guard reads and writes of AbstractLogRecords
  AbstractMutex* mutex() { return mutex_.get(); }

  // Sets the maximum number of RewriterInfo submessages that can accumulate in
  // the LoggingInfo proto wrapped by this class.
  void SetRewriterInfoMaxSize(int x);

  // Sets whether urls should be logged. This could potentially generate a lot
  // of logs data, so this should be switched on only for debugging.
  void SetAllowLoggingUrls(bool allow_logging_urls);

  // Sets whether URL indices should be logged for every rewriter application
  // or not.
  void SetLogUrlIndices(bool log_url_indices);

  // Sets the number of critical images in HTML.
  void SetNumHtmlCriticalImages(int num_html_critical_images);

  // Sets the number of critical images in CSS.
  void SetNumCssCriticalImages(int num_css_critical_images);

  // Sets image related statistics.
  virtual void SetImageStats(int num_img_tags, int num_inlined_img_tags,
                             int num_critical_images_used) = 0;

  // Sets the number of external resources on an HTML page.
  virtual void SetResourceCounts(int num_external_css, int num_scripts) = 0;

  // Sets critical CSS related byte counts (all uncompressed).
  void SetCriticalCssInfo(int critical_inlined_bytes,
                          int original_external_bytes,
                          int overhead_bytes);

  // Log information related to the user agent and device making the request.
  virtual void LogDeviceInfo(
      int device_type,
      bool supports_image_inlining,
      bool supports_lazyload_images,
      bool supports_critical_images_beacon,
      bool supports_deferjs,
      bool supports_webp_in_place,
      bool supports_webp_rewritten_urls,
      bool supports_webplossless_alpha,
      bool is_bot) = 0;

  // Log whether the request is an XmlHttpRequest.
  void LogIsXhr(bool is_xhr);

  // Sets initial information for background rewrite log.
  virtual void SetBackgroundRewriteInfo(
    bool log_urls,
    bool log_url_indices,
    int max_rewrite_info_log_size);

  // Set timing information in the logging implementation.
  virtual void SetTimingInfo(const RequestTimingInfo& timing_info) {}

 protected:
  // Implements writing a log, base implementation is a no-op. Returns false if
  // writing failed.
  virtual bool WriteLogImpl() = 0;

  // Helper function which creates a new rewriter logging submessage for
  // |rewriter_id|, sets status and the url index. It is intended to be called
  // only inside logging code.
  RewriterInfo* SetRewriterLoggingStatusHelper(
      const char* rewriter_id, const GoogleString& url,
      RewriterApplication::Status status);

 private:
  // Called on construction.
  void InitLogging();

  void PopulateUrl(
      const GoogleString& url, RewriteResourceInfo* rewrite_resource_info);

  // Fill LoggingInfo proto with information collected from LogRewriterStatus
  // and LogRewrite.
  void PopulateRewriterStatusCounts();

  // Thus must be set. Implementation constructors must minimally default this
  // to a NullMutex.
  scoped_ptr<AbstractMutex> mutex_;

  // The maximum number of rewrite info logs stored for a single request.
  int rewriter_info_max_size_;

  // Allow urls to be logged.
  bool allow_logging_urls_;

  // Allow url indices to be logged.
  bool log_url_indices_;

  // Map which maintains the url to index for logging urls.
  StringIntMap url_index_map_;

  // Stats collected from calls to LogRewrite.
  typedef std::map<RewriterApplication::Status, int> RewriteStatusCountMap;
  struct RewriterStatsInternal {
    RewriterHtmlApplication::Status html_status;

    // RewriterApplication::Status -> count.
    RewriteStatusCountMap status_counts;

    RewriterStatsInternal()
        : html_status(RewriterHtmlApplication::UNKNOWN_STATUS) {}
  };
  typedef std::map<GoogleString, RewriterStatsInternal> RewriterStatsMap;
  RewriterStatsMap rewriter_stats_;

  DISALLOW_COPY_AND_ASSIGN(AbstractLogRecord);
};

// Simple AbstractLogRecord implementation which owns a LoggingInfo protobuf.
class LogRecord : public AbstractLogRecord {
 public:
  explicit LogRecord(AbstractMutex* mutex);

  virtual ~LogRecord();

  LoggingInfo* logging_info() { return logging_info_.get(); }

  virtual void SetImageStats(int num_img_tags, int num_inlined_img_tags,
                             int num_critical_images_used) {}

  virtual void SetResourceCounts(int num_external_css, int num_scripts) {}

  virtual void AddFoundPropertyToCohortInfo(
      int page_type, const GoogleString& cohort,
      const GoogleString& property) {}

  virtual void AddRetrievedPropertyToCohortInfo(
      int page_type, const GoogleString& cohort,
      const GoogleString& property) {}

  void SetCacheStatusForCohortInfo(
      int page_type, const GoogleString& cohort, bool found, int key_state) {}

  virtual void LogImageRewriteActivity(
      const char* id,
      const GoogleString& url,
      RewriterApplication::Status status,
      bool is_image_inlined,
      bool is_critical_image,
      bool is_url_rewritten,
      int size,
      bool try_low_res_src_insertion,
      bool low_res_src_inserted,
      ImageType low_res_image_type,
      int low_res_data_size) {}

  void LogDeviceInfo(
      int device_type,
      bool supports_image_inlining,
      bool supports_lazyload_images,
      bool supports_critical_images_beacon,
      bool supports_deferjs,
      bool supports_webp_in_place,
      bool supports_webp_rewritten_urls,
      bool supports_webplossless_alpha,
      bool is_bot) override {}

  bool WriteLogImpl() override { return true; }

 private:
  scoped_ptr<LoggingInfo> logging_info_;
};

// TODO(gee): I'm pretty sure the functionality can be provided by the previous
// ALR implementation, but for the time being leave this around to make the
// refactoring as limited as possible.
// AbstractLogRecord that copies logging_info() when in WriteLog.  This should
// be useful for testing any logging flow where an owned subordinate log record
// is needed.
class CopyOnWriteLogRecord : public LogRecord {
 public:
  CopyOnWriteLogRecord(AbstractMutex* logging_mutex, LoggingInfo* logging_info)
      : LogRecord(logging_mutex), logging_info_copy_(logging_info) {}

 protected:
  virtual bool WriteLogImpl() {
    logging_info_copy_->CopyFrom(*logging_info());
    return true;
  }

 private:
  LoggingInfo* logging_info_copy_;  // Not owned by us.

  DISALLOW_COPY_AND_ASSIGN(CopyOnWriteLogRecord);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_OPT_LOGGING_LOG_RECORD_H_
