// Copyright 2014 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/pairing/fake_controller_pairing_flow.h"

#include <map>

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"

namespace chromeos {

FakeControllerPairingFlow::FakeControllerPairingFlow(const std::string& config)
    : current_stage_(STAGE_NONE),
      should_fail_on_connecting_(false),
      connection_lost_begin_(STAGE_NONE),
      connection_lost_end_(STAGE_NONE),
      enrollment_should_fail_(false) {
  ApplyConfig(config);
  AddObserver(this);
}

FakeControllerPairingFlow::~FakeControllerPairingFlow() {
  RemoveObserver(this);
}

void FakeControllerPairingFlow::ApplyConfig(const std::string& config) {
  typedef std::vector<std::string> Tokens;

  base::StringPairs kv_pairs;
  CHECK(base::SplitStringIntoKeyValuePairs(config, ':', ',', &kv_pairs))
      << "Wrong config format.";
  std::map<std::string, std::string> dict(kv_pairs.begin(), kv_pairs.end());

  if (dict.count("async_duration")) {
    int ms = 0;
    CHECK(base::StringToInt(dict["async_duration"], &ms))
        << "Wrong 'async_duration' format.";
    async_duration_ = base::TimeDelta::FromMilliseconds(ms);
  } else {
    async_duration_ = base::TimeDelta::FromMilliseconds(3000);
  }

  should_fail_on_connecting_ =
      dict.count("fail_connecting") && (dict["fail_connecting"] == "1");

  enrollment_should_fail_ =
      dict.count("fail_enrollment") && (dict["fail_enrollment"] == "1");

  if (dict.count("connection_lost")) {
    Tokens lost_begin_end;
    CHECK(Tokenize(dict["connection_lost"], "-", &lost_begin_end) == 2)
        << "Wrong 'connection_lost' format.";
    int begin = 0;
    int end = 0;
    CHECK(base::StringToInt(lost_begin_end[0], &begin) &&
          base::StringToInt(lost_begin_end[1], &end))
        << "Wrong 'connection_lost' format.";
    CHECK((begin == 0 && end == 0) ||
          (STAGE_WAITING_FOR_CODE_CONFIRMATION <= begin && begin <= end &&
           end <= STAGE_HOST_ENROLLMENT_ERROR))
        << "Wrong 'connection_lost' interval.";
    connection_lost_begin_ = static_cast<Stage>(begin);
    connection_lost_end_ = static_cast<Stage>(end);
  } else {
    connection_lost_begin_ = connection_lost_end_ = STAGE_NONE;
  }

  if (!dict.count("discovery")) {
    dict["discovery"] =
        "F-Device_1~F-Device_5~F-Device_3~L-Device_3~L-Device_1~F-Device_1";
  }
  base::StringPairs events;
  CHECK(
      base::SplitStringIntoKeyValuePairs(dict["discovery"], '-', '~', &events))
      << "Wrong 'discovery' format.";
  DiscoveryScenario scenario;
  for (base::StringPairs::const_iterator event = events.begin();
       event != events.end();
       ++event) {
    std::string type = event->first;
    std::string device_id = event->second;
    CHECK(type == "F" || type == "L" || type == "N")
        << "Wrong discovery event type.";
    CHECK(!device_id.empty() || type == "N") << "Empty device ID.";
    scenario.push_back(DiscoveryEvent(
        type == "F" ? DEVICE_FOUND : type == "L" ? DEVICE_LOST : NOTHING_FOUND,
        device_id));
  }
  SetDiscoveryScenario(scenario);

  preset_confirmation_code_ = dict["code"];
  CHECK(preset_confirmation_code_.empty() ||
        (preset_confirmation_code_.length() == 6 &&
         preset_confirmation_code_.find_first_not_of("0123456789") ==
             std::string::npos))
      << "Wrong 'code' format.";
}

void FakeControllerPairingFlow::SetShouldFailOnConnecting() {
  should_fail_on_connecting_ = true;
}

void FakeControllerPairingFlow::SetShouldLoseConnection(Stage stage_begin,
                                                        Stage stage_end) {
  connection_lost_begin_ = stage_begin;
  connection_lost_end_ = stage_end;
}

void FakeControllerPairingFlow::SetEnrollmentShouldFail() {
  enrollment_should_fail_ = true;
}

void FakeControllerPairingFlow::SetDiscoveryScenario(
    const DiscoveryScenario& discovery_scenario) {
  discovery_scenario_ = discovery_scenario;
  // Check that scenario is valid.
  std::set<std::string> devices;
  for (DiscoveryScenario::const_iterator event = discovery_scenario_.begin();
       event != discovery_scenario_.end();
       ++event) {
    switch (event->first) {
      case DEVICE_FOUND: {
        devices.insert(event->second);
        break;
      }
      case DEVICE_LOST: {
        CHECK(devices.count(event->second));
        devices.erase(event->second);
        break;
      }
      case NOTHING_FOUND: {
        CHECK(++event == discovery_scenario_.end());
        return;
      }
    }
  }
}

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

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

ControllerPairingFlow::Stage FakeControllerPairingFlow::GetCurrentStage() {
  return current_stage_;
}

void FakeControllerPairingFlow::StartFlow() {
  CHECK(current_stage_ == STAGE_NONE);
  ChangeStage(STAGE_DEVICES_DISCOVERY);
}

ControllerPairingFlow::DeviceIdList
FakeControllerPairingFlow::GetDiscoveredDevices() {
  CHECK(current_stage_ == STAGE_DEVICES_DISCOVERY);
  return DeviceIdList(discovered_devices_.begin(), discovered_devices_.end());
}

void FakeControllerPairingFlow::ChooseDeviceForPairing(
    const std::string& device_id) {
  CHECK(current_stage_ == STAGE_DEVICES_DISCOVERY);
  CHECK(discovered_devices_.count(device_id));
  choosen_device_ = device_id;
  ChangeStage(STAGE_ESTABLISHING_CONNECTION);
}

void FakeControllerPairingFlow::RepeatDiscovery() {
  CHECK(current_stage_ == STAGE_DEVICE_NOT_FOUND ||
        current_stage_ == STAGE_ESTABLISHING_CONNECTION_ERROR ||
        current_stage_ == STAGE_HOST_ENROLLMENT_ERROR);
  ChangeStage(STAGE_DEVICES_DISCOVERY);
}

std::string FakeControllerPairingFlow::GetConfirmationCode() {
  CHECK(current_stage_ == STAGE_WAITING_FOR_CODE_CONFIRMATION);
  if (confirmation_code_.empty()) {
    if (preset_confirmation_code_.empty()) {
      for (int i = 0; i < 6; ++i)
        confirmation_code_.push_back(base::RandInt('0', '9'));
    } else {
      confirmation_code_ = preset_confirmation_code_;
    }
  }
  return confirmation_code_;
}

void FakeControllerPairingFlow::SetConfirmationCodeIsCorrect(bool correct) {
  CHECK(current_stage_ == STAGE_WAITING_FOR_CODE_CONFIRMATION);
  if (correct)
    ChangeStage(STAGE_HOST_UPDATE_IN_PROGRESS);
  else
    ChangeStage(STAGE_DEVICES_DISCOVERY);
}

void FakeControllerPairingFlow::OnAuthenticationDone(
    const chromeos::UserContext& user_context,
    content::BrowserContext* browser_context) {
  CHECK(current_stage_ == STAGE_WAITING_FOR_CREDENTIALS);
  ChangeStage(STAGE_HOST_ENROLLMENT_IN_PROGRESS);
}

void FakeControllerPairingFlow::StartSession() {
  CHECK(current_stage_ == STAGE_PAIRING_DONE);
  ChangeStage(STAGE_FINISHED);
}

void FakeControllerPairingFlow::ChangeStage(Stage new_stage) {
  if (current_stage_ == new_stage)
    return;
  current_stage_ = new_stage;
  FOR_EACH_OBSERVER(Observer, observers_, PairingStageChanged(new_stage));
}

void FakeControllerPairingFlow::ChangeStageLater(Stage new_stage) {
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&FakeControllerPairingFlow::ChangeStage,
                 base::Unretained(this),
                 new_stage),
      async_duration_);
}

