// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_READING_LIST_CORE_READING_LIST_ENTRY_H_
#define COMPONENTS_READING_LIST_CORE_READING_LIST_ENTRY_H_

#include <string>

#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "net/base/backoff_entry.h"
#include "url/gurl.h"

namespace reading_list {
class ReadingListLocal;

// The different ways a reading list entry is added.
// |ADDED_VIA_CURRENT_APP| is when the entry was added by the user from within
// the current instance of the app.
// |ADDED_VIA_EXTENSION| is when the entry was added via the share extension.
// |ADDED_VIA_SYNC| is when the entry was added with sync.
enum EntrySource { ADDED_VIA_CURRENT_APP, ADDED_VIA_EXTENSION, ADDED_VIA_SYNC };

// Contains additional data used by the ContentSuggestions.
struct ContentSuggestionsExtra {
  // Whether the Reading List Entry has been dismissed in the Content
  // Suggestions.
  bool dismissed = false;
};
}

namespace sync_pb {
class ReadingListSpecifics;
}

class ReadingListEntry;

// An entry in the reading list. The URL is a unique identifier for an entry, as
// such it should not be empty and is the only thing considered when comparing
// entries.
// A word about timestamp usage in this class:
// - The backing store uses int64 values to code timestamps. We use internally
//   the same type to avoid useless conversions. This values represent the
//   number of micro seconds since Jan 1st 1970.
// - As most timestamp are used to sort entries, operations on int64_t are
//   faster than operations on base::Time. So Getter return the int64_t values.
// - However, to ensure all the conversions are done the same way, and because
//   the Now time is alway retrieved using base::Time::Now(), all the timestamp
//   parameter are passed as base::Time. These parameters are internally
//   converted in int64_t.
class ReadingListEntry {
 public:
  // Creates a ReadingList entry. |url| and |title| are the main fields of the
  // entry.
  // |now| is used to fill the |creation_time_us_| and all the update timestamp
  // fields.
  ReadingListEntry(const GURL& url,
                   const std::string& title,
                   const base::Time& now);
  ReadingListEntry(const GURL& url,
                   const std::string& title,
                   const base::Time& now,
                   std::unique_ptr<net::BackoffEntry> backoff);
  ReadingListEntry(ReadingListEntry&& entry);
  ~ReadingListEntry();

  // Entries are created in WAITING state. At some point they will be PROCESSING
  // into one of the three state: PROCESSED, the only state a distilled URL
  // would be set, WILL_RETRY, similar to wait, but with exponential delays or
  // DISTILLATION_ERROR where the system will not retry at all.
  enum DistillationState {
    WAITING,
    PROCESSING,
    PROCESSED,
    WILL_RETRY,
    DISTILLATION_ERROR
  };

  static const net::BackoffEntry::Policy kBackoffPolicy;

  // The URL of the page the user would like to read later.
  const GURL& URL() const;
  // The title of the entry. Might be empty.
  const std::string& Title() const;
  // What state this entry is in.
  DistillationState DistilledState() const;
  // The local file path for the distilled version of the page. This should only
  // be called if the state is "PROCESSED".
  const base::FilePath& DistilledPath() const;
  // The URL that has been distilled to produce file stored at |DistilledPath|.
  const GURL& DistilledURL() const;
  // The time distillation was done. The value is in microseconds since Jan 1st
  // 1970. Returns 0 if the entry was not distilled.
  int64_t DistillationTime() const;
  // The size of the stored page in bytes.
  int64_t DistillationSize() const;
  // The time before the next try. This is automatically increased when the
  // state is set to WILL_RETRY or ERROR from a non-error state.
  base::TimeDelta TimeUntilNextTry() const;
  // The number of time chrome failed to download this entry. This is
  // automatically increased when the state is set to WILL_RETRY or ERROR from a
  // non-error state.
  int FailedDownloadCounter() const;
  // The read status of the entry.
  bool IsRead() const;
  // Returns if an entry has ever been seen.
  bool HasBeenSeen() const;

  // Extra information about this entry for the Content Suggestions.
  const reading_list::ContentSuggestionsExtra* ContentSuggestionsExtra() const;

  // The last update time of the entry. This value may be used to sort the
  // entries. The value is in microseconds since Jan 1st 1970.
  int64_t UpdateTime() const;

  // The last update time of the title of the entry. The value is in
  // microseconds since Jan 1st 1970.
  int64_t UpdateTitleTime() const;

  // The creation update time of the entry. The value is in microseconds since
  // Jan 1st 1970.
  int64_t CreationTime() const;

