// 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 "base/logging.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/view.h"

namespace views {

FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
    : root_(root),
      cycle_(cycle),
      accessibility_mode_(accessibility_mode) {
}

View* FocusSearch::FindNextFocusableView(View* starting_view,
                                         bool reverse,
                                         Direction direction,
                                         bool check_starting_view,
                                         FocusTraversable** focus_traversable,
                                         View** focus_traversable_view) {
  *focus_traversable = NULL;
  *focus_traversable_view = NULL;

  if (!root_->has_children()) {
    NOTREACHED();
    // Nothing to focus on here.
    return NULL;
  }

  View* initial_starting_view = starting_view;
  int starting_view_group = -1;
  if (starting_view)
    starting_view_group = starting_view->GetGroup();

  if (!starting_view) {
    // Default to the first/last child
    starting_view = reverse ? root_->child_at(root_->child_count() - 1) :
        root_->child_at(0);
    // If there was no starting view, then the one we select is a potential
    // focus candidate.
    check_starting_view = true;
  } else {
    // The starting view should be a direct or indirect child of the root.
    DCHECK(Contains(root_, starting_view));
  }

  View* v = NULL;
  if (!reverse) {
    v = FindNextFocusableViewImpl(starting_view, check_starting_view,
                                  true,
                                  (direction == DOWN),
                                  starting_view_group,
                                  focus_traversable,
                                  focus_traversable_view);
  } else {
    // If the starting view is focusable, we don't want to go down, as we are
    // traversing the view hierarchy tree bottom-up.
    bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view);
    v = FindPreviousFocusableViewImpl(starting_view, check_starting_view,
                                      true,
                                      can_go_down,
                                      starting_view_group,
                                      focus_traversable,
                                      focus_traversable_view);
  }

  // Don't set the focus to something outside of this view hierarchy.
  if (v && v != root_ && !Contains(root_, v))
    v = NULL;

  // If |cycle_| is true, prefer to keep cycling rather than returning NULL.
  if (cycle_ && !v && initial_starting_view) {
    v = FindNextFocusableView(NULL, reverse, direction, check_starting_view,
                              focus_traversable, focus_traversable_view);
    DCHECK(IsFocusable(v));
    return v;
  }

  // Doing some sanity checks.
  if (v) {
    DCHECK(IsFocusable(v));
    return v;
  }
  if (*focus_traversable) {
    DCHECK(*focus_traversable_view);
    return NULL;
  }
  // Nothing found.
  return NULL;
}

bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) {
  return IsFocusable(v) &&
      (v->IsGroupFocusTraversable() || skip_group_id == -1 ||
       v->GetGroup() != skip_group_id);
}

bool FocusSearch::IsFocusable(View* v) {
  if (accessibility_mode_)
    return v && v->IsAccessibilityFocusable();
  return v && v->IsFocusable();
}

View* FocusSearch::FindSelectedViewForGroup(View* view) {
  if (view->IsGroupFocusTraversable() ||
      view->GetGroup() == -1)  // No group for that view.
    return view;

  View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
  if (selected_view)
    return selected_view;

  // No view selected for that group, default to the specified view.
  return view;
}

View* FocusSearch::GetParent(View* v) {
  return Contains(root_, v) ? v->parent() : NULL;
}

bool FocusSearch::Contains(View* root, const View* v) {
  return root->Contains(v);
}

