/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tv.twopanelsettings.slices.compat.widget;

import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.ICON_IMAGE;
import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.RAW_IMAGE_LARGE;
import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.UNKNOWN_IMAGE;
import static com.android.tv.twopanelsettings.slices.compat.widget.SliceViewPolicy.MODE_LARGE;
import static com.android.tv.twopanelsettings.slices.compat.widget.SliceViewPolicy.MODE_SMALL;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.tv.twopanelsettings.R;
import com.android.tv.twopanelsettings.slices.compat.SliceItem;
import java.util.ArrayList;
import java.util.List;

/** Holds style information shared between child views of a slice */
// @RestrictTo(RestrictTo.Scope.LIBRARY)
// @Deprecated // Supported for TV
public class SliceStyle {
  private int mTintColor = -1;
  private final int mTitleColor;
  private final int mSubtitleColor;
  private final int mHeaderTitleSize;
  private final int mHeaderSubtitleSize;
  private final int mVerticalHeaderTextPadding;
  private final int mTitleSize;
  private final int mSubtitleSize;
  private final int mVerticalTextPadding;
  private final int mGridTitleSize;
  private final int mGridSubtitleSize;
  private final int mVerticalGridTextPadding;
  private final int mGridTopPadding;
  private final int mGridBottomPadding;

  private final int mRowMaxHeight;
  private final int mRowTextWithRangeHeight;
  private final int mRowSingleTextWithRangeHeight;
  private final int mRowMinHeight;
  private final int mRowRangeHeight;
  private final int mRowSelectionHeight;
  private final int mRowTextWithSelectionHeight;
  private final int mRowSingleTextWithSelectionHeight;
  private final int mRowInlineRangeHeight;

  private final int mGridBigPicMinHeight;
  private final int mGridBigPicMaxHeight;
  private final int mGridAllImagesHeight;
  private final int mGridImageTextHeight;
  private final int mGridRawImageTextHeight;
  private final int mGridMaxHeight;
  private final int mGridMinHeight;

  private final int mListMinScrollHeight;
  private final int mListLargeHeight;

  private final boolean mExpandToAvailableHeight;
  private final boolean mHideHeaderRow;

  private final int mDefaultRowStyleRes;
  private final SparseArray<RowStyle> mResourceToRowStyle = new SparseArray<>();
  private RowStyleFactory mRowStyleFactory;

  private final Context mContext;

  private final float mImageCornerRadius;

  public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    TypedArray a =
        context
            .getTheme()
            .obtainStyledAttributes(attrs, R.styleable.SliceView, defStyleAttr, defStyleRes);
    try {
      int themeColor = a.getColor(R.styleable.SliceView_tintColor, -1);
      mTintColor = themeColor != -1 ? themeColor : mTintColor;
      mTitleColor = a.getColor(R.styleable.SliceView_titleColor, 0);
      mSubtitleColor = a.getColor(R.styleable.SliceView_subtitleColor, 0);

      mHeaderTitleSize = (int) a.getDimension(R.styleable.SliceView_headerTitleSize, 0);
      mHeaderSubtitleSize = (int) a.getDimension(R.styleable.SliceView_headerSubtitleSize, 0);
      mVerticalHeaderTextPadding =
          (int) a.getDimension(R.styleable.SliceView_headerTextVerticalPadding, 0);

      mTitleSize = (int) a.getDimension(R.styleable.SliceView_titleSize, 0);
      mSubtitleSize = (int) a.getDimension(R.styleable.SliceView_subtitleSize, 0);
      mVerticalTextPadding = (int) a.getDimension(R.styleable.SliceView_textVerticalPadding, 0);

      mGridTitleSize = (int) a.getDimension(R.styleable.SliceView_gridTitleSize, 0);
      mGridSubtitleSize = (int) a.getDimension(R.styleable.SliceView_gridSubtitleSize, 0);
      int defaultVerticalGridPadding =
          context.getResources().getDimensionPixelSize(R.dimen.abc_slice_grid_text_inner_padding);
      mVerticalGridTextPadding =
          (int)
              a.getDimension(
                  R.styleable.SliceView_gridTextVerticalPadding, defaultVerticalGridPadding);
      mGridTopPadding = (int) a.getDimension(R.styleable.SliceView_gridTopPadding, 0);
      mGridBottomPadding = (int) a.getDimension(R.styleable.SliceView_gridBottomPadding, 0);

      mDefaultRowStyleRes = a.getResourceId(R.styleable.SliceView_rowStyle, 0);

      int defaultRowMinHeight =
          context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
      mRowMinHeight = (int) a.getDimension(R.styleable.SliceView_rowMinHeight, defaultRowMinHeight);

      int defaultRowMaxHeight =
          context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_max_height);
      mRowMaxHeight = (int) a.getDimension(R.styleable.SliceView_rowMaxHeight, defaultRowMaxHeight);

