/*
 * 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.intelligence.search.indexing;

import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;

import com.android.settings.intelligence.search.ResultPayload;
import com.android.settings.intelligence.search.ResultPayloadUtils;

import java.text.Normalizer;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 * Data class representing a single row in the Setting Search results database.
 */
public class IndexData {
    /**
     * This is different from intentTargetPackage.
     *
     * @see SearchIndexableData#iconResId
     */
    public final String packageName;
    public final String authority;
    public final String locale;
    public final String updatedTitle;
    public final String normalizedTitle;
    public final String updatedSummaryOn;
    public final String normalizedSummaryOn;
    public final String entries;
    public final String className;
    public final String childClassName;
    public final String screenTitle;
    public final int iconResId;
    public final String spaceDelimitedKeywords;
    public final String intentAction;
    public final String intentTargetPackage;
    public final String intentTargetClass;
    public final boolean enabled;
    public final String key;
    public final int payloadType;
    public final byte[] payload;
    public final String highlightableMenuKey; // the key of top level settings row
    public final String topLevelMenuKey; // the key for highlighting the menu entry

    private final Builder mBuilder;
    private static final String NON_BREAKING_HYPHEN = "\u2011";
    private static final String EMPTY = "";
    private static final String HYPHEN = "-";
    private static final String SPACE = " ";
    // Regex matching a comma, and any number of subsequent white spaces.
    private static final String LIST_DELIMITERS = "[,]\\s*";

    private static final Pattern REMOVE_DIACRITICALS_PATTERN
            = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");

    protected IndexData(Builder builder) {
        locale = Locale.getDefault().toString();
        updatedTitle = normalizeHyphen(builder.mTitle);
        updatedSummaryOn = normalizeHyphen(builder.mSummaryOn);
        if (Locale.JAPAN.toString().equalsIgnoreCase(locale)) {
            // Special case for JP. Convert charset to the same type for indexing purpose.
            normalizedTitle = normalizeJapaneseString(builder.mTitle);
            normalizedSummaryOn = normalizeJapaneseString(builder.mSummaryOn);
        } else {
            normalizedTitle = normalizeString(builder.mTitle);
            normalizedSummaryOn = normalizeString(builder.mSummaryOn);
        }
        entries = builder.mEntries;
        className = builder.mClassName;
        childClassName = builder.mChildClassName;
        screenTitle = builder.mScreenTitle;
        iconResId = builder.mIconResId;
        spaceDelimitedKeywords = normalizeKeywords(builder.mKeywords);
        intentAction = builder.mIntentAction;
        packageName = builder.mPackageName;
        authority = builder.mAuthority;
        intentTargetPackage = builder.mIntentTargetPackage;
        intentTargetClass = builder.mIntentTargetClass;
        enabled = builder.mEnabled;
        key = builder.mKey;
        payloadType = builder.mPayloadType;
        payload = builder.mPayload != null ? ResultPayloadUtils.marshall(builder.mPayload)
                : null;
        highlightableMenuKey = builder.mHighlightableMenuKey;
        topLevelMenuKey = builder.mTopLevelMenuKey;
        mBuilder = builder;
    }

    /** Returns the builder of the IndexData. */
    public Builder mutate() {
        return mBuilder;
    }

    @Override
    public String toString() {
        return new StringBuilder(updatedTitle)
                .append(": ")
                .append(updatedSummaryOn)
                .toString();
    }

    /**
     * In the list of keywords, replace the comma and all subsequent whitespace with a single space.
     */
    public static String normalizeKeywords(String input) {
        return (input != null) ? input.replaceAll(LIST_DELIMITERS, SPACE) : EMPTY;
    }

    /**
     * @return {@param input} where all non-standard hyphens are replaced by normal hyphens.
     */
    public static String normalizeHyphen(String input) {
        return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
    }

    /**
     * @return {@param input} with all hyphens removed, and all letters lower case.
     */
    public static String normalizeString(String input) {
        final String normalizedHypen = normalizeHyphen(input);
        final String nohyphen = (input != null) ? normalizedHypen.replaceAll(HYPHEN, EMPTY) : EMPTY;
        final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD);

