// 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/fake_nfc_device_client.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_nfc_adapter_client.h"
#include "chromeos/dbus/fake_nfc_record_client.h"
#include "chromeos/dbus/nfc_client_helpers.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace chromeos {

using nfc_client_helpers::ObjectPathVector;

const char FakeNfcDeviceClient::kDevicePath[] = "/fake/device0";
const int FakeNfcDeviceClient::kDefaultSimulationTimeoutMilliseconds = 10000;

FakeNfcDeviceClient::Properties::Properties(
    const PropertyChangedCallback& callback)
    : NfcDeviceClient::Properties(NULL, callback) {
}

FakeNfcDeviceClient::Properties::~Properties() {
}

void FakeNfcDeviceClient::Properties::Get(
    dbus::PropertyBase* property,
    dbus::PropertySet::GetCallback callback) {
  VLOG(1) << "Get " << property->name();
  callback.Run(false);
}

void FakeNfcDeviceClient::Properties::GetAll() {
  VLOG(1) << "GetAll";
}

void FakeNfcDeviceClient::Properties::Set(
    dbus::PropertyBase* property,
    dbus::PropertySet::SetCallback callback) {
  VLOG(1) << "Set " << property->name();
  callback.Run(false);
}

FakeNfcDeviceClient::FakeNfcDeviceClient()
    : pairing_started_(false),
      device_visible_(false),
      simulation_timeout_(kDefaultSimulationTimeoutMilliseconds) {
  VLOG(1) << "Creating FakeNfcDeviceClient";

  properties_.reset(new Properties(
      base::Bind(&FakeNfcDeviceClient::OnPropertyChanged,
                 base::Unretained(this),
                 dbus::ObjectPath(kDevicePath))));
}

FakeNfcDeviceClient::~FakeNfcDeviceClient() {
}

void FakeNfcDeviceClient::Init(dbus::Bus* bus) {
}

void FakeNfcDeviceClient::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void FakeNfcDeviceClient::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

std::vector<dbus::ObjectPath> FakeNfcDeviceClient::GetDevicesForAdapter(
      const dbus::ObjectPath& adapter_path) {
  std::vector<dbus::ObjectPath> device_paths;
  if (device_visible_ &&
      adapter_path.value() == FakeNfcAdapterClient::kAdapterPath0)
    device_paths.push_back(dbus::ObjectPath(kDevicePath));
  return device_paths;
}

FakeNfcDeviceClient::Properties*
FakeNfcDeviceClient::GetProperties(const dbus::ObjectPath& object_path) {
  if (!device_visible_)
    return NULL;
  return properties_.get();
}

void FakeNfcDeviceClient::Push(
    const dbus::ObjectPath& object_path,
    const base::DictionaryValue& attributes,
    const base::Closure& callback,
    const nfc_client_helpers::ErrorCallback& error_callback) {
  VLOG(1) << "FakeNfcDeviceClient::Write called.";

  // Success!
  if (!device_visible_) {
    LOG(ERROR) << "Device not visible. Cannot push record.";
    error_callback.Run(nfc_error::kDoesNotExist, "No such device.");
    return;
  }
  callback.Run();
}

void FakeNfcDeviceClient::BeginPairingSimulation(int visibility_delay,
                                                 int record_push_delay) {
  if (pairing_started_) {
    VLOG(1) << "Simulation already started.";
    return;
  }
  DCHECK(!device_visible_);
  DCHECK(visibility_delay >= 0);

  pairing_started_ = true;

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&FakeNfcDeviceClient::MakeDeviceVisible,
                 base::Unretained(this),
                 record_push_delay),
      base::TimeDelta::FromMilliseconds(visibility_delay));
}

void FakeNfcDeviceClient::EndPairingSimulation() {
  if (!pairing_started_) {
    VLOG(1) << "No simulation started.";
    return;
  }
  if (device_visible_) {
    // Remove records, if they were added.
    if (!properties_->records.value().empty()) {
      FakeNfcRecordClient* record_client =
          static_cast<FakeNfcRecordClient*>(
              DBusThreadManager::Get()->GetNfcRecordClient());
      record_client->SetDeviceRecordsVisible(false);
    }
    // Remove the device.
    FOR_EACH_OBSERVER(Observer, observers_,
                      DeviceRemoved(dbus::ObjectPath(kDevicePath)));
    FakeNfcAdapterClient* adapter_client =
        static_cast<FakeNfcAdapterClient*>(
            DBusThreadManager::Get()->GetNfcAdapterClient());
    adapter_client->UnsetDevice(dbus::ObjectPath(kDevicePath));
    device_visible_ = false;
  }
  pairing_started_ = false;
}

void FakeNfcDeviceClient::EnableSimulationTimeout(int simulation_timeout) {
  simulation_timeout_ = simulation_timeout;
}

void FakeNfcDeviceClient::DisableSimulationTimeout() {
  simulation_timeout_ = -1;
}

void FakeNfcDeviceClient::SetRecords(
    const std::vector<dbus::ObjectPath>& record_paths) {
  if (!device_visible_) {
    VLOG(1) << "Device not visible.";
    return;
  }
  properties_->records.ReplaceValue(record_paths);
}

void FakeNfcDeviceClient::ClearRecords() {
  ObjectPathVector records;
  SetRecords(records);
}

void FakeNfcDeviceClient::OnPropertyChanged(
    const dbus::ObjectPath& object_path,
    const std::string& property_name) {
  FOR_EACH_OBSERVER(NfcDeviceClient::Observer, observers_,
                    DevicePropertyChanged(object_path, property_name));
}

void FakeNfcDeviceClient::MakeDeviceVisible(int record_push_delay) {
  if (!pairing_started_) {
    VLOG(1) << "Device pairing was cancelled.";
    return;
  }
  device_visible_ = true;

  FakeNfcAdapterClient* adapter_client =
      static_cast<FakeNfcAdapterClient*>(
          DBusThreadManager::Get()->GetNfcAdapterClient());
  adapter_client->SetDevice(dbus::ObjectPath(kDevicePath));
  FOR_EACH_OBSERVER(Observer, observers_,
                    DeviceAdded(dbus::ObjectPath(kDevicePath)));

  if (record_push_delay < 0) {
    // Don't simulate record push. Instead, skip directly to the timeout step.
    if (simulation_timeout_ >= 0) {
      base::MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          base::Bind(&FakeNfcDeviceClient::HandleSimulationTimeout,
                     base::Unretained(this)),
          base::TimeDelta::FromMilliseconds(simulation_timeout_));
    }
    return;
  }

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&FakeNfcDeviceClient::MakeRecordsVisible,
                 base::Unretained(this)),
      base::TimeDelta::FromMilliseconds(record_push_delay));
}

void FakeNfcDeviceClient::MakeRecordsVisible() {
  if (!pairing_started_) {
    VLOG(1) << "Pairing was cancelled";
    return;
  }
  DCHECK(device_visible_);
  FakeNfcRecordClient* record_client =
      static_cast<FakeNfcRecordClient*>(
          DBusThreadManager::Get()->GetNfcRecordClient());
  record_client->SetDeviceRecordsVisible(true);

  if (simulation_timeout_ < 0)
    return;

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&FakeNfcDeviceClient::HandleSimulationTimeout,
                 base::Unretained(this)),
      base::TimeDelta::FromMilliseconds(simulation_timeout_));
}

void FakeNfcDeviceClient::HandleSimulationTimeout() {
  if (simulation_timeout_ < 0) {
    VLOG(1) << "Simulation timeout was cancelled. Nothing to do.";
    return;
  }
  EndPairingSimulation();
}

}  // namespace chromeos