      int defaultRowRangeHeight =
          context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_range_height);
      mRowRangeHeight =
          (int) a.getDimension(R.styleable.SliceView_rowRangeHeight, defaultRowRangeHeight);

      int defaultRowSingleTextWithRangeHeight =
          context
              .getResources()
              .getDimensionPixelSize(R.dimen.abc_slice_row_range_single_text_height);
      mRowSingleTextWithRangeHeight =
          (int)
              a.getDimension(
                  R.styleable.SliceView_rowRangeSingleTextHeight,
                  defaultRowSingleTextWithRangeHeight);

      int defaultRowInlineRangeHeight =
          context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_range_inline_height);
      mRowInlineRangeHeight =
          (int)
              a.getDimension(
                  R.styleable.SliceView_rowInlineRangeHeight, defaultRowInlineRangeHeight);

      mExpandToAvailableHeight = a.getBoolean(R.styleable.SliceView_expandToAvailableHeight, false);

      mHideHeaderRow = a.getBoolean(R.styleable.SliceView_hideHeaderRow, false);

      mContext = context;

      mImageCornerRadius = a.getDimension(R.styleable.SliceView_imageCornerRadius, 0);
    } finally {
      a.recycle();
    }

    // Note: The above colors and dimensions are styleable, but the below ones are not.

    final Resources r = context.getResources();

    mRowTextWithRangeHeight =
        r.getDimensionPixelSize(R.dimen.abc_slice_row_range_multi_text_height);
    mRowSelectionHeight = r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_height);
    mRowTextWithSelectionHeight =
        r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_multi_text_height);
    mRowSingleTextWithSelectionHeight =
        r.getDimensionPixelSize(R.dimen.abc_slice_row_selection_single_text_height);

    mGridBigPicMinHeight = r.getDimensionPixelSize(R.dimen.abc_slice_big_pic_min_height);
    mGridBigPicMaxHeight = r.getDimensionPixelSize(R.dimen.abc_slice_big_pic_max_height);
    mGridAllImagesHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
    mGridImageTextHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_image_text_height);
    mGridRawImageTextHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_raw_image_text_offset);
    mGridMinHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_min_height);
    mGridMaxHeight = r.getDimensionPixelSize(R.dimen.abc_slice_grid_max_height);

    mListMinScrollHeight = r.getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
    mListLargeHeight = r.getDimensionPixelSize(R.dimen.abc_slice_large_height_internal);
  }

  public int getRowMinHeight() {
    return mRowMinHeight;
  }

  public int getRowMaxHeight() {
    return mRowMaxHeight;
  }

  public int getRowInlineRangeHeight() {
    return mRowInlineRangeHeight;
  }

  public void setTintColor(int tint) {
    mTintColor = tint;
  }

  public int getTintColor() {
    return mTintColor;
  }

  public int getTitleColor() {
    return mTitleColor;
  }

  public int getSubtitleColor() {
    return mSubtitleColor;
  }

  public int getHeaderTitleSize() {
    return mHeaderTitleSize;
  }

  public int getHeaderSubtitleSize() {
    return mHeaderSubtitleSize;
  }

  public int getVerticalHeaderTextPadding() {
    return mVerticalHeaderTextPadding;
  }

  public int getTitleSize() {
    return mTitleSize;
  }

  public int getSubtitleSize() {
    return mSubtitleSize;
  }

  public int getVerticalTextPadding() {
    return mVerticalTextPadding;
  }

  public int getGridTitleSize() {
    return mGridTitleSize;
  }

  public int getGridSubtitleSize() {
    return mGridSubtitleSize;
  }

  public int getVerticalGridTextPadding() {
    return mVerticalGridTextPadding;
  }

  public int getGridTopPadding() {
    return mGridTopPadding;
  }

  public int getGridBottomPadding() {
    return mGridBottomPadding;
  }

  /** Returns the {@link RowStyle} to use for the given {@link SliceItem}. */
  @NonNull
  public RowStyle getRowStyle(@Nullable SliceItem sliceItem) {
    int rowStyleRes = mDefaultRowStyleRes;

    if (sliceItem != null && mRowStyleFactory != null) {
      int maybeStyleRes = mRowStyleFactory.getRowStyleRes(sliceItem);
      if (maybeStyleRes != 0) {
        rowStyleRes = maybeStyleRes;
      }
    }

    if (rowStyleRes == 0) {
      // Return default values.
      return new RowStyle(mContext, this);
    }

    RowStyle rowStyle = mResourceToRowStyle.get(rowStyleRes);
    if (rowStyle == null) {
      rowStyle = new RowStyle(mContext, rowStyleRes, this);
      mResourceToRowStyle.put(rowStyleRes, rowStyle);
    }
    return rowStyle;
  }

  /** Sets the {@link RowStyleFactory} which allows multiple children to have different styles. */
  public void setRowStyleFactory(@Nullable RowStyleFactory rowStyleFactory) {
    mRowStyleFactory = rowStyleFactory;
  }

  public int getRowRangeHeight() {
    return mRowRangeHeight;
  }

  public int getRowSelectionHeight() {
    return mRowSelectionHeight;
  }

  public boolean getExpandToAvailableHeight() {
    return mExpandToAvailableHeight;
  }

  public boolean getHideHeaderRow() {
    return mHideHeaderRow;
  }

  public boolean getApplyCornerRadiusToLargeImages() {
    return mImageCornerRadius > 0;
  }

  public float getImageCornerRadius() {
    return mImageCornerRadius;
  }

  public int getRowHeight(RowContent row, SliceViewPolicy policy) {
    int maxHeight = policy.getMaxSmallHeight() > 0 ? policy.getMaxSmallHeight() : mRowMaxHeight;

    if (row.getRange() == null && row.getSelection() == null && policy.getMode() != MODE_LARGE) {
      return maxHeight;
    }

    if (row.getRange() != null) {
      // If no StartItem, keep to use original layout.
      if (row.getStartItem() == null) {
        // Range element always has set height and then the height of the text
        // area on the row will vary depending on 0,1 or 2 lines of text.
        int textAreaHeight =
            row.getLineCount() == 0
                ? 0
                : (row.getLineCount() > 1
                    ? mRowTextWithRangeHeight
                    : mRowSingleTextWithRangeHeight);
        return textAreaHeight + mRowRangeHeight;
      } else {
        // If has StartItem then Range element is inline, the row height should be more to
        // fit thumb ripple.
        return mRowInlineRangeHeight;
      }
    }

    if (row.getSelection() != null) {
      // Selection element always has set height and then the height of the text
      // area on the row will vary depending on if 1 or 2 lines of text.
      int textAreaHeight =
          row.getLineCount() > 1 ? mRowTextWithSelectionHeight : mRowSingleTextWithSelectionHeight;
      return textAreaHeight + mRowSelectionHeight;
    }

    return (row.getLineCount() > 1 || row.getIsHeader()) ? maxHeight : mRowMinHeight;
  }

  public int getGridHeight(GridContent grid, SliceViewPolicy policy) {
    boolean isSmall = policy.getMode() == MODE_SMALL;
    if (!grid.isValid()) {
      return 0;
    }
    int largestImageMode = grid.getLargestImageMode();
    int height;
    if (grid.isAllImages()) {
      height =
          (grid.getGridContent().size() == 1)
              ? (isSmall ? mGridBigPicMinHeight : mGridBigPicMaxHeight)
              : (largestImageMode == ICON_IMAGE
                  ? mGridMinHeight
                  : (largestImageMode == RAW_IMAGE_LARGE
                      ? grid.getFirstImageSize(mContext).y
                      : mGridAllImagesHeight));
    } else {
      boolean twoLines = grid.getMaxCellLineCount() > 1;
      boolean hasImage = grid.hasImage();
      boolean iconImagesOrNone =
          largestImageMode == ICON_IMAGE || largestImageMode == UNKNOWN_IMAGE;
      height =
          largestImageMode == RAW_IMAGE_LARGE
              ? (grid.getFirstImageSize(mContext).y + (twoLines ? 2 : 1) * mGridRawImageTextHeight)
              : (twoLines && !isSmall)
                  ? (hasImage ? mGridMaxHeight : mGridMinHeight)
                  : (iconImagesOrNone ? mGridMinHeight : mGridImageTextHeight);
    }
    int topPadding = grid.isAllImages() && grid.getRowIndex() == 0 ? mGridTopPadding : 0;
    int bottomPadding = grid.isAllImages() && grid.getIsLastIndex() ? mGridBottomPadding : 0;
    return height + topPadding + bottomPadding;
  }

  public int getListHeight(ListContent list, SliceViewPolicy policy) {
    if (policy.getMode() == MODE_SMALL) {
      return list.getHeader().getHeight(this, policy);
    }
    int maxHeight = policy.getMaxHeight();
    boolean scrollable = policy.isScrollable();

    int desiredHeight = getListItemsHeight(list.getRowItems(), policy);
    if (maxHeight > 0) {
      // Always ensure we're at least the height of our small version.
      int smallHeight = list.getHeader().getHeight(this, policy);
      maxHeight = Math.max(smallHeight, maxHeight);
    }
    int maxLargeHeight = maxHeight > 0 ? maxHeight : mListLargeHeight;
    // Do we have enough content to reasonably scroll in our max?
    boolean bigEnoughToScroll = desiredHeight - maxLargeHeight >= mListMinScrollHeight;

    // Adjust for scrolling
    int height =
        bigEnoughToScroll && !getExpandToAvailableHeight()
            ? maxLargeHeight
            : maxHeight <= 0 ? desiredHeight : Math.min(maxLargeHeight, desiredHeight);
    if (!scrollable) {
      height =
          getListItemsHeight(
              getListItemsForNonScrollingList(list, height, policy).getDisplayedItems(), policy);
    }
    return height;
  }

  public int getListItemsHeight(List<SliceContent> listItems, SliceViewPolicy policy) {
    if (listItems == null) {
      return 0;
    }

    int height = 0;
    for (int i = 0; i < listItems.size(); i++) {
      SliceContent listItem = listItems.get(i);
      if (i == 0 && shouldSkipFirstListItem(listItems)) {
        continue;
      }
      height += listItem.getHeight(this, policy);
    }
    return height;
  }

  /**
   * Returns a list of items that can fit in the provided height. If this list has a see more item
   * this will be displayed in the list if appropriate.
   *
   * @param list the list from which to source the items.
   * @param availableHeight to use to determine the row items to return.
   * @param policy the policy info (scrolling, mode) to use when determining row items to return.
   * @return the list of items that can be displayed in the provided height.
   */
  @NonNull
  public DisplayedListItems getListItemsForNonScrollingList(
      ListContent list, int availableHeight, SliceViewPolicy policy) {
    ArrayList<SliceContent> visibleItems = new ArrayList<>();
    int hiddenItemCount = 0;
    if (list.getRowItems() == null || list.getRowItems().isEmpty()) {
      return new DisplayedListItems(visibleItems, hiddenItemCount);
    }

    final boolean skipFirstItem = shouldSkipFirstListItem(list.getRowItems());

    int visibleHeight = 0;
    final int rowCount = list.getRowItems().size();
    for (int i = 0; i < rowCount; i++) {
      SliceContent listItem = list.getRowItems().get(i);
      if (i == 0 && skipFirstItem) {
        continue;
      }
      int itemHeight = listItem.getHeight(this, policy);
      if (availableHeight > 0 && visibleHeight + itemHeight > availableHeight) {
        hiddenItemCount = rowCount - i;
        break;
      } else {
        visibleHeight += itemHeight;
        visibleItems.add(listItem);
      }
    }

    // Only add see more if we're at least showing one item and it's not the header.
    final int minItemCountForSeeMore = skipFirstItem ? 1 : 2;
    if (list.getSeeMoreItem() != null
        && visibleItems.size() >= minItemCountForSeeMore
        && hiddenItemCount > 0) {
      // Need to show see more
      int seeMoreHeight = list.getSeeMoreItem().getHeight(this, policy);
      visibleHeight += seeMoreHeight;

      // Free enough vertical space to fit the see more item.
      while (visibleHeight > availableHeight && visibleItems.size() >= minItemCountForSeeMore) {
        int lastIndex = visibleItems.size() - 1;
        SliceContent lastItem = visibleItems.get(lastIndex);
        visibleHeight -= lastItem.getHeight(this, policy);
        visibleItems.remove(lastIndex);
        hiddenItemCount++;
      }

      if (visibleItems.size() >= minItemCountForSeeMore) {
        visibleItems.add(list.getSeeMoreItem());
      } else {
        // Not possible to free enough vertical space. We'll show only the header.
        visibleHeight -= seeMoreHeight;
      }
    }
    if (visibleItems.isEmpty()) {
      // Didn't have enough space to show anything; should still show something
      visibleItems.add(list.getRowItems().get(0));
    }
    return new DisplayedListItems(visibleItems, hiddenItemCount);
  }

  /**
   * Returns a list of items that should be displayed to the user.
   *
   * @param list the list from which to source the items.
   */
  @NonNull
  public List<SliceContent> getListItemsToDisplay(@NonNull ListContent list) {
    List<SliceContent> rowItems = list.getRowItems();
    if (!rowItems.isEmpty() && shouldSkipFirstListItem(rowItems)) {
      return rowItems.subList(1, rowItems.size());
    }
    return rowItems;
  }

  /** Returns true if the first item of a list should be skipped. */
  private boolean shouldSkipFirstListItem(List<SliceContent> rowItems) {
    // Hide header row if requested, but only if there is at least one non-header row.
    return getHideHeaderRow()
        && rowItems.size() > 1
        && rowItems.get(0) instanceof RowContent
        && ((RowContent) rowItems.get(0)).getIsHeader();
  }
}
