// 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.

#include "chrome/browser/automation/automation_tab_helper.h"

#include <algorithm>

#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "chrome/common/automation_messages.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"

TabEventObserver::TabEventObserver() { }

TabEventObserver::~TabEventObserver() {
  for (size_t i = 0; i < event_sources_.size(); ++i) {
    if (event_sources_[i])
      event_sources_[i]->RemoveObserver(this);
  }
}

void TabEventObserver::StartObserving(AutomationTabHelper* tab_helper) {
  tab_helper->AddObserver(this);
  event_sources_.push_back(tab_helper->AsWeakPtr());
}

void TabEventObserver::StopObserving(AutomationTabHelper* tab_helper) {
  tab_helper->RemoveObserver(this);
  EventSourceVector::iterator iter =
      std::find(event_sources_.begin(), event_sources_.end(), tab_helper);
  if (iter != event_sources_.end())
    event_sources_.erase(iter);
}

AutomationTabHelper::AutomationTabHelper(TabContents* tab_contents)
    : TabContentsObserver(tab_contents),
      is_loading_(false) {
}

AutomationTabHelper::~AutomationTabHelper() { }

void AutomationTabHelper::AddObserver(TabEventObserver* observer) {
  observers_.AddObserver(observer);
}

void AutomationTabHelper::RemoveObserver(TabEventObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool AutomationTabHelper::has_pending_loads() const {
  return is_loading_ || !pending_client_redirects_.empty();
}

void AutomationTabHelper::DidStartLoading() {
  if (is_loading_) {
    // DidStartLoading is often called twice. Once when the renderer sends a
    // load start message, and once when the browser calls it directly as a
    // result of some user-initiated navigation.
    VLOG(1) << "Received DidStartLoading while loading already started.";
    return;
  }
  bool had_pending_loads = has_pending_loads();
  is_loading_ = true;
  if (!had_pending_loads) {
    FOR_EACH_OBSERVER(TabEventObserver, observers_,
                      OnFirstPendingLoad(tab_contents()));
  }
}

void AutomationTabHelper::DidStopLoading() {
  if (!is_loading_) {
    LOG(WARNING) << "Received DidStopLoading while loading already stopped.";
    return;
  }
  is_loading_ = false;
  if (!has_pending_loads()) {
    FOR_EACH_OBSERVER(TabEventObserver, observers_,
                      OnNoMorePendingLoads(tab_contents()));
  }
}

void AutomationTabHelper::RenderViewGone() {
  OnTabOrRenderViewDestroyed(tab_contents());
}

void AutomationTabHelper::TabContentsDestroyed(TabContents* tab_contents) {
  OnTabOrRenderViewDestroyed(tab_contents);
}

void AutomationTabHelper::OnTabOrRenderViewDestroyed(
    TabContents* tab_contents) {
  if (has_pending_loads()) {
    is_loading_ = false;
    pending_client_redirects_.clear();
    FOR_EACH_OBSERVER(TabEventObserver, observers_,
                      OnNoMorePendingLoads(tab_contents));
  }
}

bool AutomationTabHelper::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  bool msg_is_good = true;
  IPC_BEGIN_MESSAGE_MAP_EX(AutomationTabHelper, message, msg_is_good)
    IPC_MESSAGE_HANDLER(AutomationMsg_WillPerformClientRedirect,
                        OnWillPerformClientRedirect)
    IPC_MESSAGE_HANDLER(AutomationMsg_DidCompleteOrCancelClientRedirect,
                        OnDidCompleteOrCancelClientRedirect)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP_EX()
  if (!msg_is_good) {
    LOG(ERROR) << "Failed to deserialize an IPC message";
  }
  return handled;
}

void AutomationTabHelper::OnWillPerformClientRedirect(
    int64 frame_id, double delay_seconds) {
  // Ignore all non-zero delays.
  // TODO(kkania): Handle timed redirects.
  if (delay_seconds > 0) {
    LOG(WARNING) << "Ignoring timed redirect scheduled for " << delay_seconds
                 << " seconds later. Will not wait for the redirect to occur";
    return;
  }

  bool first_pending_load = !has_pending_loads();
  pending_client_redirects_.insert(frame_id);
  if (first_pending_load) {
    FOR_EACH_OBSERVER(TabEventObserver, observers_,
                      OnFirstPendingLoad(tab_contents()));
  }
}

void AutomationTabHelper::OnDidCompleteOrCancelClientRedirect(int64 frame_id) {
  std::set<int64>::iterator iter =
      pending_client_redirects_.find(frame_id);
  // It is possible that we did not track the redirect becasue it had a non-zero
  // delay. See the comment in |OnWillPerformClientRedirect|.
  if (iter != pending_client_redirects_.end()) {
    pending_client_redirects_.erase(iter);
    if (!has_pending_loads()) {
      FOR_EACH_OBSERVER(TabEventObserver, observers_,
                        OnNoMorePendingLoads(tab_contents()));
    }
  }
}