// Copyright 2013 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 "chromeos/dbus/nfc_device_client.h"

#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/strings/stringprintf.h"
#include "chromeos/dbus/nfc_adapter_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using chromeos::nfc_client_helpers::DBusObjectMap;
using chromeos::nfc_client_helpers::ObjectProxyTree;

namespace chromeos {

NfcDeviceClient::Properties::Properties(
    dbus::ObjectProxy* object_proxy,
    const PropertyChangedCallback& callback)
    : NfcPropertySet(object_proxy,
                     nfc_device::kNfcDeviceInterface,
                     callback) {
  RegisterProperty(nfc_device::kRecordsProperty, &records);
}

NfcDeviceClient::Properties::~Properties() {
}

// The NfcDeviceClient implementation used in production.
class NfcDeviceClientImpl : public NfcDeviceClient,
                            public NfcAdapterClient::Observer,
                            public DBusObjectMap::Delegate {
 public:
  explicit NfcDeviceClientImpl(NfcAdapterClient* adapter_client)
      : bus_(NULL),
        adapter_client_(adapter_client),
        weak_ptr_factory_(this) {
    DCHECK(adapter_client);
  }

  virtual ~NfcDeviceClientImpl() {
    DCHECK(adapter_client_);
    adapter_client_->RemoveObserver(this);
  }

  // NfcDeviceClient override.
  virtual void AddObserver(NfcDeviceClient::Observer* observer) OVERRIDE {
    DCHECK(observer);
    observers_.AddObserver(observer);
  }

  // NfcDeviceClient override.
  virtual void RemoveObserver(NfcDeviceClient::Observer* observer) OVERRIDE {
    DCHECK(observer);
    observers_.RemoveObserver(observer);
  }

  // NfcDeviceClient override.
  virtual Properties* GetProperties(
      const dbus::ObjectPath& object_path) OVERRIDE {
    return static_cast<Properties*>(
        adapters_to_object_maps_.FindObjectProperties(object_path));
  }

  // NfcDeviceClient override.
  virtual void Push(
      const dbus::ObjectPath& object_path,
      const base::DictionaryValue& attributes,
      const base::Closure& callback,
      const nfc_client_helpers::ErrorCallback& error_callback) OVERRIDE {
    dbus::ObjectProxy* object_proxy =
        adapters_to_object_maps_.FindObjectProxy(object_path);
    if (!object_proxy) {
      std::string error_message =
          base::StringPrintf(
              "NFC device with object path \"%s\" does not exist.",
              object_path.value().c_str());
      LOG(ERROR) << error_message;
      error_callback.Run(nfc_client_helpers::kUnknownObjectError,
                         error_message);
      return;
    }

    // |attributes| should not be empty.
    if (attributes.empty()) {
      std::string error_message =
          "Cannot push data to device with empty arguments.";
      LOG(ERROR) << error_message;
      error_callback.Run(nfc_error::kInvalidArguments, error_message);
      return;
    }

    // Create the arguments.
    dbus::MethodCall method_call(nfc_device::kNfcDeviceInterface,
                                 nfc_device::kPush);
    dbus::MessageWriter writer(&method_call);
    dbus::MessageWriter array_writer(NULL);
    dbus::MessageWriter dict_entry_writer(NULL);
    writer.OpenArray("{sv}", &array_writer);
    for (DictionaryValue::Iterator iter(attributes);
         !iter.IsAtEnd(); iter.Advance()) {
      array_writer.OpenDictEntry(&dict_entry_writer);
      dict_entry_writer.AppendString(iter.key());
      nfc_client_helpers::AppendValueDataAsVariant(&dict_entry_writer,
                                                   iter.value());
      array_writer.CloseContainer(&dict_entry_writer);
    }
    writer.CloseContainer(&array_writer);

    object_proxy->CallMethodWithErrorCallback(
        &method_call,
        dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::Bind(&nfc_client_helpers::OnSuccess, callback),
        base::Bind(&nfc_client_helpers::OnError, error_callback));
  }

 protected:
  // DBusClient override.
  virtual void Init(dbus::Bus* bus) OVERRIDE {
    VLOG(1) << "Creating NfcDeviceClientImpl";
    DCHECK(bus);
    bus_ = bus;
    DCHECK(adapter_client_);
    adapter_client_->AddObserver(this);
  }

 private:
  // NfcAdapterClient::Observer override.
  virtual void AdapterAdded(const dbus::ObjectPath& object_path) OVERRIDE {
    VLOG(1) << "Adapter added. Creating map for device proxies belonging to "
            << "adapter: " << object_path.value();
    adapters_to_object_maps_.CreateObjectMap(
        object_path, nfc_device::kNfcDeviceServiceName, this, bus_);
  }

  // NfcAdapterClient::Observer override.
  virtual void AdapterRemoved(const dbus::ObjectPath& object_path) OVERRIDE {
    // Neard doesn't send out property changed signals for the devices that
    // are removed when the adapter they belong to is removed. Clean up the
    // object proxies for devices that are managed by the removed adapter.
    // Note: DBusObjectMap guarantees that the Properties structure for the
    // removed adapter will be valid before this method returns.
    VLOG(1) << "Adapter removed. Cleaning up device proxies belonging to "
            << "adapter: " << object_path.value();
    adapters_to_object_maps_.RemoveObjectMap(object_path);
  }

  // NfcAdapterClient::Observer override.
  virtual void AdapterPropertyChanged(
      const dbus::ObjectPath& object_path,
      const std::string& property_name) OVERRIDE {
    DCHECK(adapter_client_);
    NfcAdapterClient::Properties *adapter_properties =
        adapter_client_->GetProperties(object_path);
    DCHECK(adapter_properties);

    // Ignore changes to properties other than "Devices".
    if (property_name != adapter_properties->devices.name())
      return;

    // Update the known devices.
    VLOG(1) << "NFC devices changed.";
    const std::vector<dbus::ObjectPath>& received_devices =
        adapter_properties->devices.value();
    DBusObjectMap* object_map =
        adapters_to_object_maps_.GetObjectMap(object_path);
    DCHECK(object_map);
    object_map->UpdateObjects(received_devices);
  }

  // nfc_client_helpers::DBusObjectMap::Delegate override.
  virtual NfcPropertySet* CreateProperties(
      dbus::ObjectProxy* object_proxy) OVERRIDE {
    return new Properties(
        object_proxy,
        base::Bind(&NfcDeviceClientImpl::OnPropertyChanged,
                   weak_ptr_factory_.GetWeakPtr(),
                   object_proxy->object_path()));
  }

  // nfc_client_helpers::DBusObjectMap::Delegate override.
  virtual void ObjectAdded(const dbus::ObjectPath& object_path) OVERRIDE {
    FOR_EACH_OBSERVER(NfcDeviceClient::Observer, observers_,
                      DeviceAdded(object_path));
  }

  virtual void ObjectRemoved(const dbus::ObjectPath& object_path) OVERRIDE {
    FOR_EACH_OBSERVER(NfcDeviceClient::Observer, observers_,
                      DeviceRemoved(object_path));
  }

  // Called by NfcPropertySet when a property value is changed, either by
  // result of a signal or response to a GetAll() or Get() call.
  void OnPropertyChanged(const dbus::ObjectPath& object_path,
                         const std::string& property_name) {
    VLOG(1) << "Device property changed; Path: " << object_path.value()
            << " Property: " << property_name;
    FOR_EACH_OBSERVER(NfcDeviceClient::Observer, observers_,
                      DevicePropertyChanged(object_path, property_name));
  }

  // We maintain a pointer to the bus to be able to request proxies for
  // new NFC devices that appear.
  dbus::Bus* bus_;

  // List of observers interested in event notifications.
  ObserverList<NfcDeviceClient::Observer> observers_;

  // Mapping from object paths to object proxies and properties structures that
  // were already created by us. This stucture stores a different DBusObjectMap
  // for each known NFC adapter object path.
  ObjectProxyTree adapters_to_object_maps_;

  // The adapter client that we listen to events notifications from.
  NfcAdapterClient* adapter_client_;

  // Weak pointer factory for generating 'this' pointers that might live longer
  // than we do.
  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<NfcDeviceClientImpl> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(NfcDeviceClientImpl);
};

NfcDeviceClient::NfcDeviceClient() {
}

NfcDeviceClient::~NfcDeviceClient() {
}

NfcDeviceClient* NfcDeviceClient::Create(NfcAdapterClient* adapter_client) {
  return new NfcDeviceClientImpl(adapter_client);
}

}  // namespace chromeos