// Copyright 2015 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 "services/ui/ws/server_window_drawn_tracker.h"

#include "services/ui/ws/server_window.h"
#include "services/ui/ws/server_window_drawn_tracker_observer.h"

namespace ui {

namespace ws {

ServerWindowDrawnTracker::ServerWindowDrawnTracker(
    ServerWindow* window,
    ServerWindowDrawnTrackerObserver* observer)
    : window_(window), observer_(observer), drawn_(window->IsDrawn()) {
  AddObservers();
}

ServerWindowDrawnTracker::~ServerWindowDrawnTracker() {
  RemoveObservers();
}

void ServerWindowDrawnTracker::SetDrawn(ServerWindow* ancestor, bool drawn) {
  // If |windows_| is empty when this code runs, that means |window_| has been
  // destroyed. So set |window_| to nullptr, but make sure the right value is
  // sent to OnDrawnStateChanged().
  ServerWindow* window = window_;
  if (windows_.empty())
    window_ = nullptr;

  if (drawn == drawn_)
    return;

  drawn_ = drawn;
  observer_->OnDrawnStateChanged(ancestor, window, drawn);
}

void ServerWindowDrawnTracker::AddObservers() {
  if (!window_)
    return;

  for (ServerWindow* v = window_; v; v = v->parent()) {
    v->AddObserver(this);
    windows_.insert(v);
  }
}

void ServerWindowDrawnTracker::RemoveObservers() {
  for (ServerWindow* window : windows_)
    window->RemoveObserver(this);

  windows_.clear();
}

void ServerWindowDrawnTracker::OnWindowDestroying(ServerWindow* window) {
  if (!drawn_)
    return;
  observer_->OnDrawnStateWillChange(window->parent(), window_, false);
}

void ServerWindowDrawnTracker::OnWindowDestroyed(ServerWindow* window) {
  // As windows are removed before being destroyed, resulting in
  // OnWindowHierarchyChanged() and us removing ourself as an observer, the only
  // window we should ever get notified of destruction on is |window_|.
  DCHECK_EQ(window, window_);
  RemoveObservers();
  SetDrawn(nullptr, false);
}

void ServerWindowDrawnTracker::OnWillChangeWindowHierarchy(
    ServerWindow* window,
    ServerWindow* new_parent,
    ServerWindow* old_parent) {
  bool new_is_drawn = new_parent && new_parent->IsDrawn();
  if (new_is_drawn) {
    for (ServerWindow* w = window_; new_is_drawn && w != old_parent;
         w = w->parent()) {
      new_is_drawn = w->visible();
    }
  }
  if (drawn_ != new_is_drawn) {
    observer_->OnDrawnStateWillChange(new_is_drawn ? nullptr : old_parent,
                                      window_, new_is_drawn);
  }
}

void ServerWindowDrawnTracker::OnWindowHierarchyChanged(
    ServerWindow* window,
    ServerWindow* new_parent,
    ServerWindow* old_parent) {
  RemoveObservers();
  AddObservers();
  const bool is_drawn = window_->IsDrawn();
  SetDrawn(is_drawn ? nullptr : old_parent, is_drawn);
}

void ServerWindowDrawnTracker::OnWillChangeWindowVisibility(
    ServerWindow* window) {
  bool will_change = false;
  if (drawn_) {
    // If |window_| is currently drawn, then any change of visibility of the
    // windows will toggle the drawn status.
    will_change = true;
  } else {
    // If |window| is currently visible, then it's becoming invisible, and so
    // |window_| will remain not drawn.
    if (window->visible()) {
      will_change = false;
    } else {
      bool is_drawn = (window->GetRoot() == window) ||
          (window->parent() && window->parent()->IsDrawn());
      if (is_drawn) {
        for (ServerWindow* w = window_; is_drawn && w != window;
             w = w->parent())
          is_drawn = w->visible();
      }
      will_change = drawn_ != is_drawn;
    }
  }
  if (will_change) {
    bool new_is_drawn = !drawn_;
    observer_->OnDrawnStateWillChange(new_is_drawn ? nullptr : window->parent(),
                                      window_, new_is_drawn);
  }
}

void ServerWindowDrawnTracker::OnWindowVisibilityChanged(ServerWindow* window) {
  const bool is_drawn = window_->IsDrawn();
  SetDrawn(is_drawn ? nullptr : window->parent(), is_drawn);
}

}  // namespace ws

}  // namespace ui