        return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase();
    }

    public static String normalizeJapaneseString(String input) {
        final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY;
        final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFKD);
        final StringBuffer sb = new StringBuffer();
        final int length = normalized.length();
        for (int i = 0; i < length; i++) {
            char c = normalized.charAt(i);
            // Convert Hiragana to full-width Katakana
            if (c >= '\u3041' && c <= '\u3096') {
                sb.append((char) (c - '\u3041' + '\u30A1'));
            } else {
                sb.append(c);
            }
        }

        return REMOVE_DIACRITICALS_PATTERN.matcher(sb.toString()).replaceAll("").toLowerCase();
    }

    public static class Builder {
        private String mTitle;
        private String mSummaryOn;
        private String mEntries;
        private String mClassName;
        private String mChildClassName;
        private String mScreenTitle;
        private String mPackageName;
        private String mAuthority;
        private int mIconResId;
        private String mKeywords;
        private String mIntentAction;
        private String mIntentTargetPackage;
        private String mIntentTargetClass;
        private boolean mEnabled;
        private String mKey;
        @ResultPayload.PayloadType
        private int mPayloadType;
        private ResultPayload mPayload;
        private String mHighlightableMenuKey;
        private String mTopLevelMenuKey;

        @Override
        public String toString() {
            return "IndexData.Builder {"
                    + "title: " + mTitle + ","
                    + "package: " + mPackageName
                    + "}";
        }

        public Builder setTitle(String title) {
            mTitle = title;
            return this;
        }

        public String getKey() {
            return mKey;
        }

        public String getIntentAction() {
            return mIntentAction;
        }

        public String getIntentTargetPackage() {
            return mIntentTargetPackage;
        }

        public String getIntentTargetClass() {
            return mIntentTargetClass;
        }

        public Builder setSummaryOn(String summaryOn) {
            mSummaryOn = summaryOn;
            return this;
        }

        public Builder setEntries(String entries) {
            mEntries = entries;
            return this;
        }

        public Builder setClassName(String className) {
            mClassName = className;
            return this;
        }

        public Builder setChildClassName(String childClassName) {
            mChildClassName = childClassName;
            return this;
        }

        public Builder setScreenTitle(String screenTitle) {
            mScreenTitle = screenTitle;
            return this;
        }

        public Builder setPackageName(String packageName) {
            mPackageName = packageName;
            return this;
        }

        public Builder setAuthority(String authority) {
            mAuthority = authority;
            return this;
        }

        public Builder setIconResId(int iconResId) {
            mIconResId = iconResId;
            return this;
        }

        public Builder setKeywords(String keywords) {
            mKeywords = keywords;
            return this;
        }

        public Builder setIntentAction(String intentAction) {
            mIntentAction = intentAction;
            return this;
        }

        public Builder setIntentTargetPackage(String intentTargetPackage) {
            mIntentTargetPackage = intentTargetPackage;
            return this;
        }

        public Builder setIntentTargetClass(String intentTargetClass) {
            mIntentTargetClass = intentTargetClass;
            return this;
        }

        public Builder setEnabled(boolean enabled) {
            mEnabled = enabled;
            return this;
        }

        public Builder setKey(String key) {
            mKey = key;
            return this;
        }

        public Builder setPayload(ResultPayload payload) {
            mPayload = payload;

            if (mPayload != null) {
                setPayloadType(mPayload.getType());
            }
            return this;
        }

        public Builder setHighlightableMenuKey(String highlightableMenuKey) {
            mHighlightableMenuKey = highlightableMenuKey;
            return this;
        }

        Builder setTopLevelMenuKey(String topLevelMenuKey) {
            mTopLevelMenuKey = topLevelMenuKey;
            mPayload = null; // clear the payload to rebuild intent
            return this;
        }

        /**
         * Payload type is added when a Payload is added to the Builder in {setPayload}
         *
         * @param payloadType PayloadType
         * @return The Builder
         */
        private Builder setPayloadType(@ResultPayload.PayloadType int payloadType) {
            mPayloadType = payloadType;
            return this;
        }

        /**
         * Adds intent to inline payloads, or creates an Intent Payload as a fallback if the
         * payload is null.
         */
        private void setIntent() {
            if (mPayload != null) {
                return;
            }
            final Intent intent = buildIntent();
            mPayload = new ResultPayload(intent);
            mPayloadType = ResultPayload.PayloadType.INTENT;
        }

        /**
         * Builds Intent payload for the builder.
         * This protected method that can be overridden in a subclass for custom intents.
         */
        protected Intent buildIntent() {
            final Intent intent;

            // TODO REFACTOR (b/62807132) With inline results re-add proper intent support
            boolean isEmptyIntentAction = TextUtils.isEmpty(mIntentAction);
            if (isEmptyIntentAction) {
                // No intent action is set, or the intent action is for a sub-setting.
                intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mClassName, mKey,
                        mScreenTitle, mTopLevelMenuKey);
            } else {
                if (!TextUtils.isEmpty(mTopLevelMenuKey)) {
                    intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(mIntentAction,
                            mIntentTargetPackage, mIntentTargetClass, mKey, mTopLevelMenuKey);
                } else {
                    intent = DatabaseIndexingUtils.buildDirectSearchResultIntent(mIntentAction,
                            mIntentTargetPackage, mIntentTargetClass, mKey);
                }
            }
            return intent;
        }

        @Deprecated
        protected Intent buildIntent(Context context) {
            return buildIntent();
        }

        public IndexData build() {
            setIntent();
            return new IndexData(this);
        }
    }
}
