// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.omnibox.suggestions;

import android.content.Context;
import android.os.Handler;
import android.widget.FrameLayout;

import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

import org.chromium.base.TraceEvent;
import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
import org.chromium.chrome.browser.omnibox.OmniboxMetrics;
import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;

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

/**
 * RecyclerView pool that:
 * 1. Pre-creates a hardcoded set of ViewHolders.
 * 2. Records the performance of the view recycling mechanism.
 */
public class PreWarmingRecycledViewPool extends RecycledViewPool {
    private static final long STEP_MILLIS = 50;

    private static class ViewTypeAndCount {
        public final int viewType;
        public final int count;
        public ViewTypeAndCount(int viewType, int count) {
            this.viewType = viewType;
            assert count > 0;
            this.count = count;
        }
    }

    private final ViewTypeAndCount[] mViewsToCreate = new ViewTypeAndCount[] {
            new ViewTypeAndCount(OmniboxSuggestionUiType.EDIT_URL_SUGGESTION, 1),
            new ViewTypeAndCount(OmniboxSuggestionUiType.TILE_NAVSUGGEST, 1),
            new ViewTypeAndCount(OmniboxSuggestionUiType.HEADER, 1),
            new ViewTypeAndCount(OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION, 1),
            new ViewTypeAndCount(OmniboxSuggestionUiType.DEFAULT, 15),
            new ViewTypeAndCount(OmniboxSuggestionUiType.ENTITY_SUGGESTION, 3)};

    private OmniboxSuggestionsDropdownAdapter mAdapter;
    private final Handler mHandler;
    private final FrameLayout mPlaceholderParent;
    private boolean mStopCreatingViews;
    private final List<ViewHolder> mPrewarmedViews = new ArrayList<>(22);

    PreWarmingRecycledViewPool(
            OmniboxSuggestionsDropdownAdapter adapter, Context context, Handler handler) {
        mAdapter = adapter;
        mHandler = handler;
        mPlaceholderParent = new FrameLayout(context);
        // The list below should include suggestions defined in OmniboxSuggestionUiType
        // and specify the maximum anticipated volume of suggestions of each type.
        // For readability reasons, keep the order of this list same as the order of
        // the types defined in OmniboxSuggestionUiType.
        setMaxRecycledViews(OmniboxSuggestionUiType.DEFAULT, 20);
        setMaxRecycledViews(OmniboxSuggestionUiType.EDIT_URL_SUGGESTION, 1);
        setMaxRecycledViews(OmniboxSuggestionUiType.ANSWER_SUGGESTION, 1);
        setMaxRecycledViews(OmniboxSuggestionUiType.ENTITY_SUGGESTION, 8);

        setMaxRecycledViews(OmniboxSuggestionUiType.TAIL_SUGGESTION, 15);
        setMaxRecycledViews(OmniboxSuggestionUiType.CLIPBOARD_SUGGESTION, 1);
        setMaxRecycledViews(OmniboxSuggestionUiType.HEADER, 4);
        setMaxRecycledViews(OmniboxSuggestionUiType.TILE_NAVSUGGEST, 1);
        setMaxRecycledViews(OmniboxSuggestionUiType.PEDAL_SUGGESTION, 3);
        setMaxRecycledViews(OmniboxSuggestionUiType.DIVIDER_LINE, 1);
    }

    public void destroy() {
        stopCreatingViews();
        mAdapter = null;
    }

    public void onNativeInitialized() {
        if (OmniboxFeatures.shouldPreWarmRecyclerViewPool()) {
            startCreatingViews();
        }
    }

    /**
     * Starts creating views. This will immediately post a separate delayed task for every view we
     * intend to create with a delay equal to STEP_MILLIS * order_of_view_creation.
     * */
    public void startCreatingViews() {
        if (mStopCreatingViews) return;
        for (var viewTypeAndCount : mViewsToCreate) {
            for (int index = 0; index < viewTypeAndCount.count; ++index) {
                mHandler.postDelayed(() -> {
                    createViewHolder(viewTypeAndCount.viewType);
                }, STEP_MILLIS * (index + 1));
            }
        }
    }

    /**
     * Stops the task posted by {@link #startCreatingViews()} from creating any more views and
     * removes it from the handler. Places any pre-created views into the pool.
     */
    @VisibleForTesting
    void stopCreatingViews() {
        if (mStopCreatingViews) return;
        mStopCreatingViews = true;
        mHandler.removeCallbacks(null);
        putViewsIntoPool();
    }

    private void createViewHolder(@OmniboxSuggestionUiType int viewType) {
        if (mAdapter == null || mStopCreatingViews) return;
        try (TraceEvent t = TraceEvent.scoped("PreWarmingRecycledViewPool.createNextViewHolder")) {
            mPrewarmedViews.add(mAdapter.createViewHolder(mPlaceholderParent, viewType));
        }
    }

    private void putViewsIntoPool() {
        for (var viewHolder : mPrewarmedViews) {
            putRecycledView(viewHolder);
        }
        mPrewarmedViews.clear();
    }

    @Override
    public ViewHolder getRecycledView(int viewType) {
        stopCreatingViews();
        ViewHolder result = super.getRecycledView(viewType);
        if (result == null) {
            OmniboxMetrics.recordSuggestionsViewCreatedType(viewType);
        } else {
            OmniboxMetrics.recordSuggestionsViewReusedType(viewType);
        }
        return result;
    }
}
