/*
 * 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.applications.assist;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.speech.RecognitionService;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

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

public final class VoiceInputHelper {
    static final String TAG = "VoiceInputHelper";
    final Context mContext;

    final List<ResolveInfo> mAvailableVoiceInteractions;
    final List<ResolveInfo> mAvailableRecognition;

    static public class BaseInfo implements Comparable {
        public final ServiceInfo service;
        public final ComponentName componentName;
        public final String key;
        public final ComponentName settings;
        public final CharSequence label;
        public final String labelStr;
        public final CharSequence appLabel;

        public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) {
            service = _service;
            componentName = new ComponentName(_service.packageName, _service.name);
            key = componentName.flattenToShortString();
            settings = _settings != null
                    ? new ComponentName(_service.packageName, _settings) : null;
            label = _service.loadLabel(pm);
            labelStr = label.toString();
            appLabel = _service.applicationInfo.loadLabel(pm);
        }

        @Override
        public int compareTo(Object another) {
            return labelStr.compareTo(((BaseInfo) another).labelStr);
        }
    }

    static public class InteractionInfo extends BaseInfo {
        public final VoiceInteractionServiceInfo serviceInfo;

        public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) {
            super(pm, _service.getServiceInfo(), _service.getSettingsActivity());
            serviceInfo = _service;
        }
    }

    static public class RecognizerInfo extends BaseInfo {
        public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) {
            super(pm, _service, _settings);
        }
    }

    final ArrayList<InteractionInfo> mAvailableInteractionInfos = new ArrayList<>();
    final ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>();

    ComponentName mCurrentVoiceInteraction;
    ComponentName mCurrentRecognizer;

    public VoiceInputHelper(Context context) {
        mContext = context;

        mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices(
                new Intent(VoiceInteractionService.SERVICE_INTERFACE),
                PackageManager.GET_META_DATA);
        mAvailableRecognition = mContext.getPackageManager().queryIntentServices(
                new Intent(RecognitionService.SERVICE_INTERFACE),
                PackageManager.GET_META_DATA);
    }

    public void buildUi() {
        // Get the currently selected interactor from the secure setting.
        String currentSetting = Settings.Secure.getString(
                mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
        if (currentSetting != null && !currentSetting.isEmpty()) {
            mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting);
        } else {
            mCurrentVoiceInteraction = null;
        }

        ArraySet<ComponentName> interactorRecognizers = new ArraySet<>();

        // Iterate through all the available interactors and load up their info to show
        // in the preference.
        int size = mAvailableVoiceInteractions.size();
        for (int i = 0; i < size; i++) {
            ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i);
            VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo(
                    mContext.getPackageManager(), resolveInfo.serviceInfo);
            if (info.getParseError() != null) {
                Log.w("VoiceInteractionService", "Error in VoiceInteractionService "
                        + resolveInfo.serviceInfo.packageName + "/"
                        + resolveInfo.serviceInfo.name + ": " + info.getParseError());
                continue;
            }
            mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info));
            interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName,
                    info.getRecognitionService()));
        }
        Collections.sort(mAvailableInteractionInfos);

        // Get the currently selected recognizer from the secure setting.
        currentSetting = Settings.Secure.getString(
                mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
        if (currentSetting != null && !currentSetting.isEmpty()) {
            mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting);
        } else {
            mCurrentRecognizer = null;
        }

        // Iterate through all the available recognizers and load up their info to show
        // in the preference.
        size = mAvailableRecognition.size();
        for (int i = 0; i < size; i++) {
            ResolveInfo resolveInfo = mAvailableRecognition.get(i);
            ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName,
                    resolveInfo.serviceInfo.name);
            if (interactorRecognizers.contains(comp)) {
                //continue;
            }
            ServiceInfo si = resolveInfo.serviceInfo;
            XmlResourceParser parser = null;
            String settingsActivity = null;
            try {
                parser = si.loadXmlMetaData(mContext.getPackageManager(),
                        RecognitionService.SERVICE_META_DATA);
                if (parser == null) {
                    throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
                            " meta-data for " + si.packageName);
                }

                Resources res = mContext.getPackageManager().getResourcesForApplication(
                        si.applicationInfo);

                AttributeSet attrs = Xml.asAttributeSet(parser);

                int type;
                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                        && type != XmlPullParser.START_TAG) {
                }

                String nodeName = parser.getName();
                if (!"recognition-service".equals(nodeName)) {
                    throw new XmlPullParserException(
                            "Meta-data does not start with recognition-service tag");
                }

                TypedArray array = res.obtainAttributes(attrs,
                        com.android.internal.R.styleable.RecognitionService);
                settingsActivity = array.getString(
                        com.android.internal.R.styleable.RecognitionService_settingsActivity);
                array.recycle();
            } catch (XmlPullParserException e) {
                Log.e(TAG, "error parsing recognition service meta-data", e);
            } catch (IOException e) {
                Log.e(TAG, "error parsing recognition service meta-data", e);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "error parsing recognition service meta-data", e);
            } finally {
                if (parser != null) parser.close();
            }
            mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(),
                    resolveInfo.serviceInfo, settingsActivity));
        }
        Collections.sort(mAvailableRecognizerInfos);
    }
}
