// Copyright 2019 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.tab;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.UserData;
import org.chromium.ui.base.WindowAndroid;

/**
 * Helper that coordinates the browser controls offsets from the perspective of a particular Tab.
 */
public class TabBrowserControlsOffsetHelper extends EmptyTabObserver implements UserData {
    private static final Class<TabBrowserControlsOffsetHelper> USER_DATA_KEY =
            TabBrowserControlsOffsetHelper.class;

    private TabImpl mTab;

    private int mTopControlsOffset;
    private int mBottomControlsOffset;
    private int mContentOffset;
    private int mTopControlsMinHeightOffset;
    private int mBottomControlsMinHeightOffset;

    /** {@code true} if offset was changed by compositor. */
    private boolean mOffsetInitialized;

    /**
     * Get (or lazily create) the offset helper for a particular Tab.
     * @param tab The tab whose helper is being retrieved.
     * @return The offset helper for a given tab.
     */
    public static @NonNull TabBrowserControlsOffsetHelper get(Tab tab) {
        TabBrowserControlsOffsetHelper helper = tab.getUserDataHost().getUserData(USER_DATA_KEY);
        if (helper == null) {
            helper = new TabBrowserControlsOffsetHelper(tab);
            tab.getUserDataHost().setUserData(USER_DATA_KEY, helper);
        }
        return helper;
    }

    private TabBrowserControlsOffsetHelper(Tab tab) {
        mTab = (TabImpl) tab;
        tab.addObserver(this);
    }

    @Override
    public void destroy() {
        mTab.removeObserver(this);
        mTab = null;
    }

    /**
     * Sets new top control, content, and min-height offset from renderer.
     * @param topControlsOffset Top control offset.
     * @param contentOffset Content offset.
     * @param topControlsMinHeightOffset Current min-height offset for the top controls that may be
     *                                   changing as a result of an in-progress min-height change
     *                                   animation in the renderer.
     */
    void setTopOffset(int topControlsOffset, int contentOffset, int topControlsMinHeightOffset) {
        if (mOffsetInitialized && topControlsOffset == mTopControlsOffset
                && mContentOffset == contentOffset
                && mTopControlsMinHeightOffset == topControlsMinHeightOffset) {
            return;
        }
        mTopControlsOffset = topControlsOffset;
        mContentOffset = contentOffset;
        mTopControlsMinHeightOffset = topControlsMinHeightOffset;
        notifyControlsOffsetChanged();
    }

    /**
     * Sets new bottom control offset from renderer.
     * @param bottomControlsOffset Bottom control offset.
     * @param bottomControlsMinHeightOffset Current min-height offset for the bottom controls that
     *                                      may be changing as a result of an in-progress min-height
     *                                      change animation in the renderer.
     */
    void setBottomOffset(int bottomControlsOffset, int bottomControlsMinHeightOffset) {
        if (mOffsetInitialized && mBottomControlsOffset == bottomControlsOffset
                && mBottomControlsMinHeightOffset == bottomControlsMinHeightOffset) {
            return;
        }
        mBottomControlsOffset = bottomControlsOffset;
        mBottomControlsMinHeightOffset = bottomControlsMinHeightOffset;
        notifyControlsOffsetChanged();
    }

    private void notifyControlsOffsetChanged() {
        mOffsetInitialized = true;
        RewindableIterator<TabObserver> observers = mTab.getTabObservers();
        while (observers.hasNext()) {
            observers.next().onBrowserControlsOffsetChanged(mTab, mTopControlsOffset,
                    mBottomControlsOffset, mContentOffset, mTopControlsMinHeightOffset,
                    mBottomControlsMinHeightOffset);
        }
    }

    @Override
    public void onCrash(Tab tab) {
        super.onCrash(tab);
        mTopControlsOffset = 0;
        mBottomControlsOffset = 0;
        mContentOffset = 0;
        mOffsetInitialized = false;
    }

    @Override
    public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
        // Intentionally do nothing to prevent automatic observer removal on detachment.
    }

    /** @return Top control offset */
    public int topControlsOffset() {
        return mTopControlsOffset;
    }

    /** @return Bottom control offset */
    public int bottomControlsOffset() {
        return mBottomControlsOffset;
    }

    /** @return Content offset */
    public int contentOffset() {
        return mContentOffset;
    }

    /** @return Top controls min-height offset */
    public int topControlsMinHeightOffset() {
        return mTopControlsMinHeightOffset;
    }

    /** @return Bottom controls min-height offset */
    public int bottomControlsMinHeightOffset() {
        return mBottomControlsMinHeightOffset;
    }

    /** @return Whether the control offset is initialized by compositor. */
    public boolean offsetInitialized() {
        return mOffsetInitialized;
    }
}