  // The time when the entry was read for the first time. The value is in
  // microseconds since Jan 1st 1970.
  int64_t FirstReadTime() const;

  // Set the update time to |now|.
  void MarkEntryUpdated(const base::Time& now);

  // Returns a protobuf encoding the content of this ReadingListEntry for local
  // storage. Use |now| to serialize the backoff_entry.
  std::unique_ptr<reading_list::ReadingListLocal> AsReadingListLocal(
      const base::Time& now) const;

  // Returns a protobuf encoding the content of this ReadingListEntry for sync.
  std::unique_ptr<sync_pb::ReadingListSpecifics> AsReadingListSpecifics() const;

  // Created a ReadingListEntry from the protobuf format.
  // Use |now| to deserialize the backoff_entry.
  static std::unique_ptr<ReadingListEntry> FromReadingListLocal(
      const reading_list::ReadingListLocal& pb_entry,
      const base::Time& now);

  // Created a ReadingListEntry from the protobuf format.
  // If creation time is not set, it will be set to |now|.
  static std::unique_ptr<ReadingListEntry> FromReadingListSpecifics(
      const sync_pb::ReadingListSpecifics& pb_entry,
      const base::Time& now);

  // Merge |this| and |other| into this.
  // Local fields are kept from |this|.
  // Each field is merged individually keeping the highest value as defined by
  // the |ReadingListStore.CompareEntriesForSync| function.
  //
  // After calling |MergeLocalStateFrom|, the result must verify
  // ReadingListStore.CompareEntriesForSync(old_this.AsReadingListSpecifics(),
  //                                        new_this.AsReadingListSpecifics())
  // and
  // ReadingListStore.CompareEntriesForSync(other.AsReadingListSpecifics(),
  //                                        new_this.AsReadingListSpecifics()).
  void MergeWithEntry(const ReadingListEntry& other);

  ReadingListEntry& operator=(ReadingListEntry&& other);

  bool operator==(const ReadingListEntry& other) const;

  // Sets |title_| to |title|. Sets |update_title_time_us_| to |now|.
  void SetTitle(const std::string& title, const base::Time& now);
  // Sets the distilled info (offline path, online URL, size and date of the
  // stored files) about distilled page, switch the state to PROCESSED and reset
  // the time until the next try.
  void SetDistilledInfo(const base::FilePath& path,
                        const GURL& distilled_url,
                        int64_t distilation_size,
                        const base::Time& distilation_time);
  // Sets the state to one of PROCESSING, WILL_RETRY or ERROR.
  void SetDistilledState(DistillationState distilled_state);
  // Sets the read state of the entry. Will set the UpdateTime of the entry.
  // If |first_read_time_us_| is 0 and read is READ, sets |first_read_time_us_|
  // to |now|.
  void SetRead(bool read, const base::Time& now);
  // Sets extra information about this entry used by Content Suggestions.
  void SetContentSuggestionsExtra(
      const reading_list::ContentSuggestionsExtra& extra);

 private:
  enum State { UNSEEN, UNREAD, READ };
  ReadingListEntry(
      const GURL& url,
      const std::string& title,
      State state,
      int64_t creation_time,
      int64_t first_read_time,
      int64_t update_time,
      int64_t update_title_time,
      ReadingListEntry::DistillationState distilled_state,
      const base::FilePath& distilled_path,
      const GURL& distilled_url,
      int64_t distillation_time,
      int64_t distillation_size,
      int failed_download_counter,
      std::unique_ptr<net::BackoffEntry> backoff,
      const reading_list::ContentSuggestionsExtra& content_suggestions_extra);
  GURL url_;
  std::string title_;
  State state_;
  base::FilePath distilled_path_;
  GURL distilled_url_;
  DistillationState distilled_state_;

  std::unique_ptr<net::BackoffEntry> backoff_;
  int failed_download_counter_;

  // These value are in microseconds since Jan 1st 1970. They are used for
  // sorting the entries from the database. They are kept in int64_t to avoid
  // conversion on each save/read event.
  int64_t creation_time_us_;
  int64_t first_read_time_us_;
  int64_t update_time_us_;
  int64_t update_title_time_us_;
  int64_t distillation_time_us_;
  int64_t distillation_size_;

  reading_list::ContentSuggestionsExtra content_suggestions_extra_;

  DISALLOW_COPY_AND_ASSIGN(ReadingListEntry);
};

#endif  // COMPONENTS_READING_LIST_CORE_READING_LIST_ENTRY_H_