// Strategy for finding the next focusable view:
// - keep going down the first child, stop when you find a focusable view or
//   a focus traversable view (in that case return it) or when you reach a view
//   with no children.
// - go to the right sibling and start the search from there (by invoking
//   FindNextFocusableViewImpl on that view).
// - if the view has no right sibling, go up the parents until you find a parent
//   with a right sibling and start the search from there.
View* FocusSearch::FindNextFocusableViewImpl(
    View* starting_view,
    bool check_starting_view,
    bool can_go_up,
    bool can_go_down,
    int skip_group_id,
    FocusTraversable** focus_traversable,
    View** focus_traversable_view) {
  if (check_starting_view) {
    if (IsViewFocusableCandidate(starting_view, skip_group_id)) {
      View* v = FindSelectedViewForGroup(starting_view);
      // The selected view might not be focusable (if it is disabled for
      // example).
      if (IsFocusable(v))
        return v;
    }

    *focus_traversable = starting_view->GetFocusTraversable();
    if (*focus_traversable) {
      *focus_traversable_view = starting_view;
      return NULL;
    }
  }

  // First let's try the left child.
  if (can_go_down) {
    if (starting_view->has_children()) {
      View* v = FindNextFocusableViewImpl(starting_view->child_at(0),
                                          true, false, true, skip_group_id,
                                          focus_traversable,
                                          focus_traversable_view);
      if (v || *focus_traversable)
        return v;
    }
  }

  // Then try the right sibling.
  View* sibling = starting_view->GetNextFocusableView();
  if (sibling) {
    View* v = FindNextFocusableViewImpl(sibling,
                                        true, false, true, skip_group_id,
                                        focus_traversable,
                                        focus_traversable_view);
    if (v || *focus_traversable)
      return v;
  }

  // Then go up to the parent sibling.
  if (can_go_up) {
    View* parent = GetParent(starting_view);
    while (parent && parent != root_) {
      sibling = parent->GetNextFocusableView();
      if (sibling) {
        return FindNextFocusableViewImpl(sibling,
                                         true, true, true,
                                         skip_group_id,
                                         focus_traversable,
                                         focus_traversable_view);
      }
      parent = GetParent(parent);
    }
  }

  // We found nothing.
  return NULL;
}

// Strategy for finding the previous focusable view:
// - keep going down on the right until you reach a view with no children, if it
//   it is a good candidate return it.
// - start the search on the left sibling.
// - if there are no left sibling, start the search on the parent (without going
//   down).
View* FocusSearch::FindPreviousFocusableViewImpl(
    View* starting_view,
    bool check_starting_view,
    bool can_go_up,
    bool can_go_down,
    int skip_group_id,
    FocusTraversable** focus_traversable,
    View** focus_traversable_view) {
  // Let's go down and right as much as we can.
  if (can_go_down) {
    // Before we go into the direct children, we have to check if this view has
    // a FocusTraversable.
    *focus_traversable = starting_view->GetFocusTraversable();
    if (*focus_traversable) {
      *focus_traversable_view = starting_view;
      return NULL;
    }

    if (starting_view->has_children()) {
      View* view =
          starting_view->child_at(starting_view->child_count() - 1);
      View* v = FindPreviousFocusableViewImpl(view, true, false, true,
                                              skip_group_id,
                                              focus_traversable,
                                              focus_traversable_view);
      if (v || *focus_traversable)
        return v;
    }
  }

  // Then look at this view. Here, we do not need to see if the view has
  // a FocusTraversable, since we do not want to go down any more.
  if (check_starting_view &&
      IsViewFocusableCandidate(starting_view, skip_group_id)) {
    View* v = FindSelectedViewForGroup(starting_view);
    // The selected view might not be focusable (if it is disabled for
    // example).
    if (IsFocusable(v))
      return v;
  }

  // Then try the left sibling.
  View* sibling = starting_view->GetPreviousFocusableView();
  if (sibling) {
    return FindPreviousFocusableViewImpl(sibling,
                                         true, true, true,
                                         skip_group_id,
                                         focus_traversable,
                                         focus_traversable_view);
  }

  // Then go up the parent.
  if (can_go_up) {
    View* parent = GetParent(starting_view);
    if (parent)
      return FindPreviousFocusableViewImpl(parent,
                                           true, true, false,
                                           skip_group_id,
                                           focus_traversable,
                                           focus_traversable_view);
  }

  // We found nothing.
  return NULL;
}

}  // namespace views