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

#ifndef SERVICES_UI_COMMON_TRANSIENT_WINDOW_UTILS_H_
#define SERVICES_UI_COMMON_TRANSIENT_WINDOW_UTILS_H_

#include <stddef.h>

#include <vector>

#include "services/ui/public/interfaces/mus_constants.mojom.h"

namespace ui {

// Returns true if |window| has |ancestor| as a transient ancestor. A transient
// ancestor is found by following the transient parent chain of the window.
template <class T>
bool HasTransientAncestor(const T* window, const T* ancestor) {
  const T* transient_parent = window->transient_parent();
  if (transient_parent == ancestor)
    return true;
  return transient_parent ? HasTransientAncestor(transient_parent, ancestor)
                          : false;
}

// Populates |ancestors| with all transient ancestors of |window| that are
// siblings of |window|. Returns true if any ancestors were found, false if not.
template <class T>
bool GetAllTransientAncestors(T* window, std::vector<T*>* ancestors) {
  T* parent = window->parent();
  for (; window; window = window->transient_parent()) {
    if (window->parent() == parent)
      ancestors->push_back(window);
  }
  return !ancestors->empty();
}

// Replaces |window1| and |window2| with their possible transient ancestors that
// are still siblings (have a common transient parent).  |window1| and |window2|
// are not modified if such ancestors cannot be found.
template <class T>
void FindCommonTransientAncestor(T** window1, T** window2) {
  DCHECK(window1);
  DCHECK(window2);
  DCHECK(*window1);
  DCHECK(*window2);
  // Assemble chains of ancestors of both windows.
  std::vector<T*> ancestors1;
  std::vector<T*> ancestors2;
  if (!GetAllTransientAncestors(*window1, &ancestors1) ||
      !GetAllTransientAncestors(*window2, &ancestors2)) {
    return;
  }
  // Walk the two chains backwards and look for the first difference.
  auto it1 = ancestors1.rbegin();
  auto it2 = ancestors2.rbegin();
  for (; it1 != ancestors1.rend() && it2 != ancestors2.rend(); ++it1, ++it2) {
    if (*it1 != *it2) {
      *window1 = *it1;
      *window2 = *it2;
      break;
    }
  }
}

template <class T>
bool AdjustStackingForTransientWindows(T** child,
                                       T** target,
                                       mojom::OrderDirection* direction,
                                       T* stacking_target) {
  if (stacking_target == *target)
    return true;

  // For windows that have transient children stack the transient ancestors that
  // are siblings. This prevents one transient group from being inserted in the
  // middle of another.
  FindCommonTransientAncestor(child, target);

  // When stacking above skip to the topmost transient descendant of the target.
  if (*direction == mojom::OrderDirection::ABOVE &&
      !HasTransientAncestor(*child, *target)) {
    const std::vector<T*>& siblings((*child)->parent()->children());
    size_t target_i =
        std::find(siblings.begin(), siblings.end(), *target) - siblings.begin();
    while (target_i + 1 < siblings.size() &&
           HasTransientAncestor(siblings[target_i + 1], *target)) {
      ++target_i;
    }
    *target = siblings[target_i];
  }

  return *child != *target;
}

// Stacks transient descendants of |window| that are its siblings just above it.
// |GetStackingTarget| is a function that returns a marker associated with a
// Window that indicates the current Window being stacked.
// |Reorder| is a function that takes in two windows and orders the first
// relative to the second based on the provided OrderDirection.
template <class T>
void RestackTransientDescendants(T* window,
                                 T** (*GetStackingTarget)(T*),
                                 void (*Reorder)(T*,
                                                 T*,
                                                 mojom::OrderDirection)) {
  T* parent = window->parent();
  if (!parent)
    return;

  // stack any transient children that share the same parent to be in front of
  // |window_|. the existing stacking order is preserved by iterating backwards
  // and always stacking on top.
  std::vector<T*> children(parent->children());
  for (auto it = children.rbegin(); it != children.rend(); ++it) {
    if ((*it) != window && HasTransientAncestor(*it, window)) {
      T* old_stacking_target = *GetStackingTarget(*it);
      *GetStackingTarget(*it) = window;
      Reorder(*it, window, mojom::OrderDirection::ABOVE);
      *GetStackingTarget(*it) = old_stacking_target;
    }
  }
}
}  // namespace ui

#endif  // SERVICES_UI_COMMON_TRANSIENT_WINDOW_UTILS_H_
