/*
 * Copyright (C) 2017 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.settings.dashboard;

import static android.provider.SearchIndexablesContract.SITE_MAP_COLUMNS;
import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.SearchIndexablesContract.SiteMapColumns;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
import android.util.Log;

import com.android.settings.SettingsActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.search.IndexDatabaseHelper.IndexColumns;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;

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

/**
 * A manager class that maintains a "site map" and look up breadcrumb for a certain page on demand.
 * <p/>
 * The methods on this class can only be called on a background thread.
 */
public class SiteMapManager {

    private static final String TAG = "SiteMapManager";
    private static final boolean DEBUG_TIMING = false;

    private static final String[] CLASS_TO_SCREEN_TITLE_COLUMNS = {
            IndexColumns.CLASS_NAME,
            IndexColumns.SCREEN_TITLE,
    };

    private final List<SiteMapPair> mPairs = new ArrayList<>();

    private boolean mInitialized;

    /**
     * Given a fragment class name and its screen title, build a breadcrumb from Settings root to
     * this screen.
     * <p/>
     * Not all screens have a full breadcrumb path leading up to root, it's because either some
     * page in the breadcrumb path is not indexed, or it's only reachable via search.
     */
    @WorkerThread
    public synchronized List<String> buildBreadCrumb(Context context, String clazz,
            String screenTitle) {
        init(context);
        final long startTime = System.currentTimeMillis();
        final List<String> breadcrumbs = new ArrayList<>();
        if (!mInitialized) {
            Log.w(TAG, "SiteMap is not initialized yet, skipping");
            return breadcrumbs;
        }
        breadcrumbs.add(screenTitle);
        String currentClass = clazz;
        String currentTitle = screenTitle;
        // Look up current page's parent, if found add it to breadcrumb string list, and repeat.
        while (true) {
            final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
            if (pair == null) {
                if (DEBUG_TIMING) {
                    Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
                }
                return breadcrumbs;
            }
            breadcrumbs.add(0, pair.parentTitle);
            currentClass = pair.parentClass;
            currentTitle = pair.parentTitle;
        }
    }

    /**
     * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
     * page relationship.
     *
     * We get the knowledge of such mPairs from 2 sources:
     * 1. Static indexing time: we know which page(s) a parent can open by parsing its pref xml.
     * 2. IA: We know from {@link DashboardFeatureProvider} which page can be dynamically
     * injected to where.
     */
    @VisibleForTesting
    @WorkerThread
    synchronized void init(Context context) {
        if (mInitialized) {
            // Make sure only init once.
            return;
        }
        final long startTime = System.currentTimeMillis();
        // First load site map from static index table.
        final Context appContext = context.getApplicationContext();
        final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
        Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
                null, null, null, null);
        while (sitemap.moveToNext()) {
            final SiteMapPair pair = new SiteMapPair(
                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
            mPairs.add(pair);
        }
        sitemap.close();

        // Then prepare a local map that contains class name -> screen title mapping. This is needed
        // to figure out the display name for any fragment if it's injected dynamically through IA.
        final Map<String, String> classToTitleMap = new ArrayMap<>();
        final Cursor titleQuery = db.query(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX,
                CLASS_TO_SCREEN_TITLE_COLUMNS, null, null, null, null, null);
        while (titleQuery.moveToNext()) {
            classToTitleMap.put(
                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.CLASS_NAME)),
                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.SCREEN_TITLE)));
        }
        titleQuery.close();

        // Loop through all IA categories and pages and build additional SiteMapPairs
        List<DashboardCategory> categories = FeatureFactory.getFactory(context)
                .getDashboardFeatureProvider(context).getAllCategories();

        for (DashboardCategory category : categories) {
            // Find the category key first.
            final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
            if (parentClass == null) {
                continue;
            }
            // Use the key to look up parent (which page hosts this key)
            final String parentName = classToTitleMap.get(parentClass);
            if (parentName == null) {
                continue;
            }
            // Build parent-child mPairs for all children listed under this key.
            for (Tile tile : category.getTiles()) {
                final String childTitle = tile.title.toString();
                String childClass = null;
                if (tile.metaData != null) {
                    childClass = tile.metaData.getString(
                            SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
                }
                if (childClass == null) {
                    continue;
                }
                mPairs.add(new SiteMapPair(parentClass, parentName, childClass, childTitle));
            }
        }
        // Done.
        mInitialized = true;
        if (DEBUG_TIMING) {
            Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
        }
    }

    @WorkerThread
    private SiteMapPair lookUpParent(String clazz, String title) {
        for (SiteMapPair pair : mPairs) {
            if (TextUtils.equals(pair.childClass, clazz)
                    && TextUtils.equals(title, pair.childTitle)) {
                return pair;
            }
        }
        return null;
    }

    /**
     * Data model for a parent-child page pair.
     */
    private static class SiteMapPair {
        public final String parentClass;
        public final String parentTitle;
        public final String childClass;
        public final String childTitle;

        public SiteMapPair(String parentClass, String parentTitle, String childClass,
                String childTitle) {
            this.parentClass = parentClass;
            this.parentTitle = parentTitle;
            this.childClass = childClass;
            this.childTitle = childTitle;
        }
    }
}
