// Copyright 2012 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.

#include "sync/internal_api/public/base/invalidation.h"

#include <cstddef>

#include "base/json/json_string_value_serializer.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "sync/notifier/ack_handler.h"
#include "sync/notifier/dropped_invalidation_tracker.h"
#include "sync/notifier/invalidation_util.h"

namespace syncer {

namespace {
const char kObjectIdKey[] = "objectId";
const char kIsUnknownVersionKey[] = "isUnknownVersion";
const char kVersionKey[] = "version";
const char kPayloadKey[] = "payload";
const int64 kInvalidVersion = -1;
}

Invalidation Invalidation::Init(
    const invalidation::ObjectId& id,
    int64 version,
    const std::string& payload) {
  return Invalidation(id, false, version, payload, AckHandle::CreateUnique());
}

Invalidation Invalidation::InitUnknownVersion(
    const invalidation::ObjectId& id) {
  return Invalidation(id, true, kInvalidVersion,
                      std::string(), AckHandle::CreateUnique());
}

Invalidation Invalidation::InitFromDroppedInvalidation(
    const Invalidation& dropped) {
  return Invalidation(dropped.id_, true, kInvalidVersion,
                      std::string(), dropped.ack_handle_);
}

scoped_ptr<Invalidation> Invalidation::InitFromValue(
    const base::DictionaryValue& value) {
  invalidation::ObjectId id;

  const base::DictionaryValue* object_id_dict;
  if (!value.GetDictionary(kObjectIdKey, &object_id_dict)
      || !ObjectIdFromValue(*object_id_dict, &id)) {
    DLOG(WARNING) << "Failed to parse id";
    return scoped_ptr<Invalidation>();
  }
  bool is_unknown_version;
  if (!value.GetBoolean(kIsUnknownVersionKey, &is_unknown_version)) {
    DLOG(WARNING) << "Failed to parse is_unknown_version flag";
    return scoped_ptr<Invalidation>();
  }
  if (is_unknown_version) {
    return scoped_ptr<Invalidation>(new Invalidation(
        id,
        true,
        kInvalidVersion,
        std::string(),
        AckHandle::CreateUnique()));
  } else {
    int64 version;
    std::string version_as_string;
    if (!value.GetString(kVersionKey, &version_as_string)
        || !base::StringToInt64(version_as_string, &version)) {
      DLOG(WARNING) << "Failed to parse version";
      return scoped_ptr<Invalidation>();
    }
    std::string payload;
    if (!value.GetString(kPayloadKey, &payload)) {
      DLOG(WARNING) << "Failed to parse payload";
      return scoped_ptr<Invalidation>();
    }
    return scoped_ptr<Invalidation>(new Invalidation(
        id,
        false,
        version,
        payload,
        AckHandle::CreateUnique()));
  }
}

Invalidation::~Invalidation() {}

invalidation::ObjectId Invalidation::object_id() const {
  return id_;
}

bool Invalidation::is_unknown_version() const {
  return is_unknown_version_;
}

int64 Invalidation::version() const {
  DCHECK(!is_unknown_version_);
  return version_;
}

const std::string& Invalidation::payload() const {
  DCHECK(!is_unknown_version_);
  return payload_;
}

const AckHandle& Invalidation::ack_handle() const {
  return ack_handle_;
}

void Invalidation::set_ack_handler(syncer::WeakHandle<AckHandler> handler) {
  ack_handler_ = handler;
}

bool Invalidation::SupportsAcknowledgement() const {
  return ack_handler_.IsInitialized();
}

void Invalidation::Acknowledge() const {
  if (SupportsAcknowledgement()) {
    ack_handler_.Call(FROM_HERE,
                      &AckHandler::Acknowledge,
                      id_,
                      ack_handle_);
  }
}

void Invalidation::Drop(DroppedInvalidationTracker* tracker) const {
  DCHECK(tracker->object_id() == object_id());
  tracker->RecordDropEvent(ack_handler_, ack_handle_);
  if (SupportsAcknowledgement()) {
    ack_handler_.Call(FROM_HERE,
                      &AckHandler::Drop,
                      id_,
                      ack_handle_);
  }
}

bool Invalidation::Equals(const Invalidation& other) const {
  return id_ == other.id_
      && is_unknown_version_ == other.is_unknown_version_
      && version_ == other.version_
      && payload_ == other.payload_;
}

scoped_ptr<base::DictionaryValue> Invalidation::ToValue() const {
  scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
  value->Set(kObjectIdKey, ObjectIdToValue(id_).release());
  if (is_unknown_version_) {
    value->SetBoolean(kIsUnknownVersionKey, true);
  } else {
    value->SetBoolean(kIsUnknownVersionKey, false);
    value->SetString(kVersionKey, base::Int64ToString(version_));
    value->SetString(kPayloadKey, payload_);
  }
  return value.Pass();
}

std::string Invalidation::ToString() const {
  std::string output;
  JSONStringValueSerializer serializer(&output);
  serializer.set_pretty_print(true);
  serializer.Serialize(*ToValue().get());
  return output;
}

Invalidation::Invalidation(
    const invalidation::ObjectId& id,
    bool is_unknown_version,
    int64 version,
    const std::string& payload,
    AckHandle ack_handle)
  : id_(id),
    is_unknown_version_(is_unknown_version),
    version_(version),
    payload_(payload),
    ack_handle_(ack_handle) {}

}  // namespace syncer