/*
 * Copyright (C) 2013 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.print;

import android.app.ActivityManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.print.PrintJob;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrintManager.PrintJobStateChangeListener;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
import android.widget.TextView;

import com.android.internal.content.PackageMonitor;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * Fragment with the top level print settings.
 */
public class PrintSettingsFragment extends SettingsPreferenceFragment implements DialogCreatable {

    static final char ENABLED_PRINT_SERVICES_SEPARATOR = ':';

    private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;

    private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
    private static final String PRINT_SERVICES_CATEGORY = "print_services_category";

    // Extras passed to sub-fragments.
    static final String EXTRA_PREFERENCE_KEY = "EXTRA_PREFERENCE_KEY";
    static final String EXTRA_CHECKED = "EXTRA_CHECKED";
    static final String EXTRA_TITLE = "EXTRA_TITLE";
    static final String EXTRA_ENABLE_WARNING_TITLE = "EXTRA_ENABLE_WARNING_TITLE";
    static final String EXTRA_ENABLE_WARNING_MESSAGE = "EXTRA_ENABLE_WARNING_MESSAGE";
    static final String EXTRA_SETTINGS_TITLE = "EXTRA_SETTINGS_TITLE";
    static final String EXTRA_SETTINGS_COMPONENT_NAME = "EXTRA_SETTINGS_COMPONENT_NAME";
    static final String EXTRA_ADD_PRINTERS_TITLE = "EXTRA_ADD_PRINTERS_TITLE";
    static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "EXTRA_ADD_PRINTERS_COMPONENT_NAME";
    static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";

    static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";

    private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
            "EXTRA_PRINT_SERVICE_COMPONENT_NAME";

    private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();