void FakeControllerPairingFlow::ExecuteDiscoveryEvent(size_t event_position) {
  if (current_stage_ != STAGE_DEVICES_DISCOVERY)
    return;
  CHECK(event_position < discovery_scenario_.size());
  const DiscoveryEvent& event = discovery_scenario_[event_position];
  switch (event.first) {
    case DEVICE_FOUND: {
      DeviceFound(event.second);
      break;
    }
    case DEVICE_LOST: {
      DeviceLost(event.second);
      break;
    }
    case NOTHING_FOUND: {
      ChangeStage(STAGE_DEVICE_NOT_FOUND);
      break;
    }
  }
  if (++event_position == discovery_scenario_.size()) {
    return;
  }
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&FakeControllerPairingFlow::ExecuteDiscoveryEvent,
                 base::Unretained(this),
                 event_position),
      async_duration_);
}

void FakeControllerPairingFlow::DeviceFound(const std::string& device_id) {
  CHECK(current_stage_ == STAGE_DEVICES_DISCOVERY);
  discovered_devices_.insert(device_id);
  FOR_EACH_OBSERVER(Observer, observers_, DiscoveredDevicesListChanged());
}

void FakeControllerPairingFlow::DeviceLost(const std::string& device_id) {
  CHECK(current_stage_ == STAGE_DEVICES_DISCOVERY);
  discovered_devices_.erase(device_id);
  FOR_EACH_OBSERVER(Observer, observers_, DiscoveredDevicesListChanged());
}

