// Copyright (c) 2011 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.

#ifndef CHROME_BROWSER_NET_CONNECTION_TESTER_H_
#define CHROME_BROWSER_NET_CONNECTION_TESTER_H_
#pragma once

#include <vector>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "googleurl/src/gurl.h"
#include "net/base/completion_callback.h"

namespace net {
class URLRequestContext;
}  // namespace net

// ConnectionTester runs a suite of tests (also called "experiments"),
// to try and discover why loading a particular URL is failing with an error
// code.
//
// For example, one reason why the URL might have failed, is that the
// network requires the URL to be routed through a proxy, however chrome is
// not configured for that.
//
// The above issue might be detected by running test that fetches the URL using
// auto-detect and seeing if it works this time. Or even by retrieving the
// settings from another installed browser and trying with those.
//
// USAGE:
//
// To run the test suite, create an instance of ConnectionTester and then call
// RunAllTests().
//
// This starts a sequence of tests, which will complete asynchronously.
// The ConnectionTester object can be deleted at any time, and it will abort
// any of the in-progress tests.
//
// As tests are started or completed, notification will be sent through the
// "Delegate" object.

class ConnectionTester {
 public:
  // This enum lists the possible proxy settings configurations.
  enum ProxySettingsExperiment {
    // Do not use any proxy.
    PROXY_EXPERIMENT_USE_DIRECT = 0,

    // Use the system proxy settings.
    PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS,

    // Use Firefox's proxy settings if they are available.
    PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS,

    // Use proxy auto-detect.
    PROXY_EXPERIMENT_USE_AUTO_DETECT,

    PROXY_EXPERIMENT_COUNT,
  };

  // This enum lists the possible host resolving configurations.
  enum HostResolverExperiment {
    // Use a default host resolver implementation.
    HOST_RESOLVER_EXPERIMENT_PLAIN = 0,

    // Disable IPv6 host resolving.
    HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6,

    // Probe for IPv6 support.
    HOST_RESOLVER_EXPERIMENT_IPV6_PROBE,

    HOST_RESOLVER_EXPERIMENT_COUNT,
  };

  // The "Experiment" structure describes an individual test to run.
  struct Experiment {
    Experiment(const GURL& url,
               ProxySettingsExperiment proxy_settings_experiment,
               HostResolverExperiment host_resolver_experiment)
        : url(url),
          proxy_settings_experiment(proxy_settings_experiment),
          host_resolver_experiment(host_resolver_experiment) {
    }

    // The URL to try and fetch.
    GURL url;

    // The proxy settings to use.
    ProxySettingsExperiment proxy_settings_experiment;

    // The host resolver settings to use.
    HostResolverExperiment host_resolver_experiment;
  };

  typedef std::vector<Experiment> ExperimentList;

  // "Delegate" is an interface for receiving start and completion notification
  // of individual tests that are run by the ConnectionTester.
  //
  // NOTE: do not delete the ConnectionTester when executing within one of the
  // delegate methods.
  class Delegate {
   public:
    virtual ~Delegate() {}

    // Called once the test suite is about to start.
    virtual void OnStartConnectionTestSuite() = 0;

    // Called when an individual experiment is about to be started.
    virtual void OnStartConnectionTestExperiment(
        const Experiment& experiment) = 0;

    // Called when an individual experiment has completed.
    //   |experiment| - the experiment that has completed.
    //   |result| - the net error that the experiment completed with
    //              (or net::OK if it was success).
    virtual void OnCompletedConnectionTestExperiment(
        const Experiment& experiment,
        int result) = 0;

    // Called once ALL tests have completed.
    virtual void OnCompletedConnectionTestSuite() = 0;
  };

  // Constructs a ConnectionTester that notifies test progress to |delegate|.
  // |delegate| is owned by the caller, and must remain valid for the lifetime
  // of ConnectionTester.
  ConnectionTester(Delegate* delegate,
                   net::URLRequestContext* proxy_request_context);

  // Note that destruction cancels any in-progress tests.
  ~ConnectionTester();

  // Starts running the test suite on |url|. Notification of progress is sent to
  // |delegate_|.
  void RunAllTests(const GURL& url);

  // Returns a text string explaining what |experiment| is testing.
  static string16 ProxySettingsExperimentDescription(
      ProxySettingsExperiment experiment);
  static string16 HostResolverExperimentDescription(
      HostResolverExperiment experiment);

 private:
  // Internally each experiment run by ConnectionTester is handled by a
  // "TestRunner" instance.
  class TestRunner;
  friend class TestRunner;

  // Fills |list| with the set of all possible experiments for |url|.
  static void GetAllPossibleExperimentCombinations(const GURL& url,
                                                   ExperimentList* list);

  // Starts the next experiment from |remaining_experiments_|.
  void StartNextExperiment();

  // Callback for when |current_test_runner_| finishes.
  void OnExperimentCompleted(int result);

  // Returns the experiment at the front of our list.
  const Experiment& current_experiment() const {
    return remaining_experiments_.front();
  }

  // The object to notify test progress to.
  Delegate* delegate_;

  // The current in-progress test, or NULL if there is no active test.
  scoped_ptr<TestRunner> current_test_runner_;

  // The ordered list of experiments to try next. The experiment at the front
  // of the list is the one currently in progress.
  ExperimentList remaining_experiments_;

  const scoped_refptr<net::URLRequestContext> proxy_request_context_;

  DISALLOW_COPY_AND_ASSIGN(ConnectionTester);
};

#endif  // CHROME_BROWSER_NET_CONNECTION_TESTER_H_