// Copyright 2013 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 "ash/shelf/shelf_window_watcher.h"

#include "ash/display/display_controller.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_item_delegate_manager.h"
#include "ash/shelf/shelf_model.h"
#include "ash/shelf/shelf_util.h"
#include "ash/shelf/shelf_window_watcher_item_delegate.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/memory/scoped_ptr.h"
#include "ui/aura/window.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/screen.h"
#include "ui/wm/public/activation_client.h"

namespace {

// Sets ShelfItem property by using the value of |details|.
void SetShelfItemDetailsForShelfItem(ash::ShelfItem* item,
                                     const ash::ShelfItemDetails& details) {
  item->type = details.type;
  if (details.image_resource_id != ash::kInvalidImageResourceID) {
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    item->image = *rb.GetImageSkiaNamed(details.image_resource_id);
  }
}

// Returns true if |window| has a ShelfItem added by ShelfWindowWatcher.
bool HasShelfItemForWindow(aura::Window* window) {
  if (ash::GetShelfItemDetailsForWindow(window) != NULL &&
      ash::GetShelfIDForWindow(window) != ash::kInvalidShelfID)
    return true;
  return false;
}

// Returns true if |window| is in the process of being dragged.
bool IsDragging(aura::Window* window) {
  return ash::wm::GetWindowState(window)->is_dragged();
}

}  // namespace