void FakeControllerPairingFlow::PairingStageChanged(Stage new_stage) {
  Stage next_stage = STAGE_NONE;
  switch (new_stage) {
    case STAGE_DEVICES_DISCOVERY: {
      discovered_devices_.clear();
      base::MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          base::Bind(&FakeControllerPairingFlow::ExecuteDiscoveryEvent,
                     base::Unretained(this),
                     0),
          async_duration_);
      break;
    }
    case STAGE_ESTABLISHING_CONNECTION: {
      if (should_fail_on_connecting_) {
        next_stage = STAGE_ESTABLISHING_CONNECTION_ERROR;
        should_fail_on_connecting_ = false;
      } else {
        confirmation_code_.clear();
        next_stage = STAGE_WAITING_FOR_CODE_CONFIRMATION;
      }
      break;
    }
    case STAGE_HOST_UPDATE_IN_PROGRESS: {
      next_stage = STAGE_WAITING_FOR_CREDENTIALS;
      break;
    }
    case STAGE_HOST_ENROLLMENT_IN_PROGRESS: {
      if (enrollment_should_fail_) {
        enrollment_should_fail_ = false;
        next_stage = STAGE_HOST_ENROLLMENT_ERROR;
      } else {
        next_stage = STAGE_PAIRING_DONE;
      }
      break;
    }
    case STAGE_HOST_CONNECTION_LOST: {
      next_stage = connection_lost_end_;
      connection_lost_end_ = STAGE_NONE;
      break;
    }
    default:
      break;
  }
  if (new_stage == connection_lost_begin_) {
    connection_lost_begin_ = STAGE_NONE;
    next_stage = STAGE_HOST_CONNECTION_LOST;
  }
  if (next_stage != STAGE_NONE)
    ChangeStageLater(next_stage);
}

void FakeControllerPairingFlow::DiscoveredDevicesListChanged() {
}

}  // namespace chromeos