// Copyright 2016 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 "cc/base/spiral_iterator.h"

#include <algorithm>

namespace cc {

SpiralIterator::SpiralIterator()
    : around_index_rect_(-1, -1, -1, -1),
      consider_index_rect_(-1, -1, -1, -1),
      ignore_index_rect_(-1, -1, -1, -1),
      index_x_(-1),
      index_y_(-1) {}

SpiralIterator::SpiralIterator(const IndexRect& around_index_rect,
                               const IndexRect& consider_index_rect,
                               const IndexRect& ignore_index_rect)
    : around_index_rect_(around_index_rect),
      consider_index_rect_(consider_index_rect),
      ignore_index_rect_(ignore_index_rect),
      index_x_(-1),
      index_y_(-1),
      direction_(RIGHT),
      delta_x_(1),
      delta_y_(0),
      current_step_(0),
      horizontal_step_count_(0),
      vertical_step_count_(0) {
  vertical_step_count_ = around_index_rect_.num_indices_y();
  horizontal_step_count_ = around_index_rect_.num_indices_x();
  current_step_ = horizontal_step_count_ - 1;

  index_x_ = around_index_rect_.right();
  index_y_ = around_index_rect_.bottom();

  // The current index is the bottom right of the around rect, which is also
  // ignored. So we have to advance.
  ++(*this);
}

SpiralIterator::operator bool() const {
  return index_x_ != -1 && index_y_ != -1;
}

SpiralIterator& SpiralIterator::operator++() {
  int cannot_hit_consider_count = 0;
  while (cannot_hit_consider_count < 4) {
    if (needs_direction_switch())
      switch_direction();

    index_x_ += delta_x_;
    index_y_ += delta_y_;
    ++current_step_;

    if (consider_index_rect_.Contains(index_x_, index_y_)) {
      cannot_hit_consider_count = 0;

      if (!ignore_index_rect_.Contains(index_x_, index_y_))
        break;

      // Steps needed to reach the very edge of the ignore rect, while remaining
      // inside (so that the continue would take us outside).
      int steps_to_edge = 0;
      switch (direction_) {
        case UP:
          steps_to_edge = index_y_ - ignore_index_rect_.top();
          break;
        case LEFT:
          steps_to_edge = index_x_ - ignore_index_rect_.left();
          break;
        case DOWN:
          steps_to_edge = ignore_index_rect_.bottom() - index_y_;
          break;
        case RIGHT:
          steps_to_edge = ignore_index_rect_.right() - index_x_;
          break;
      }

      // We need to switch directions in |max_steps|.
      int max_steps = current_step_count() - current_step_;

      int steps_to_take = std::min(steps_to_edge, max_steps);
      DCHECK_GE(steps_to_take, 0);

      index_x_ += steps_to_take * delta_x_;
      index_y_ += steps_to_take * delta_y_;
      current_step_ += steps_to_take;
    } else {
      int max_steps = current_step_count() - current_step_;
      int steps_to_take = max_steps;
      bool can_hit_consider_rect = false;
      switch (direction_) {
        case UP:
          if (consider_index_rect_.valid_column(index_x_) &&
              consider_index_rect_.bottom() < index_y_)
            steps_to_take = index_y_ - consider_index_rect_.bottom() - 1;
          can_hit_consider_rect |= consider_index_rect_.right() >= index_x_;
          break;
        case LEFT:
          if (consider_index_rect_.valid_row(index_y_) &&
              consider_index_rect_.right() < index_x_)
            steps_to_take = index_x_ - consider_index_rect_.right() - 1;
          can_hit_consider_rect |= consider_index_rect_.top() <= index_y_;
          break;
        case DOWN:
          if (consider_index_rect_.valid_column(index_x_) &&
              consider_index_rect_.top() > index_y_)
            steps_to_take = consider_index_rect_.top() - index_y_ - 1;
          can_hit_consider_rect |= consider_index_rect_.left() <= index_x_;
          break;
        case RIGHT:
          if (consider_index_rect_.valid_row(index_y_) &&
              consider_index_rect_.left() > index_x_)
            steps_to_take = consider_index_rect_.left() - index_x_ - 1;
          can_hit_consider_rect |= consider_index_rect_.bottom() >= index_y_;
          break;
      }
      steps_to_take = std::min(steps_to_take, max_steps);
      DCHECK_GE(steps_to_take, 0);

      index_x_ += steps_to_take * delta_x_;
      index_y_ += steps_to_take * delta_y_;
      current_step_ += steps_to_take;

      if (can_hit_consider_rect)
        cannot_hit_consider_count = 0;
      else
        ++cannot_hit_consider_count;
    }
  }

  if (cannot_hit_consider_count >= 4) {
    index_x_ = -1;
    index_y_ = -1;
  }

  return *this;
}

bool SpiralIterator::needs_direction_switch() const {
  return current_step_ >= current_step_count();
}

void SpiralIterator::switch_direction() {
  // Note that delta_x_ and delta_y_ always remain between -1 and 1.
  int new_delta_x = delta_y_;
  delta_y_ = -delta_x_;
  delta_x_ = new_delta_x;

  current_step_ = 0;
  direction_ = static_cast<Direction>((direction_ + 1) % 4);

  if (direction_ == RIGHT || direction_ == LEFT) {
    ++vertical_step_count_;
    ++horizontal_step_count_;
  }
}

}  // namespace cc
