// Copyright (c) 2010 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 "chrome/browser/net/websocket_experiment/websocket_experiment_runner.h"

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/task.h"
#include "base/string_util.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/browser_thread.h"
#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
#include "net/websockets/websocket.h"

namespace chrome_browser_net_websocket_experiment {

static const char *kExperimentHost = "websocket-experiment.chromium.org";
static const int kAlternativePort = 61985;

// Hold reference while experiment is running.
static scoped_refptr<WebSocketExperimentRunner> runner;

/* static */
void WebSocketExperimentRunner::Start() {
  DCHECK(!runner.get());

  // After June 30, 2011 builds, it will always be in default group.
  scoped_refptr<base::FieldTrial> trial(
      new base::FieldTrial(
          "WebSocketExperiment", 1000, "default", 2011, 6, 30));
  int active = trial->AppendGroup("active", 5);  // 0.5% in active group.

  bool run_experiment = (trial->group() == active);
#ifndef NDEBUG
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  std::string experiment_host = command_line.GetSwitchValueASCII(
      switches::kWebSocketLiveExperimentHost);
  if (!experiment_host.empty())
    run_experiment = true;
#else
  run_experiment = false;
#endif
  if (!run_experiment)
    return;

  runner = new WebSocketExperimentRunner;
  runner->Run();
}

/* static */
void WebSocketExperimentRunner::Stop() {
  if (runner.get())
    runner->Cancel();
  runner = NULL;
}

WebSocketExperimentRunner::WebSocketExperimentRunner()
    : next_state_(STATE_NONE),
      task_state_(STATE_NONE),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          task_callback_(this, &WebSocketExperimentRunner::OnTaskCompleted)) {
  WebSocketExperimentTask::InitHistogram();
  InitConfig();
}

WebSocketExperimentRunner::~WebSocketExperimentRunner() {
  DCHECK(!task_.get());
  WebSocketExperimentTask::ReleaseHistogram();
}

void WebSocketExperimentRunner::Run() {
  DCHECK_EQ(next_state_, STATE_NONE);
  next_state_ = STATE_RUN_WS;
  BrowserThread::PostDelayedTask(
      BrowserThread::IO,
      FROM_HERE,
      NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop),
      config_.initial_delay_ms);
}

void WebSocketExperimentRunner::Cancel() {
  next_state_ = STATE_NONE;
  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop));
}

void WebSocketExperimentRunner::InitConfig() {
  config_.initial_delay_ms = 5 * 60 * 1000;  // 5 mins
  config_.next_delay_ms = 12 * 60 * 60 * 1000;  // 12 hours

  std::string experiment_host = kExperimentHost;
#ifndef NDEBUG
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  std::string experiment_host_override = command_line.GetSwitchValueASCII(
      switches::kWebSocketLiveExperimentHost);
  if (!experiment_host_override.empty()) {
    experiment_host = experiment_host_override;
    config_.initial_delay_ms = 5 * 1000;  // 5 secs.
  }
#endif

  WebSocketExperimentTask::Config* config;
  WebSocketExperimentTask::Config task_config;

  task_config.protocol_version = net::WebSocket::DEFAULT_VERSION;
  config = &config_.ws_config[STATE_RUN_WS - STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("ws://%s/live_exp", experiment_host.c_str()));
  config->ws_location =
      StringPrintf("ws://%s/live_exp", experiment_host.c_str());
  config->http_url =
      GURL(StringPrintf("http://%s/", experiment_host.c_str()));

  config = &config_.ws_config[STATE_RUN_WSS - STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("wss://%s/live_exp", experiment_host.c_str()));
  config->ws_location =
      StringPrintf("wss://%s/live_exp", experiment_host.c_str());
  config->http_url =
      GURL(StringPrintf("https://%s/", experiment_host.c_str()));

  config = &config_.ws_config[STATE_RUN_WS_NODEFAULT_PORT -
                              STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("ws://%s:%d/live_exp",
                        experiment_host.c_str(), kAlternativePort));
  config->ws_location =
      StringPrintf("ws://%s:%d/live_exp",
                   experiment_host.c_str(), kAlternativePort);
  config->http_url =
      GURL(StringPrintf("http://%s:%d/",
                        experiment_host.c_str(), kAlternativePort));

  task_config.protocol_version = net::WebSocket::DRAFT75;
  config = &config_.ws_config[STATE_RUN_WS_DRAFT75 - STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("ws://%s/live_exp", experiment_host.c_str()));
  config->ws_location =
      StringPrintf("ws://%s/live_exp", experiment_host.c_str());
  config->http_url =
      GURL(StringPrintf("http://%s/", experiment_host.c_str()));

  config = &config_.ws_config[STATE_RUN_WSS_DRAFT75 - STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("wss://%s/live_exp", experiment_host.c_str()));
  config->ws_location =
      StringPrintf("wss://%s/live_exp", experiment_host.c_str());
  config->http_url =
      GURL(StringPrintf("https://%s/", experiment_host.c_str()));

  config = &config_.ws_config[STATE_RUN_WS_NODEFAULT_PORT_DRAFT75 -
                              STATE_RUN_WS];
  *config = task_config;
  config->url =
      GURL(StringPrintf("ws://%s:%d/live_exp",
                        experiment_host.c_str(), kAlternativePort));
  config->ws_location =
      StringPrintf("ws://%s:%d/live_exp",
                   experiment_host.c_str(), kAlternativePort);
  config->http_url =
      GURL(StringPrintf("http://%s:%d/",
                        experiment_host.c_str(), kAlternativePort));

}

void WebSocketExperimentRunner::DoLoop() {
  if (next_state_ == STATE_NONE) {
    if (task_.get()) {
      AddRef();  // Release in OnTaskCompleted after Cancelled.
      task_->Cancel();
    }
    return;
  }

  State state = next_state_;
  task_state_ = STATE_NONE;
  next_state_ = STATE_NONE;

  switch (state) {
    case STATE_IDLE:
      task_.reset();
      next_state_ = STATE_RUN_WS;
      BrowserThread::PostDelayedTask(
          BrowserThread::IO,
          FROM_HERE,
          NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop),
          config_.next_delay_ms);
      break;
    case STATE_RUN_WS:
    case STATE_RUN_WSS:
    case STATE_RUN_WS_NODEFAULT_PORT:
    case STATE_RUN_WS_DRAFT75:
    case STATE_RUN_WSS_DRAFT75:
    case STATE_RUN_WS_NODEFAULT_PORT_DRAFT75:
      task_.reset(new WebSocketExperimentTask(
          config_.ws_config[state - STATE_RUN_WS], &task_callback_));
      task_state_ = state;
      if (static_cast<State>(state + 1) == NUM_STATES)
        next_state_ = STATE_IDLE;
      else
        next_state_ = static_cast<State>(state + 1);
      break;
    default:
      NOTREACHED();
      break;
  }
  if (task_.get())
    task_->Run();
}

void WebSocketExperimentRunner::OnTaskCompleted(int result) {
  if (next_state_ == STATE_NONE) {
    task_.reset();
    // Task is Canceled.
    DVLOG(1) << "WebSocketExperiment Task is canceled.";
    Release();
    return;
  }
  task_->SaveResult();
  task_.reset();

  DoLoop();
}

}  // namespace chrome_browser_net_websocket_experiment