// Copyright 2010-2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package org.mozc.android.inputmethod.japanese.ui;

import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateList;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Row;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Span;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * Layouts the symbol candidate words.
 *
 * This is a table-like layout, which can be scrollable vertically.
 * The client can set the minimum column width and the row height.
 * The number of columns is calculated based on the given minimum column width
 * and the view's width. Each row should horizontally fit the view width.
 *
 */
public class SymbolCandidateLayouter implements CandidateLayouter {

  private Optional<SpanFactory> spanFactory = Optional.absent();

  /** The minimum width for each column. */
  private float minColumnWidth;

  /** The number of rows in a page. */
  private int rowHeight;

  /** The current view's width. */
  private int viewWidth;

  public void setMinColumnWidth(float minColumnWidth) {
    this.minColumnWidth = minColumnWidth;
  }

  public void setRowHeight(int rowHeight) {
    this.rowHeight = rowHeight;
  }

  @Override
  public boolean setViewSize(int width, int height) {
    // Ignore the height.
    if (viewWidth == width) {
      return false;
    }

    viewWidth = width;
    return true;
  }

  @Override
  public int getPageWidth() {
    return viewWidth;
  }

  @Override
  public int getPageHeight() {
    return rowHeight;
  }

  @Override
  public Optional<CandidateLayout> layout(CandidateList candidateList) {
    Preconditions.checkNotNull(candidateList);
    if (viewWidth <= 0 || rowHeight <= 0 || minColumnWidth <= 0 ||
        candidateList == null || candidateList.getCandidatesCount() == 0 ||
        !spanFactory.isPresent()) {
      return Optional.absent();
    }

    int numColumns = getNumColumns(viewWidth, minColumnWidth);
    List<Row> rowList = buildRowList(candidateList, spanFactory.get(), numColumns);
    for (Row row : rowList) {
      layoutSpanList(row.getSpanList(), viewWidth, numColumns);
    }
    layoutRowList(rowList, rowHeight);
    return Optional.of(new CandidateLayout(rowList, viewWidth, rowHeight * rowList.size()));
  }

  private static int getNumColumns(int viewWidth, float minColumnWidth) {
    // Ensure at least one column.
    return Math.max((int) (viewWidth / minColumnWidth), 1);
  }

  /** Builds a list of {@code Row}s from candidate word list. */
  static List<Row> buildRowList(
      CandidateList candidateList, SpanFactory spanFactory, int numColumns) {
    Preconditions.checkNotNull(candidateList);
    Preconditions.checkNotNull(spanFactory);

    List<Row> rowList = new ArrayList<Row>(
        (candidateList.getCandidatesCount() + numColumns - 1) / numColumns);
    int columnIndex = 0;
    Row row = null;
    for (CandidateWord candidateWord : candidateList.getCandidatesList()) {
      if (columnIndex == 0) {
        row = new Row();
        rowList.add(row);
      }

      row.addSpan(spanFactory.newInstance(candidateWord));
      columnIndex = (columnIndex + 1) % numColumns;
    }
    return rowList;
  }

  /** Sets the left and right position to all spans. */
  static void layoutSpanList(List<Span> spanList, int viewWidth, int numColumns) {
    Preconditions.checkNotNull(spanList);
    float left = 0;
    for (ListIterator<Span> iter = spanList.listIterator(); iter.hasNext(); ) {
      Span span = iter.next();
      // To avoid fp error at the end of the span list, we use the following expression.
      float right = viewWidth * iter.nextIndex() / (float) numColumns;
      span.setLeft(left);
      span.setRight(right);
      left = right;
    }
  }

  /** Sets the top, height and width to all rows. */
  static void layoutRowList(List<Row> rowList, int rowHeight) {
    Preconditions.checkNotNull(rowList);
    // The pageHeight will be divided evenly to the each row.
    for (ListIterator<Row> iter = rowList.listIterator(); iter.hasNext(); ) {
      int index = iter.nextIndex();
      Row row = iter.next();
      row.setTop(rowHeight * index);
      row.setHeight(rowHeight);
      List<Span> spanList = row.getSpanList();
      row.setWidth(spanList.isEmpty() ? 0 : spanList.get(spanList.size() - 1).getRight());
    }
  }

  /**
   * @param spanFactory the spanFactory to set
   */
  public void setSpanFactory(SpanFactory spanFactory) {
    this.spanFactory =  Optional.of(Preconditions.checkNotNull(spanFactory));
  }
}
