//
// Copyright (C) 2012 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.
//

#include "shill/wimax/wimax.h"

#include <memory>
#include <string>

#include "shill/dhcp/mock_dhcp_config.h"
#include "shill/dhcp/mock_dhcp_provider.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/nice_mock_control.h"
#include "shill/test_event_dispatcher.h"
#include "shill/testing.h"
#include "shill/wimax/mock_wimax_device_proxy.h"
#include "shill/wimax/mock_wimax_provider.h"
#include "shill/wimax/mock_wimax_service.h"

using base::Bind;
using base::Unretained;
using std::string;
using testing::_;
using testing::NiceMock;
using testing::Return;

namespace shill {

namespace {

const char kTestLinkName[] = "wm0";
const char kTestAddress[] = "01:23:45:67:89:ab";
const int kTestInterfaceIndex = 5;
const char kTestPath[] = "/org/chromium/WiMaxManager/Device/6";

}  // namespace

class WiMaxTest : public testing::Test {
 public:
  WiMaxTest()
      : proxy_(new MockWiMaxDeviceProxy()),
        metrics_(&dispatcher_),
        manager_(&control_, &dispatcher_, &metrics_),
        dhcp_config_(new MockDHCPConfig(&control_,
                                        kTestLinkName)),
        device_(new WiMax(&control_, &dispatcher_, &metrics_, &manager_,
                          kTestLinkName, kTestAddress, kTestInterfaceIndex,
                          kTestPath)) {}

  virtual ~WiMaxTest() {}

 protected:
  class Target {
   public:
    virtual ~Target() {}

    MOCK_METHOD1(EnabledStateChanged, void(const Error& error));
  };

  virtual void SetUp() {
    device_->set_dhcp_provider(&dhcp_provider_);
  }

  virtual void TearDown() {
    device_->SelectService(nullptr);
    device_->pending_service_ = nullptr;
  }

  std::unique_ptr<MockWiMaxDeviceProxy> proxy_;
  NiceMockControl control_;
  EventDispatcherForTest dispatcher_;
  NiceMock<MockMetrics> metrics_;
  MockManager manager_;
  MockDHCPProvider dhcp_provider_;
  scoped_refptr<MockDHCPConfig> dhcp_config_;
  WiMaxRefPtr device_;
};

TEST_F(WiMaxTest, Constructor) {
  EXPECT_EQ(kTestPath, device_->path());
  EXPECT_FALSE(device_->scanning());
}

TEST_F(WiMaxTest, StartStop) {
  EXPECT_FALSE(device_->proxy_.get());
  EXPECT_CALL(control_, CreateWiMaxDeviceProxy(_))
      .WillOnce(ReturnAndReleasePointee(&proxy_));
  EXPECT_CALL(*proxy_, Enable(_, _, _));
  EXPECT_CALL(*proxy_, set_networks_changed_callback(_));
  EXPECT_CALL(*proxy_, set_status_changed_callback(_));
  EXPECT_CALL(*proxy_, Disable(_, _, _));
  device_->Start(nullptr, EnabledStateChangedCallback());
  ASSERT_TRUE(device_->proxy_.get());

  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->pending_service_ = service;
  EXPECT_CALL(*service, SetState(Service::kStateIdle));
  device_->networks_.insert("path");
  MockWiMaxProvider provider;
  EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider));
  EXPECT_CALL(provider, OnNetworksChanged());
  device_->StartConnectTimeout();
  device_->Stop(nullptr, EnabledStateChangedCallback());
  EXPECT_TRUE(device_->networks_.empty());
  EXPECT_FALSE(device_->IsConnectTimeoutStarted());
  EXPECT_FALSE(device_->pending_service_);
}