    private final Handler mHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            updateServicesPreferences();
        }
    };

    private final SettingsContentObserver mSettingsContentObserver =
            new SettingsContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            updateServicesPreferences();
        }
    };

    private PreferenceCategory mActivePrintJobsCategory;
    private PreferenceCategory mPrintServicesCategory;

    private PrintJobsController mPrintJobsController;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        addPreferencesFromResource(R.xml.print_settings);

        mActivePrintJobsCategory = (PreferenceCategory) findPreference(
                PRINT_JOBS_CATEGORY);
        mPrintServicesCategory= (PreferenceCategory) findPreference(
                PRINT_SERVICES_CATEGORY);
        getPreferenceScreen().removePreference(mActivePrintJobsCategory);

        mPrintJobsController = new PrintJobsController();
        getActivity().getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER,
                null, mPrintJobsController);
    }

    @Override
    public void onResume() {
        super.onResume();
        mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
        mSettingsContentObserver.register(getContentResolver());
        updateServicesPreferences();
        setHasOptionsMenu(true);
        startSubSettingsIfNeeded();
    }

    @Override
    public void onPause() {
        mSettingsPackageMonitor.unregister();
        mSettingsContentObserver.unregister(getContentResolver());
        super.onPause();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        String searchUri = Settings.Secure.getString(getContentResolver(),
                Settings.Secure.PRINT_SERVICE_SEARCH_URI);
        if (!TextUtils.isEmpty(searchUri)) {
            MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
            menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            menuItem.setIntent(new Intent(Intent.ACTION_VIEW,Uri.parse(searchUri)));
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ViewGroup contentRoot = (ViewGroup) getListView().getParent();
        View emptyView = getActivity().getLayoutInflater().inflate(
                    R.layout.empty_print_state, contentRoot, false);
        TextView textView = (TextView) emptyView.findViewById(R.id.message);
        textView.setText(R.string.print_no_services_installed);
        contentRoot.addView(emptyView);
        getListView().setEmptyView(emptyView);
    }

    private void updateServicesPreferences() {
        if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
            getPreferenceScreen().addPreference(mPrintServicesCategory);
        } else {
            // Since services category is auto generated we have to do a pass
            // to generate it since services can come and go.
            mPrintServicesCategory.removeAll();
        }

        List<ComponentName> enabledServices = SettingsUtils
                .readEnabledPrintServices(getActivity());

        List<ResolveInfo> installedServices = getActivity().getPackageManager()
                .queryIntentServices(
                        new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);

        final int installedServiceCount = installedServices.size();
        for (int i = 0; i < installedServiceCount; i++) {
            ResolveInfo installedService = installedServices.get(i);

            PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
                    getActivity());

            String title = installedService.loadLabel(getPackageManager()).toString();
            preference.setTitle(title);

            ComponentName componentName = new ComponentName(
                    installedService.serviceInfo.packageName,
                    installedService.serviceInfo.name);
            preference.setKey(componentName.flattenToString());

            preference.setOrder(i);
            preference.setFragment(PrintServiceSettingsFragment.class.getName());
            preference.setPersistent(false);

            final boolean serviceEnabled = enabledServices.contains(componentName);
            if (serviceEnabled) {
                preference.setSummary(getString(R.string.print_feature_state_on));
            } else {
                preference.setSummary(getString(R.string.print_feature_state_off));
            }

            Bundle extras = preference.getExtras();
            extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
            extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
            extras.putString(EXTRA_TITLE, title);

            PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
                    installedService, getActivity());

            CharSequence applicationLabel = installedService.loadLabel(getPackageManager());

            extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
                    R.string.print_service_security_warning_title, applicationLabel));
            extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
                    R.string.print_service_security_warning_summary, applicationLabel));

            String settingsClassName = printServiceInfo.getSettingsActivityName();
            if (!TextUtils.isEmpty(settingsClassName)) {
                extras.putString(EXTRA_SETTINGS_TITLE,
                        getString(R.string.print_menu_item_settings));
                extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
                        new ComponentName(installedService.serviceInfo.packageName,
                                settingsClassName).flattenToString());
            }

            String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
            if (!TextUtils.isEmpty(addPrinterClassName)) {
                extras.putString(EXTRA_ADD_PRINTERS_TITLE,
                        getString(R.string.print_menu_item_add_printers));
                extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
                        new ComponentName(installedService.serviceInfo.packageName,
                                addPrinterClassName).flattenToString());
            }

            extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());

            mPrintServicesCategory.addPreference(preference);
        }

        if (mPrintServicesCategory.getPreferenceCount() == 0) {
            getPreferenceScreen().removePreference(mPrintServicesCategory);
        }
    }

    private void startSubSettingsIfNeeded() {
        if (getArguments() == null) {
            return;
        }
        String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
        if (componentName != null) {
            getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
            Preference prereference = findPreference(componentName);
            if (prereference != null) {
                prereference.performClick(getPreferenceScreen());
            }
        }
    }

    private class SettingsPackageMonitor extends PackageMonitor {
        @Override
        public void onPackageAdded(String packageName, int uid) {
           mHandler.obtainMessage().sendToTarget();
        }

        @Override
        public void onPackageAppeared(String packageName, int reason) {
            mHandler.obtainMessage().sendToTarget();
        }

        @Override
        public void onPackageDisappeared(String packageName, int reason) {
            mHandler.obtainMessage().sendToTarget();
        }

        @Override
        public void onPackageRemoved(String packageName, int uid) {
            mHandler.obtainMessage().sendToTarget();
        }
    }

    public static class ToggleSwitch extends Switch {

        private OnBeforeCheckedChangeListener mOnBeforeListener;

        public static interface OnBeforeCheckedChangeListener {
            public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
        }

        public ToggleSwitch(Context context) {
            super(context);
        }

        public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
            mOnBeforeListener = listener;
        }

        @Override
        public void setChecked(boolean checked) {
            if (mOnBeforeListener != null
                    && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
                return;
            }
            super.setChecked(checked);
        }

        public void setCheckedInternal(boolean checked) {
            super.setChecked(checked);
        }
    }

    private static abstract class SettingsContentObserver extends ContentObserver {

        public SettingsContentObserver(Handler handler) {
            super(handler);
        }

        public void register(ContentResolver contentResolver) {
            contentResolver.registerContentObserver(Settings.Secure.getUriFor(
                    Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
        }

        public void unregister(ContentResolver contentResolver) {
            contentResolver.unregisterContentObserver(this);
        }

        @Override
        public abstract void onChange(boolean selfChange, Uri uri);
    }

    private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {

        @Override
        public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
            if (id == LOADER_ID_PRINT_JOBS_LOADER) {
                return new PrintJobsLoader(getActivity());
            }
            return null;
        }

        @Override
        public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
                List<PrintJobInfo> printJobs) {
            if (printJobs == null || printJobs.isEmpty()) {
                getPreferenceScreen().removePreference(mActivePrintJobsCategory);
            } else {
                if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
                    getPreferenceScreen().addPreference(mActivePrintJobsCategory);
                }

                mActivePrintJobsCategory.removeAll();

                final int printJobCount = printJobs.size();
                for (int i = 0; i < printJobCount; i++) {
                    PrintJobInfo printJob = printJobs.get(i);

                    PreferenceScreen preference = getPreferenceManager()
                            .createPreferenceScreen(getActivity());

                    preference.setPersistent(false);
                    preference.setFragment(PrintJobSettingsFragment.class.getName());
                    preference.setKey(printJob.getId().flattenToString());

                    switch (printJob.getState()) {
                        case PrintJobInfo.STATE_QUEUED:
                        case PrintJobInfo.STATE_STARTED: {
                            if (!printJob.isCancelling()) {
                                preference.setTitle(getString(
                                        R.string.print_printing_state_title_template,
                                        printJob.getLabel()));
                            } else {
                                preference.setTitle(getString(
                                        R.string.print_cancelling_state_title_template,
                                        printJob.getLabel()));
                            }
                        } break;

                        case PrintJobInfo.STATE_FAILED: {
                            preference.setTitle(getString(
                                    R.string.print_failed_state_title_template,
                                    printJob.getLabel()));
                        } break;

                        case PrintJobInfo.STATE_BLOCKED: {
                            if (!printJob.isCancelling()) {
                                preference.setTitle(getString(
                                        R.string.print_blocked_state_title_template,
                                        printJob.getLabel()));
                            } else {
                                preference.setTitle(getString(
                                        R.string.print_cancelling_state_title_template,
                                        printJob.getLabel()));
                            }
                        } break;
                    }

                    preference.setSummary(getString(R.string.print_job_summary,
                            printJob.getPrinterName(), DateUtils.formatSameDayTime(
                                    printJob.getCreationTime(), printJob.getCreationTime(),
                                    DateFormat.SHORT, DateFormat.SHORT)));

                    switch (printJob.getState()) {
                        case PrintJobInfo.STATE_QUEUED:
                        case PrintJobInfo.STATE_STARTED: {
                            preference.setIcon(com.android.internal.R.drawable.ic_print);
                        } break;

                        case PrintJobInfo.STATE_FAILED:
                        case PrintJobInfo.STATE_BLOCKED: {
                            preference.setIcon(com.android.internal.R.drawable.ic_print_error);
                        } break;
                    }

                    Bundle extras = preference.getExtras();
                    extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());

                    mActivePrintJobsCategory.addPreference(preference);
                }
            }
        }

        @Override
        public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
            getPreferenceScreen().removePreference(mActivePrintJobsCategory);
        }
    }

    private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {

        private static final String LOG_TAG = "PrintJobsLoader";

        private static final boolean DEBUG = false;

        private List <PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();

        private final PrintManager mPrintManager;

        private PrintJobStateChangeListener mPrintJobStateChangeListener;

        public PrintJobsLoader(Context context) {
            super(context);
            mPrintManager = ((PrintManager) context.getSystemService(
                    Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
                            ActivityManager.getCurrentUser());
        }

        @Override
        public void deliverResult(List<PrintJobInfo> printJobs) {
            if (isStarted()) {
                super.deliverResult(printJobs);
            }
        }

        @Override
        protected void onStartLoading() {
            if (DEBUG) {
                Log.i(LOG_TAG, "onStartLoading()");
            }
            // If we already have a result, deliver it immediately.
            if (!mPrintJobs.isEmpty()) {
                deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
            }
            // Start watching for changes.
            if (mPrintJobStateChangeListener == null) {
                mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
                    @Override
                    public void onPrintJobStateChanged(PrintJobId printJobId) {
                        onForceLoad();
                    }
                };
                mPrintManager.addPrintJobStateChangeListener(
                        mPrintJobStateChangeListener);
            }
            // If the data changed or we have no data - load it now.
            if (mPrintJobs.isEmpty()) {
                onForceLoad();
            }
        }

        @Override
        protected void onStopLoading() {
            if (DEBUG) {
                Log.i(LOG_TAG, "onStopLoading()");
            }
            // Cancel the load in progress if possible.
            onCancelLoad();
        }

        @Override
        protected void onReset() {
            if (DEBUG) {
                Log.i(LOG_TAG, "onReset()");
            }
            // Stop loading.
            onStopLoading();
            // Clear the cached result.
            mPrintJobs.clear();
            // Stop watching for changes.
            if (mPrintJobStateChangeListener != null) {
                mPrintManager.removePrintJobStateChangeListener(
                        mPrintJobStateChangeListener);
                mPrintJobStateChangeListener = null;
            }
        }

        @Override
        public List<PrintJobInfo> loadInBackground() {
            List<PrintJobInfo> printJobInfos = null;
            List<PrintJob> printJobs = mPrintManager.getPrintJobs();
            final int printJobCount = printJobs.size();
            for (int i = 0; i < printJobCount; i++) {
                PrintJobInfo printJob = printJobs.get(i).getInfo();
                if (shouldShowToUser(printJob)) {
                    if (printJobInfos == null) {
                        printJobInfos = new ArrayList<PrintJobInfo>();
                    }
                    printJobInfos.add(printJob);
                }
            }
            return printJobInfos;
        }

        private static boolean shouldShowToUser(PrintJobInfo printJob) {
            switch (printJob.getState()) {
                case PrintJobInfo.STATE_QUEUED:
                case PrintJobInfo.STATE_STARTED:
                case PrintJobInfo.STATE_BLOCKED:
                case PrintJobInfo.STATE_FAILED: {
                    return true;
                }
            }
            return false;
        }
    }
}
