// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/device/geolocation/wifi_data_provider_mac.h"

#import <CoreWLAN/CoreWLAN.h>
#import <Foundation/Foundation.h>

#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "services/device/geolocation/wifi_data_provider_common.h"
#include "services/device/geolocation/wifi_data_provider_handle.h"
#include "services/device/public/mojom/geolocation_internals.mojom.h"

namespace device {

namespace {

class CoreWlanApi : public WifiDataProviderCommon::WlanApiInterface {
 public:
  CoreWlanApi() = default;

  CoreWlanApi(const CoreWlanApi&) = delete;
  CoreWlanApi& operator=(const CoreWlanApi&) = delete;

  // WlanApiInterface:
  void GetAccessPointData(
      base::OnceCallback<void(std::unique_ptr<WifiData::AccessPointDataSet>)>
          callback) override;

 private:
  CWWiFiClient* __strong wifi_client_ = [CWWiFiClient sharedWiFiClient];
};

void CoreWlanApi::GetAccessPointData(
    base::OnceCallback<void(std::unique_ptr<WifiData::AccessPointDataSet>)>
        callback) {
  @autoreleasepool {
    auto data = std::make_unique<WifiData::AccessPointDataSet>();
    NSArray<CWInterface*>* interfaces = wifi_client_.interfaces;
    NSUInteger interface_error_count = 0;
    for (CWInterface* interface in interfaces) {
      NSError* err = nil;
      NSSet<CWNetwork*>* scan = [interface scanForNetworksWithName:nil
                                                             error:&err];
      const int error_code = err.code;
      const int count = scan.count;
      // We could get an error code but count != 0 if the scan was interrupted,
      // for example. For our purposes this is not fatal, so process as normal.
      if (error_code && count == 0) {
        DLOG(WARNING) << interface.interfaceName
                      << ": CoreWLAN scan failed with error " << error_code;
        ++interface_error_count;
        continue;
      }

      DVLOG(1) << interface.interfaceName << ": found " << count << " wifi APs";

      for (CWNetwork* network in scan) {
        DCHECK(network);
        mojom::AccessPointData access_point_data;
        // -[CWNetwork bssid] uses colons to separate the components of the MAC
        // address, but AccessPointData requires they be separated with a dash.
        access_point_data.mac_address = base::SysNSStringToUTF8([network.bssid
            stringByReplacingOccurrencesOfString:@":"
                                      withString:@"-"]);
        access_point_data.radio_signal_strength = network.rssiValue;
        access_point_data.channel = network.wlanChannel.channelNumber;
        access_point_data.signal_to_noise =
            access_point_data.radio_signal_strength - network.noiseMeasurement;
        data->insert(access_point_data);
      }
    }

    // Return true even if some interfaces failed to scan, so long as at least
    // one interface did not fail.
    if (interface_error_count > 0 &&
        interfaces.count <= interface_error_count) {
      std::move(callback).Run(nullptr);
    } else {
      std::move(callback).Run(std::move(data));
    }
  }
}

// The time periods, in milliseconds, between successive polls of the wifi data.
const int kDefaultPollingInterval = 120000;                // 2 mins
const int kNoChangePollingInterval = 300000;               // 5 mins
const int kTwoNoChangePollingInterval = 600000;            // 10 mins
const int kNoWifiPollingIntervalMilliseconds = 20 * 1000;  // 20s

}  // namespace

// static
WifiDataProvider* WifiDataProviderHandle::DefaultFactoryFunction() {
  return new WifiDataProviderMac();
}

WifiDataProviderMac::WifiDataProviderMac() = default;

WifiDataProviderMac::~WifiDataProviderMac() = default;

std::unique_ptr<WifiDataProviderMac::WlanApiInterface>
WifiDataProviderMac::CreateWlanApi() {
  return std::make_unique<CoreWlanApi>();
}

std::unique_ptr<WifiPollingPolicy> WifiDataProviderMac::CreatePollingPolicy() {
  return std::make_unique<GenericWifiPollingPolicy<
      kDefaultPollingInterval, kNoChangePollingInterval,
      kTwoNoChangePollingInterval, kNoWifiPollingIntervalMilliseconds>>();
}

}  // namespace device
