// Copyright 2014 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.tabmodel;

import org.chromium.base.ObserverList;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;

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

/**
 * Implement methods shared across the different model implementations.
 */
public abstract class TabModelSelectorBase implements TabModelSelector {
    public static final int NORMAL_TAB_MODEL_INDEX = 0;
    public static final int INCOGNITO_TAB_MODEL_INDEX = 1;

    private static TabModelSelectorObserver sObserver;

    private List<TabModel> mTabModels = Collections.emptyList();
    private int mActiveModelIndex = NORMAL_TAB_MODEL_INDEX;
    private final ObserverList<TabModelSelectorObserver> mObservers =
            new ObserverList<TabModelSelectorObserver>();
    private boolean mTabStateInitialized;

    protected final void initialize(boolean startIncognito, TabModel... models) {
        // Only normal and incognito supported for now.
        assert mTabModels.isEmpty();
        assert models.length > 0;
        if (startIncognito) {
            assert models.length > INCOGNITO_TAB_MODEL_INDEX;
        }

        List<TabModel> tabModels = new ArrayList<TabModel>();
        for (int i = 0; i < models.length; i++) {
            tabModels.add(models[i]);
        }
        mActiveModelIndex = startIncognito ? INCOGNITO_TAB_MODEL_INDEX : NORMAL_TAB_MODEL_INDEX;
        mTabModels = Collections.unmodifiableList(tabModels);

        TabModelObserver tabModelObserver = new EmptyTabModelObserver() {
            @Override
            public void didAddTab(Tab tab, TabLaunchType type) {
                notifyChanged();
                notifyNewTabCreated(tab);
            }

            @Override
            public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {
                notifyChanged();
            }

            @Override
            public void didMoveTab(Tab tab, int newIndex, int curIndex) {
                notifyChanged();
            }
        };
        for (TabModel model : models) {
            model.addObserver(tabModelObserver);
        }

        if (sObserver != null) {
            addObserver(sObserver);
        }

        notifyChanged();
    }

    public static void setObserverForTests(TabModelSelectorObserver observer) {
        sObserver = observer;
    }

    @Override
    public void selectModel(boolean incognito) {
        TabModel previousModel = getCurrentModel();
        mActiveModelIndex = incognito ? INCOGNITO_TAB_MODEL_INDEX : NORMAL_TAB_MODEL_INDEX;
        TabModel newModel = getCurrentModel();

        if (previousModel != newModel) {
            for (TabModelSelectorObserver listener : mObservers) {
                listener.onTabModelSelected(newModel, previousModel);
            }
        }
    }

    @Override
    public TabModel getModelAt(int index) {
        assert (index < mTabModels.size() && index >= 0) :
            "requested index " + index + " size " + mTabModels.size();
        return mTabModels.get(index);
    }

    @Override
    public Tab getCurrentTab() {
        return getCurrentModel() == null ? null : TabModelUtils.getCurrentTab(getCurrentModel());
    }

    @Override
    public int getCurrentTabId() {
        Tab tab = getCurrentTab();
        return tab != null ? tab.getId() : Tab.INVALID_TAB_ID;
    }

    @Override
    public TabModel getModelForTabId(int id) {
        for (int i = 0; i < mTabModels.size(); i++) {
            TabModel model = mTabModels.get(i);
            if (TabModelUtils.getTabById(model, id) != null || model.isClosurePending(id)) {
                return model;
            }
        }
        return null;
    }

    @Override
    public TabModel getCurrentModel() {
        return getModelAt(mActiveModelIndex);
    }

    @Override
    public int getCurrentModelIndex() {
        return mActiveModelIndex;
    }

    @Override
    public TabModel getModel(boolean incognito) {
        int index = incognito ? INCOGNITO_TAB_MODEL_INDEX : NORMAL_TAB_MODEL_INDEX;
        return getModelAt(index);
    }

    @Override
    public boolean isIncognitoSelected() {
        return mActiveModelIndex == INCOGNITO_TAB_MODEL_INDEX;
    }

    @Override
    public List<TabModel> getModels() {
        return mTabModels;
    }

    @Override
    public boolean closeTab(Tab tab) {
        for (int i = 0; i < getModels().size(); i++) {
            TabModel model = getModelAt(i);
            if (model.indexOf(tab) >= 0) {
                return model.closeTab(tab);
            }
        }
        assert false : "Tried to close a tab that is not in any model!";
        return false;
    }

    @Override
    public void commitAllTabClosures() {
        for (int i = 0; i < mTabModels.size(); i++) {
            mTabModels.get(i).commitAllTabClosures();
        }
    }

    @Override
    public Tab getTabById(int id) {
        for (int i = 0; i < getModels().size(); i++) {
            Tab tab = TabModelUtils.getTabById(getModelAt(i), id);
            if (tab != null) return tab;
        }
        return null;
    }

    @Override
    public void closeAllTabs() {
        closeAllTabs(false);
    }

    @Override
    public void closeAllTabs(boolean uponExit) {
        for (int i = 0; i < getModels().size(); i++) {
            getModelAt(i).closeAllTabs(!uponExit, uponExit);
        }
    }

    @Override
    public int getTotalTabCount() {
        int count = 0;
        for (int i = 0; i < getModels().size(); i++)  {
            count += getModelAt(i).getCount();
        }
        return count;
    }

    @Override
    public void addObserver(TabModelSelectorObserver observer) {
        if (!mObservers.hasObserver(observer)) mObservers.addObserver(observer);
    }

    @Override
    public void removeObserver(TabModelSelectorObserver observer) {
        mObservers.removeObserver(observer);
    }

    @Override
    public void setCloseAllTabsDelegate(CloseAllTabsDelegate delegate) { }

    /**
     * Marks the task state being initialized and notifies observers.
     */
    public void markTabStateInitialized() {
        mTabStateInitialized = true;
        for (TabModelSelectorObserver listener : mObservers) listener.onTabStateInitialized();
    }

    @Override
    public boolean isTabStateInitialized() {
        return mTabStateInitialized;
    }

    @Override
    public void destroy() {
        for (int i = 0; i < getModels().size(); i++) getModelAt(i).destroy();
    }

    /**
     * Notifies all the listeners that the {@link TabModelSelector} or its {@link TabModel} has
     * changed.
     */
    protected void notifyChanged() {
        for (TabModelSelectorObserver listener : mObservers) {
            listener.onChange();
        }
    }

    /**
     * Notifies all the listeners that a new tab has been created.
     * @param tab The tab that has been created.
     */
    private void notifyNewTabCreated(Tab tab) {
        for (TabModelSelectorObserver listener : mObservers) {
            listener.onNewTabCreated(tab);
        }
    }
}