TEST_F(WiMaxTest, OnServiceStopped) {
  scoped_refptr<NiceMock<MockWiMaxService>> service0(
      new NiceMock<MockWiMaxService>(&control_, nullptr, &metrics_, &manager_));
  scoped_refptr<MockWiMaxService> service1(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->SelectService(service0);
  device_->pending_service_ = service1;

  device_->OnServiceStopped(nullptr);
  EXPECT_TRUE(device_->selected_service());
  EXPECT_TRUE(device_->pending_service_);

  device_->OnServiceStopped(service0);
  EXPECT_FALSE(device_->selected_service());
  EXPECT_TRUE(device_->pending_service_);

  device_->OnServiceStopped(service1);
  EXPECT_FALSE(device_->selected_service());
  EXPECT_FALSE(device_->pending_service_);
}

TEST_F(WiMaxTest, OnNetworksChanged) {
  MockWiMaxProvider provider;
  EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider));
  EXPECT_CALL(provider, OnNetworksChanged());
  device_->networks_.insert("foo");
  RpcIdentifiers networks;
  networks.push_back("bar");
  networks.push_back("zoo");
  networks.push_back("bar");
  device_->OnNetworksChanged(networks);
  EXPECT_EQ(2, device_->networks_.size());
  EXPECT_TRUE(ContainsKey(device_->networks_, "bar"));
  EXPECT_TRUE(ContainsKey(device_->networks_, "zoo"));
}

TEST_F(WiMaxTest, OnConnectComplete) {
  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->pending_service_ = service;
  EXPECT_CALL(*service, SetState(_)).Times(0);
  EXPECT_TRUE(device_->pending_service_);
  EXPECT_CALL(*service, SetState(Service::kStateFailure));
  device_->OnConnectComplete(Error(Error::kOperationFailed));
  EXPECT_FALSE(device_->pending_service_);
}

TEST_F(WiMaxTest, OnStatusChanged) {
  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));

  EXPECT_EQ(wimax_manager::kDeviceStatusUninitialized, device_->status_);
  device_->pending_service_ = service;
  EXPECT_CALL(*service, SetState(_)).Times(0);
  EXPECT_CALL(*service, ClearPassphrase()).Times(0);
  device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning);
  EXPECT_TRUE(device_->pending_service_);
  EXPECT_EQ(wimax_manager::kDeviceStatusScanning, device_->status_);

  device_->status_ = wimax_manager::kDeviceStatusConnecting;
  EXPECT_CALL(*service, SetState(Service::kStateFailure));
  EXPECT_CALL(*service, ClearPassphrase()).Times(0);
  device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning);
  EXPECT_FALSE(device_->pending_service_);

  device_->status_ = wimax_manager::kDeviceStatusConnecting;
  device_->SelectService(service);
  EXPECT_CALL(*service, SetState(Service::kStateFailure));
  EXPECT_CALL(*service, SetState(Service::kStateIdle));
  EXPECT_CALL(*service, ClearPassphrase()).Times(0);
  device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning);
  EXPECT_FALSE(device_->selected_service());

  device_->pending_service_ = service;
  device_->SelectService(service);
  EXPECT_CALL(*service, SetState(_)).Times(0);
  EXPECT_CALL(*service, ClearPassphrase()).Times(0);
  device_->OnStatusChanged(wimax_manager::kDeviceStatusConnecting);
  EXPECT_TRUE(device_->pending_service_);
  EXPECT_TRUE(device_->selected_service());
  EXPECT_EQ(wimax_manager::kDeviceStatusConnecting, device_->status_);

  EXPECT_CALL(*service, SetState(Service::kStateIdle));
  device_->SelectService(nullptr);
}

TEST_F(WiMaxTest, UseNoArpGateway) {
  EXPECT_CALL(dhcp_provider_, CreateIPv4Config(kTestLinkName, _, false, _))
      .WillOnce(Return(dhcp_config_));
  device_->AcquireIPConfig();
}

