/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#define LOG_TAG "bt_gd_neigh"

#include "neighbor/scan.h"

#include <bluetooth/log.h>

#include <memory>

#include "hci/hci_layer.h"
#include "hci/hci_packets.h"
#include "module.h"
#include "os/handler.h"

namespace bluetooth {
namespace neighbor {

struct ScanModule::impl {
  impl(ScanModule& module);

  void SetInquiryScan(bool enabled);
  bool IsInquiryEnabled() const;

  void SetPageScan(bool enabled);
  bool IsPageEnabled() const;

  void Start();
  void Stop();

private:
  ScanModule& module_;

  bool inquiry_scan_enabled_;
  bool page_scan_enabled_;

  void WriteScanEnable();
  void ReadScanEnable(hci::ScanEnable);

  void OnCommandComplete(hci::CommandCompleteView status);

  hci::HciLayer* hci_layer_;
  os::Handler* handler_;
};

const ModuleFactory neighbor::ScanModule::Factory =
        ModuleFactory([]() { return new neighbor::ScanModule(); });

neighbor::ScanModule::impl::impl(neighbor::ScanModule& module)
    : module_(module), inquiry_scan_enabled_(false), page_scan_enabled_(false) {}

void neighbor::ScanModule::impl::OnCommandComplete(hci::CommandCompleteView view) {
  switch (view.GetCommandOpCode()) {
    case hci::OpCode::READ_SCAN_ENABLE: {
      auto packet = hci::ReadScanEnableCompleteView::Create(view);
      log::assert_that(packet.IsValid(), "assert failed: packet.IsValid()");
      log::assert_that(packet.GetStatus() == hci::ErrorCode::SUCCESS,
                       "assert failed: packet.GetStatus() == hci::ErrorCode::SUCCESS");
      ReadScanEnable(packet.GetScanEnable());
    } break;

    case hci::OpCode::WRITE_SCAN_ENABLE: {
      auto packet = hci::WriteScanEnableCompleteView::Create(view);
      log::assert_that(packet.IsValid(), "assert failed: packet.IsValid()");
      log::assert_that(packet.GetStatus() == hci::ErrorCode::SUCCESS,
                       "assert failed: packet.GetStatus() == hci::ErrorCode::SUCCESS");
    } break;

    default:
      log::error("Unhandled command {}", hci::OpCodeText(view.GetCommandOpCode()));
      break;
  }
}

void neighbor::ScanModule::impl::WriteScanEnable() {
  hci::ScanEnable scan_enable;

  if (inquiry_scan_enabled_ && !page_scan_enabled_) {
    scan_enable = hci::ScanEnable::INQUIRY_SCAN_ONLY;
  } else if (!inquiry_scan_enabled_ && page_scan_enabled_) {
    scan_enable = hci::ScanEnable::PAGE_SCAN_ONLY;
  } else if (inquiry_scan_enabled_ && page_scan_enabled_) {
    scan_enable = hci::ScanEnable::INQUIRY_AND_PAGE_SCAN;
  } else {
    scan_enable = hci::ScanEnable::NO_SCANS;
  }

  {
    std::unique_ptr<hci::WriteScanEnableBuilder> packet =
            hci::WriteScanEnableBuilder::Create(scan_enable);
    hci_layer_->EnqueueCommand(std::move(packet),
                               handler_->BindOnceOn(this, &impl::OnCommandComplete));
  }

  {
    std::unique_ptr<hci::ReadScanEnableBuilder> packet = hci::ReadScanEnableBuilder::Create();
    hci_layer_->EnqueueCommand(std::move(packet),
                               handler_->BindOnceOn(this, &impl::OnCommandComplete));
  }
}

void neighbor::ScanModule::impl::ReadScanEnable(hci::ScanEnable scan_enable) {
  switch (scan_enable) {
    case hci::ScanEnable::INQUIRY_SCAN_ONLY:
      inquiry_scan_enabled_ = true;
      page_scan_enabled_ = false;
      break;

    case hci::ScanEnable::PAGE_SCAN_ONLY:
      inquiry_scan_enabled_ = false;
      page_scan_enabled_ = true;
      break;

    case hci::ScanEnable::INQUIRY_AND_PAGE_SCAN:
      inquiry_scan_enabled_ = true;
      page_scan_enabled_ = true;
      break;

    default:
      inquiry_scan_enabled_ = false;
      page_scan_enabled_ = false;
      break;
  }
}

void neighbor::ScanModule::impl::SetInquiryScan(bool enabled) {
  inquiry_scan_enabled_ = enabled;
  WriteScanEnable();
}

void neighbor::ScanModule::impl::SetPageScan(bool enabled) {
  page_scan_enabled_ = enabled;
  WriteScanEnable();
}

bool neighbor::ScanModule::impl::IsInquiryEnabled() const { return inquiry_scan_enabled_; }

bool neighbor::ScanModule::impl::IsPageEnabled() const { return page_scan_enabled_; }

void neighbor::ScanModule::impl::Start() {
  hci_layer_ = module_.GetDependency<hci::HciLayer>();
  handler_ = module_.GetHandler();

  std::unique_ptr<hci::ReadScanEnableBuilder> packet = hci::ReadScanEnableBuilder::Create();
  hci_layer_->EnqueueCommand(std::move(packet),
                             handler_->BindOnceOn(this, &impl::OnCommandComplete));
}

void neighbor::ScanModule::impl::Stop() {
  log::info("inquiry scan enabled:{} page scan enabled:{}", inquiry_scan_enabled_,
            page_scan_enabled_);
}

neighbor::ScanModule::ScanModule() : pimpl_(std::make_unique<impl>(*this)) {}

neighbor::ScanModule::~ScanModule() { pimpl_.reset(); }

void neighbor::ScanModule::SetInquiryScan() { pimpl_->SetInquiryScan(true); }

void neighbor::ScanModule::ClearInquiryScan() { pimpl_->SetInquiryScan(false); }

void neighbor::ScanModule::SetPageScan() { pimpl_->SetPageScan(true); }

void neighbor::ScanModule::ClearPageScan() { pimpl_->SetPageScan(false); }

bool neighbor::ScanModule::IsInquiryEnabled() const { return pimpl_->IsInquiryEnabled(); }

bool neighbor::ScanModule::IsPageEnabled() const { return pimpl_->IsPageEnabled(); }

void neighbor::ScanModule::ListDependencies(ModuleList* list) const { list->add<hci::HciLayer>(); }

void neighbor::ScanModule::Start() { pimpl_->Start(); }

void neighbor::ScanModule::Stop() { pimpl_->Stop(); }

}  // namespace neighbor
}  // namespace bluetooth
