// Copyright 2015 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.

package org.chromium.chrome.browser.ntp;

import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.SystemClock;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;

import org.chromium.base.ActivityState;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.NativePage;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider;
import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.util.ViewUtils;

import java.util.concurrent.TimeUnit;

/**
 * The native recent tabs page. Lists recently closed tabs, open windows and tabs from the user's
 * synced devices, and snapshot documents sent from Chrome to Mobile in an expandable list view.
 */
public class RecentTabsPage
        implements NativePage, ApplicationStatus.ActivityStateListener,
                   ExpandableListView.OnChildClickListener,
                   ExpandableListView.OnGroupCollapseListener,
                   ExpandableListView.OnGroupExpandListener, RecentTabsManager.UpdatedCallback,
                   View.OnAttachStateChangeListener, View.OnCreateContextMenuListener,
                   InvalidationAwareThumbnailProvider {
    private final Activity mActivity;
    private final ExpandableListView mListView;
    private final String mTitle;
    private final ViewGroup mView;

    private RecentTabsManager mRecentTabsManager;
    private RecentTabsRowAdapter mAdapter;

    private boolean mSnapshotContentChanged;
    private int mSnapshotListPosition;
    private int mSnapshotListTop;
    private int mSnapshotWidth;
    private int mSnapshotHeight;

    private final int mThemeColor;

    /**
     * Whether the page is in the foreground and is visible.
     */
    private boolean mInForeground;

    /**
     * Whether {@link #mView} is attached to the application window.
     */
    private boolean mIsAttachedToWindow;

    /**
     * The time, whichever is most recent, that the page:
     * - Moved to the foreground
     * - Became visible
     */
    private long mForegroundTimeMs;

    /**
     * Constructor returns an instance of RecentTabsPage.
     *
     * @param activity The activity this view belongs to.
     * @param recentTabsManager The RecentTabsManager which provides the model data.
     */
    public RecentTabsPage(Activity activity, RecentTabsManager recentTabsManager) {
        mActivity = activity;
        mRecentTabsManager = recentTabsManager;

        mTitle = activity.getResources().getString(R.string.recent_tabs);
        mThemeColor = ApiCompatibilityUtils.getColor(
                activity.getResources(), R.color.default_primary_color);
        mRecentTabsManager.setUpdatedCallback(this);
        LayoutInflater inflater = LayoutInflater.from(activity);
        mView = (ViewGroup) inflater.inflate(R.layout.recent_tabs_page, null);
        mListView = (ExpandableListView) mView.findViewById(R.id.odp_listview);
        mAdapter = buildAdapter(activity, recentTabsManager);
        mListView.setAdapter(mAdapter);
        mListView.setOnChildClickListener(this);
        mListView.setGroupIndicator(null);
        mListView.setOnGroupCollapseListener(this);
        mListView.setOnGroupExpandListener(this);
        mListView.setOnCreateContextMenuListener(this);

        mView.addOnAttachStateChangeListener(this);
        ApplicationStatus.registerStateListenerForActivity(this, activity);
        // {@link #mInForeground} will be updated once the view is attached to the window.

        onUpdated();
    }

    private static RecentTabsRowAdapter buildAdapter(Activity activity,
            RecentTabsManager recentTabsManager) {
        return new RecentTabsRowAdapter(activity, recentTabsManager);
    }

    /**
     * Updates whether the page is in the foreground based on whether the application is in the
     * foreground and whether {@link #mView} is attached to the application window. If the page is
     * no longer in the foreground, records the time that the page spent in the foreground to UMA.
     */
    private void updateForegroundState() {
        boolean inForeground = mIsAttachedToWindow
                && ApplicationStatus.getStateForActivity(mActivity) == ActivityState.RESUMED;
        if (mInForeground == inForeground) {
            return;
        }

        mInForeground = inForeground;
        if (mInForeground) {
            mForegroundTimeMs = SystemClock.elapsedRealtime();
            StartupMetrics.getInstance().recordOpenedRecents();
        } else {
            RecordHistogram.recordLongTimesHistogram("NewTabPage.RecentTabsPage.TimeVisibleAndroid",
                    SystemClock.elapsedRealtime() - mForegroundTimeMs, TimeUnit.MILLISECONDS);
        }
    }

    // NativePage overrides

    @Override
    public String getUrl() {
        return UrlConstants.RECENT_TABS_URL;
    }

    @Override
    public String getTitle() {
        return mTitle;
    }

    @Override
    public int getBackgroundColor() {
        return Color.WHITE;
    }

    @Override
    public int getThemeColor() {
        return mThemeColor;
    }

    @Override
    public boolean needsToolbarShadow() {
        return true;
    }

    @Override
    public View getView() {
        return mView;
    }

    @Override
    public String getHost() {
        return UrlConstants.RECENT_TABS_HOST;
    }

    @Override
    public void destroy() {
        assert getView().getParent() == null : "Destroy called before removed from window";
        mRecentTabsManager.destroy();
        mRecentTabsManager = null;
        mAdapter.notifyDataSetInvalidated();
        mAdapter = null;
        mListView.setAdapter((RecentTabsRowAdapter) null);

        mView.removeOnAttachStateChangeListener(this);
        ApplicationStatus.unregisterActivityStateListener(this);
    }

    @Override
    public void updateForUrl(String url) {
    }

    // ApplicationStatus.ActivityStateListener
    @Override
    public void onActivityStateChange(Activity activity, int state) {
        // Called when the user locks the screen or moves Chrome to the background via the task
        // switcher.
        updateForegroundState();
    }

    // View.OnAttachStateChangeListener
    @Override
    public void onViewAttachedToWindow(View view) {
        // Called when the user opens the RecentTabsPage or switches back to the RecentTabsPage from
        // another tab.
        mIsAttachedToWindow = true;
        updateForegroundState();

        // Work around a bug on Samsung devices where the recent tabs page does not appear after
        // toggling the Sync quick setting.  For some reason, the layout is being dropped on the
        // flow and we need to force a root level layout to get the UI to appear.
        view.getRootView().requestLayout();
    }

    @Override
    public void onViewDetachedFromWindow(View view) {
        // Called when the user navigates from the RecentTabsPage or switches to another tab.
        mIsAttachedToWindow = false;
        updateForegroundState();
    }

    // ExpandableListView.OnChildClickedListener
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
            int childPosition, long id) {
        return mAdapter.getGroup(groupPosition).onChildClick(childPosition);
    }

    // ExpandableListView.OnGroupExpandedListener
    @Override
    public void onGroupExpand(int groupPosition) {
        mAdapter.getGroup(groupPosition).setCollapsed(false);
        mSnapshotContentChanged = true;
    }

    // ExpandableListView.OnGroupCollapsedListener
    @Override
    public void onGroupCollapse(int groupPosition) {
        mAdapter.getGroup(groupPosition).setCollapsed(true);
        mSnapshotContentChanged = true;
    }

    // RecentTabsManager.UpdatedCallback
    @Override
    public void onUpdated() {
        mAdapter.notifyDataSetChanged();
        for (int i = 0; i < mAdapter.getGroupCount(); i++) {
            if (mAdapter.getGroup(i).isCollapsed()) {
                mListView.collapseGroup(i);
            } else {
                mListView.expandGroup(i);
            }
        }
        mSnapshotContentChanged = true;
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        // Would prefer to have this context menu view managed internal to RecentTabsGroupView
        // Unfortunately, setting either onCreateContextMenuListener or onLongClickListener
        // disables the native onClick (expand/collapse) behaviour of the group view.
        ExpandableListView.ExpandableListContextMenuInfo info =
                (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;

        int type = ExpandableListView.getPackedPositionType(info.packedPosition);
        int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);

        if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            mAdapter.getGroup(groupPosition).onCreateContextMenuForGroup(menu, mActivity);
        } else if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
            int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
            mAdapter.getGroup(groupPosition).onCreateContextMenuForChild(childPosition, menu,
                    mActivity);
        }
    }

    // InvalidationAwareThumbnailProvider

    @Override
    public boolean shouldCaptureThumbnail() {
        if (mView.getWidth() == 0 || mView.getHeight() == 0) return false;

        View topItem = mListView.getChildAt(0);
        return mSnapshotContentChanged
                || mSnapshotListPosition != mListView.getFirstVisiblePosition()
                || mSnapshotListTop != (topItem == null ? 0 : topItem.getTop())
                || mView.getWidth() != mSnapshotWidth
                || mView.getHeight() != mSnapshotHeight;
    }

    @Override
    public void captureThumbnail(Canvas canvas) {
        ViewUtils.captureBitmap(mView, canvas);
        mSnapshotContentChanged = false;
        mSnapshotListPosition = mListView.getFirstVisiblePosition();
        View topItem = mListView.getChildAt(0);
        mSnapshotListTop = topItem == null ? 0 : topItem.getTop();
        mSnapshotWidth = mView.getWidth();
        mSnapshotHeight = mView.getHeight();
    }
}