namespace ash {

ShelfWindowWatcher::RootWindowObserver::RootWindowObserver(
    ShelfWindowWatcher* window_watcher)
    : window_watcher_(window_watcher) {
}

ShelfWindowWatcher::RootWindowObserver::~RootWindowObserver() {
}

void ShelfWindowWatcher::RootWindowObserver::OnWindowDestroying(
    aura::Window* window) {
  window_watcher_->OnRootWindowRemoved(window);
}

ShelfWindowWatcher::RemovedWindowObserver::RemovedWindowObserver(
    ShelfWindowWatcher* window_watcher)
    : window_watcher_(window_watcher) {
}

ShelfWindowWatcher::RemovedWindowObserver::~RemovedWindowObserver() {
}

void ShelfWindowWatcher::RemovedWindowObserver::OnWindowParentChanged(
    aura::Window* window,
    aura::Window* parent) {
  // When |parent| is NULL, this |window| will be destroyed. In that case, its
  // item will be removed at OnWindowDestroyed().
  if (!parent)
    return;

  // When |parent| is changed from default container to docked container
  // during the dragging, |window|'s item should not be removed because it will
  // be re-parented to default container again after finishing the dragging.
  // We don't need to check |parent| is default container because this observer
  // is already removed from |window| when |window| is re-parented to default
  // container.
  if (IsDragging(window) && parent->id() == kShellWindowId_DockedContainer)
    return;

  // When |window| is re-parented to other containers or |window| is re-parented
  // not to |docked_container| during the dragging, its item should be removed
  // and stop observing this |window|.
  window_watcher_->FinishObservingRemovedWindow(window);
}

void ShelfWindowWatcher::RemovedWindowObserver::OnWindowDestroyed(
    aura::Window* window) {
  DCHECK(HasShelfItemForWindow(window));
  window_watcher_->FinishObservingRemovedWindow(window);
}

ShelfWindowWatcher::ShelfWindowWatcher(
    ShelfModel* model,
    ShelfItemDelegateManager* item_delegate_manager)
    : model_(model),
      item_delegate_manager_(item_delegate_manager),
      root_window_observer_(this),
      removed_window_observer_(this),
      observed_windows_(this),
      observed_root_windows_(&root_window_observer_),
      observed_removed_windows_(&removed_window_observer_),
      observed_activation_clients_(this) {
  // We can't assume all RootWindows have the same ActivationClient.
  // Add a RootWindow and its ActivationClient to the observed list.
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  for (aura::Window::Windows::const_iterator it = root_windows.begin();
       it != root_windows.end(); ++it)
    OnRootWindowAdded(*it);

  Shell::GetScreen()->AddObserver(this);
}

ShelfWindowWatcher::~ShelfWindowWatcher() {
  Shell::GetScreen()->RemoveObserver(this);
}

void ShelfWindowWatcher::AddShelfItem(aura::Window* window) {
  const ShelfItemDetails* item_details =
      GetShelfItemDetailsForWindow(window);
  ShelfItem item;
  ShelfID id = model_->next_id();
  item.status = wm::IsActiveWindow(window) ? STATUS_ACTIVE: STATUS_RUNNING;
  SetShelfItemDetailsForShelfItem(&item, *item_details);
  SetShelfIDForWindow(id, window);
  scoped_ptr<ShelfItemDelegate> item_delegate(
      new ShelfWindowWatcherItemDelegate(window, model_));
  // |item_delegate| is owned by |item_delegate_manager_|.
  item_delegate_manager_->SetShelfItemDelegate(id, item_delegate.Pass());
  model_->Add(item);
}

void ShelfWindowWatcher::RemoveShelfItem(aura::Window* window) {
  model_->RemoveItemAt(model_->ItemIndexByID(GetShelfIDForWindow(window)));
  SetShelfIDForWindow(kInvalidShelfID, window);
}

void ShelfWindowWatcher::OnRootWindowAdded(aura::Window* root_window) {
  // |observed_activation_clients_| can have the same ActivationClient multiple
  // times - which would be handled by the |observed_activation_clients_|.
  observed_activation_clients_.Add(
      aura::client::GetActivationClient(root_window));
  observed_root_windows_.Add(root_window);

  aura::Window* default_container = Shell::GetContainer(
      root_window,
      kShellWindowId_DefaultContainer);
  observed_windows_.Add(default_container);
  for (size_t i = 0; i < default_container->children().size(); ++i)
    observed_windows_.Add(default_container->children()[i]);
}

void ShelfWindowWatcher::OnRootWindowRemoved(aura::Window* root_window) {
  observed_root_windows_.Remove(root_window);
  observed_activation_clients_.Remove(
      aura::client::GetActivationClient(root_window));
}

void ShelfWindowWatcher::UpdateShelfItemStatus(aura::Window* window,
                                               bool is_active) {
  int index = GetShelfItemIndexForWindow(window);
  DCHECK_GE(index, 0);

  ShelfItem item = model_->items()[index];
  item.status = is_active ? STATUS_ACTIVE : STATUS_RUNNING;
  model_->Set(index, item);
}

int ShelfWindowWatcher::GetShelfItemIndexForWindow(
    aura::Window* window) const {
  return model_->ItemIndexByID(GetShelfIDForWindow(window));
}

void ShelfWindowWatcher::StartObservingRemovedWindow(aura::Window* window) {
  observed_removed_windows_.Add(window);
}

void ShelfWindowWatcher::FinishObservingRemovedWindow(aura::Window* window) {
  observed_removed_windows_.Remove(window);
  RemoveShelfItem(window);
}

void ShelfWindowWatcher::OnWindowActivated(aura::Window* gained_active,
                                           aura::Window* lost_active) {
  if (gained_active && HasShelfItemForWindow(gained_active))
    UpdateShelfItemStatus(gained_active, true);
  if (lost_active && HasShelfItemForWindow(lost_active))
    UpdateShelfItemStatus(lost_active, false);
}

void ShelfWindowWatcher::OnWindowAdded(aura::Window* window) {
  observed_windows_.Add(window);

  if (observed_removed_windows_.IsObserving(window)) {
    // When |window| is added and it is already observed by
    // |dragged_window_observer_|, |window| already has its item.
    DCHECK(HasShelfItemForWindow(window));
    observed_removed_windows_.Remove(window);
    return;
  }

  // Add ShelfItem if |window| already has a ShelfItemDetails when it is
  // created. Don't make a new ShelfItem for the re-parented |window| that
  // already has a ShelfItem.
  if (GetShelfIDForWindow(window) == kInvalidShelfID &&
      GetShelfItemDetailsForWindow(window))
    AddShelfItem(window);
}

void ShelfWindowWatcher::OnWillRemoveWindow(aura::Window* window) {
  // Remove a child window of default container.
  if (observed_windows_.IsObserving(window))
    observed_windows_.Remove(window);

  // Don't remove |window| item immediately. Instead, defer handling of removing
  // |window|'s item to RemovedWindowObserver because |window| could be added
  // again to default container.
  if (HasShelfItemForWindow(window))
    StartObservingRemovedWindow(window);
}

void ShelfWindowWatcher::OnWindowDestroying(aura::Window* window) {
  // Remove the default container.
  if (observed_windows_.IsObserving(window))
    observed_windows_.Remove(window);
}

void ShelfWindowWatcher::OnWindowPropertyChanged(aura::Window* window,
                                                 const void* key,
                                                 intptr_t old) {
  if (key != kShelfItemDetailsKey)
    return;

  if (GetShelfItemDetailsForWindow(window) == NULL) {
    // Removes ShelfItem for |window| when it has a ShelfItem.
    if (reinterpret_cast<ShelfItemDetails*>(old) != NULL)
      RemoveShelfItem(window);
    return;
  }

  // When ShelfItemDetails is changed, update ShelfItem.
  if (HasShelfItemForWindow(window)) {
    int index = GetShelfItemIndexForWindow(window);
    DCHECK_GE(index, 0);
    ShelfItem item = model_->items()[index];
    const ShelfItemDetails* details =
        GetShelfItemDetailsForWindow(window);
    SetShelfItemDetailsForShelfItem(&item, *details);
    model_->Set(index, item);
    return;
  }

  // Creates a new ShelfItem for |window|.
  AddShelfItem(window);
}

void ShelfWindowWatcher::OnDisplayAdded(const gfx::Display& new_display) {
  // Add a new RootWindow and its ActivationClient to observed list.
  aura::Window* root_window = Shell::GetInstance()->display_controller()->
      GetRootWindowForDisplayId(new_display.id());

  // When the primary root window's display get removed, the existing root
  // window is taken over by the new display and the observer is already set.
  if (!observed_root_windows_.IsObserving(root_window))
    OnRootWindowAdded(root_window);
}

void ShelfWindowWatcher::OnDisplayRemoved(const gfx::Display& old_display) {
  // When this is called, RootWindow of |old_display| is already removed.
  // Instead, we remove an observer from RootWindow and ActivationClient in the
  // OnRootWindowDestroyed().
  // Do nothing here.
}

void ShelfWindowWatcher::OnDisplayMetricsChanged(const gfx::Display&,
                                                 uint32_t) {
}

}  // namespace ash