TEST_F(WiMaxTest, DropService) {
  scoped_refptr<NiceMock<MockWiMaxService>> service0(
      new NiceMock<MockWiMaxService>(&control_, nullptr, &metrics_, &manager_));
  scoped_refptr<MockWiMaxService> service1(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->SelectService(service0);
  device_->pending_service_ = service1;
  device_->StartConnectTimeout();

  EXPECT_CALL(*service0, SetState(Service::kStateIdle)).Times(2);
  EXPECT_CALL(*service1, SetState(Service::kStateIdle));
  device_->DropService(Service::kStateIdle);
  EXPECT_FALSE(device_->selected_service());
  EXPECT_FALSE(device_->pending_service_);
  EXPECT_FALSE(device_->IsConnectTimeoutStarted());

  // Expect no crash.
  device_->DropService(Service::kStateFailure);
}

TEST_F(WiMaxTest, OnDeviceVanished) {
  device_->proxy_.reset(proxy_.release());
  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->pending_service_ = service;
  EXPECT_CALL(*service, SetState(Service::kStateIdle));
  device_->OnDeviceVanished();
  EXPECT_FALSE(device_->proxy_.get());
  EXPECT_FALSE(device_->pending_service_);
}

TEST_F(WiMaxTest, OnEnableComplete) {
  MockWiMaxProvider provider;
  EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider));
  RpcIdentifiers networks(1, "path");
  EXPECT_CALL(*proxy_, Networks(_)).WillOnce(Return(networks));
  device_->proxy_.reset(proxy_.release());
  EXPECT_CALL(provider, OnNetworksChanged());
  Target target;
  EXPECT_CALL(target, EnabledStateChanged(_));
  EnabledStateChangedCallback callback(
      Bind(&Target::EnabledStateChanged, Unretained(&target)));
  Error error;
  device_->OnEnableComplete(callback, error);
  EXPECT_EQ(1, device_->networks_.size());
  EXPECT_TRUE(ContainsKey(device_->networks_, "path"));

  EXPECT_TRUE(device_->proxy_.get());
  error.Populate(Error::kOperationFailed);
  EXPECT_CALL(target, EnabledStateChanged(_));
  device_->OnEnableComplete(callback, error);
  EXPECT_FALSE(device_->proxy_.get());
}

TEST_F(WiMaxTest, ConnectTimeout) {
  EXPECT_EQ(&dispatcher_, device_->dispatcher());
  EXPECT_TRUE(device_->connect_timeout_callback_.IsCancelled());
  EXPECT_FALSE(device_->IsConnectTimeoutStarted());
  EXPECT_EQ(WiMax::kDefaultConnectTimeoutSeconds,
            device_->connect_timeout_seconds_);
  device_->connect_timeout_seconds_ = 0;
  device_->StartConnectTimeout();
  EXPECT_FALSE(device_->connect_timeout_callback_.IsCancelled());
  EXPECT_TRUE(device_->IsConnectTimeoutStarted());
  device_->dispatcher_ = nullptr;
  device_->StartConnectTimeout();  // Expect no crash.
  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  device_->pending_service_ = service;
  EXPECT_CALL(*service, SetState(Service::kStateFailure));
  dispatcher_.DispatchPendingEvents();
  EXPECT_TRUE(device_->connect_timeout_callback_.IsCancelled());
  EXPECT_FALSE(device_->IsConnectTimeoutStarted());
  EXPECT_FALSE(device_->pending_service_);
}

TEST_F(WiMaxTest, ConnectTo) {
  static const char kPath[] = "/network/path";
  scoped_refptr<MockWiMaxService> service(
      new MockWiMaxService(&control_, nullptr, &metrics_, &manager_));
  EXPECT_CALL(*service, SetState(Service::kStateAssociating));
  device_->status_ = wimax_manager::kDeviceStatusScanning;
  EXPECT_CALL(*service, GetNetworkObjectPath()).WillOnce(Return(kPath));
  EXPECT_CALL(*proxy_, Connect(kPath, _, _, _, _))
      .WillOnce(SetErrorTypeInArgument<2>(Error::kSuccess));
  device_->proxy_.reset(proxy_.release());
  Error error;
  device_->ConnectTo(service, &error);
  EXPECT_TRUE(error.IsSuccess());
  EXPECT_EQ(service.get(), device_->pending_service_.get());
  EXPECT_EQ(wimax_manager::kDeviceStatusUninitialized, device_->status_);
  EXPECT_TRUE(device_->IsConnectTimeoutStarted());

  device_->ConnectTo(service, &error);
  EXPECT_EQ(Error::kInProgress, error.type());

  device_->pending_service_ = nullptr;
}

TEST_F(WiMaxTest, IsIdle) {
  EXPECT_TRUE(device_->IsIdle());
  scoped_refptr<NiceMock<MockWiMaxService>> service(
      new NiceMock<MockWiMaxService>(&control_, nullptr, &metrics_, &manager_));
  device_->pending_service_ = service;
  EXPECT_FALSE(device_->IsIdle());
  device_->pending_service_ = nullptr;
  device_->SelectService(service);
  EXPECT_FALSE(device_->IsIdle());
}

}  // namespace shill