// Copyright 2016 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.preferences.privacy;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.favicon.LargeIconBridge;
import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.widget.RoundedIconGenerator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Modal dialog that shows a list of important domains to the user which they can uncheck. Used to
 * allow the user to exclude domains from being cleared by the clear browsing data function.
 * We use proper bundle construction (through the {@link #newInstance(String[], int[], String[])}
 * method) and onActivityResult return conventions.
 */
public class ConfirmImportantSitesDialogFragment extends DialogFragment {
    private class ClearBrowsingDataAdapter extends ArrayAdapter<String>
            implements AdapterView.OnItemClickListener {
        private final String[] mDomains;
        private final String[] mFaviconURLs;
        private final int mCornerRadius;
        private final int mFaviconSize;
        private RoundedIconGenerator mIconGenerator;

        private ClearBrowsingDataAdapter(
                String[] domains, String[] faviconURLs, Resources resources) {
            super(getActivity(), R.layout.confirm_important_sites_list_row, domains);
            mDomains = domains;
            mFaviconURLs = faviconURLs;
            mFaviconSize = resources.getDimensionPixelSize(R.dimen.default_favicon_size);
            mCornerRadius = resources.getDimensionPixelSize(R.dimen.default_favicon_corner_radius);
            int textSize = resources.getDimensionPixelSize(R.dimen.default_favicon_icon_text_size);
            int iconColor = ApiCompatibilityUtils.getColor(
                    resources, R.color.default_favicon_background_color);
            mIconGenerator = new RoundedIconGenerator(
                    mFaviconSize, mFaviconSize, mCornerRadius, iconColor, textSize);
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View childView = convertView;
            if (childView == null) {
                LayoutInflater inflater = LayoutInflater.from(getActivity());
                childView =
                        inflater.inflate(R.layout.confirm_important_sites_list_row, parent, false);

                ViewAndFaviconHolder viewHolder = new ViewAndFaviconHolder();
                viewHolder.checkboxView = (CheckBox) childView.findViewById(R.id.icon_row_checkbox);
                viewHolder.imageView = (ImageView) childView.findViewById(R.id.icon_row_image);
                childView.setTag(viewHolder);
            }
            ViewAndFaviconHolder viewHolder = (ViewAndFaviconHolder) childView.getTag();
            configureChildView(position, viewHolder);
            return childView;
        }

        private void configureChildView(int position, ViewAndFaviconHolder viewHolder) {
            String domain = mDomains[position];
            viewHolder.checkboxView.setChecked(mCheckedState.get(domain));
            viewHolder.checkboxView.setText(domain);
            loadFavicon(viewHolder, mFaviconURLs[position]);
        }

        /**
         * Called when a list item is clicked. We toggle the checkbox and update our selected
         * domains list.
         */
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String domain = mDomains[position];
            ViewAndFaviconHolder viewHolder = (ViewAndFaviconHolder) view.getTag();
            boolean isChecked = mCheckedState.get(domain);
            mCheckedState.put(domain, !isChecked);
            viewHolder.checkboxView.setChecked(!isChecked);
        }

        private void loadFavicon(final ViewAndFaviconHolder viewHolder, final String url) {
            viewHolder.imageCallback = new LargeIconCallback() {
                @Override
                public void onLargeIconAvailable(
                        Bitmap icon, int fallbackColor, boolean isFallbackColorDefault) {
                    if (this != viewHolder.imageCallback) return;
                    Drawable image = getFaviconDrawable(icon, fallbackColor, url);
                    viewHolder.imageView.setImageDrawable(image);
                }
            };
            mLargeIconBridge.getLargeIconForUrl(url, mFaviconSize, viewHolder.imageCallback);
        }

        private Drawable getFaviconDrawable(Bitmap icon, int fallbackColor, String url) {
            if (icon == null) {
                mIconGenerator.setBackgroundColor(fallbackColor);
                icon = mIconGenerator.generateIconForUrl(url);
                return new BitmapDrawable(getResources(), icon);
            } else {
                RoundedBitmapDrawable roundedIcon =
                        RoundedBitmapDrawableFactory.create(getResources(),
                                Bitmap.createScaledBitmap(icon, mFaviconSize, mFaviconSize, false));
                roundedIcon.setCornerRadius(mCornerRadius);
                return roundedIcon;
            }
        }
    }

    /**
     * ViewHolder class optimizes looking up table row fields. findViewById is only called once
     * per row view initialization, and the references are cached here. Also stores a reference to
     * the favicon image callback so that we can make sure we load the correct favicon.
     */
    private static class ViewAndFaviconHolder {
        public CheckBox checkboxView;
        public ImageView imageView;
        public LargeIconCallback imageCallback;
    }

    /**
     * Constructs a new instance of the important sites dialog fragment.
     * @param importantDomains The list of important domains to display.
     * @param importantDomainReasons The reasons for choosing each important domain.
     * @param faviconURLs The list of favicon urls that correspond to each importantDomains.
     * @return An instance of ConfirmImportantSitesDialogFragment with the bundle arguments set.
     */
    public static ConfirmImportantSitesDialogFragment newInstance(
            String[] importantDomains, int[] importantDomainReasons, String[] faviconURLs) {
        ConfirmImportantSitesDialogFragment dialogFragment =
                new ConfirmImportantSitesDialogFragment();
        Bundle bundle = new Bundle();
        bundle.putStringArray(IMPORTANT_DOMAINS_TAG, importantDomains);
        bundle.putIntArray(IMPORTANT_DOMAIN_REASONS_TAG, importantDomainReasons);
        bundle.putStringArray(FAVICON_URLS_TAG, faviconURLs);
        dialogFragment.setArguments(bundle);
        return dialogFragment;
    }

    private static final int FAVICON_MAX_CACHE_SIZE_BYTES = 100 * 1024; // 100KB

    /** The tag used when showing the clear browsing fragment. */
    public static final String FRAGMENT_TAG = "ConfirmImportantSitesDialogFragment";

    /** The tag for the string array of deselected domains. These are meant to NOT be cleared. */
    public static final String DESELECTED_DOMAINS_TAG = "DeselectedDomains";
    /** The tag for the int array of reasons the deselected domains were important. */
    public static final String DESELECTED_DOMAIN_REASONS_TAG = "DeselectedDomainReasons";
    /** The tag for the string array of ignored domains, which whill be cleared. */
    public static final String IGNORED_DOMAINS_TAG = "IgnoredDomains";
    /** The tag for the int array of reasons the ignored domains were important. */
    public static final String IGNORED_DOMAIN_REASONS_TAG = "IgnoredDomainReasons";

    /** The tag used for logging. */
    public static final String TAG = "ConfirmImportantSitesDialogFragment";

    /** The tag used to store the important domains in the bundle. */
    private static final String IMPORTANT_DOMAINS_TAG = "ImportantDomains";
    /** The tag used to store the important domain reasons in the bundle. */
    private static final String IMPORTANT_DOMAIN_REASONS_TAG = "ImportantDomainReasons";

    /** The tag used to store the favicon urls corresponding to each important domain. */
    private static final String FAVICON_URLS_TAG = "FaviconURLs";

    /** Array of important registerable domains we're showing to the user. */
    private String[] mImportantDomains;
    /** Map of the reasons the above important domains were chosen. */
    private Map<String, Integer> mImportantDomainsReasons;
    /** Array of favicon urls to use for each important domain above. */
    private String[] mFaviconURLs;
    /** The map of domains to the checked state, where true is checked. */
    private Map<String, Boolean> mCheckedState;
    /** The alert dialog shown to the user. */
    private AlertDialog mDialog;
    /** Our adapter that we use with the list view in the dialog. */
    private ClearBrowsingDataAdapter mAdapter;

    private LargeIconBridge mLargeIconBridge;

    private Profile mProfile;

    /** We store the custom list view for testing */
    private ListView mSitesListView;

    public ConfirmImportantSitesDialogFragment() {
        mImportantDomainsReasons = new HashMap<>();
        mCheckedState = new HashMap<>();
    }

    @Override
    public void setArguments(Bundle args) {
        super.setArguments(args);
        mImportantDomains = args.getStringArray(IMPORTANT_DOMAINS_TAG);
        mFaviconURLs = args.getStringArray(FAVICON_URLS_TAG);
        int[] importantDomainReasons = args.getIntArray(IMPORTANT_DOMAIN_REASONS_TAG);
        for (int i = 0; i < mImportantDomains.length; ++i) {
            mImportantDomainsReasons.put(mImportantDomains[i], importantDomainReasons[i]);
            mCheckedState.put(mImportantDomains[i], true);
        }
    }

    @VisibleForTesting
    public Set<String> getDeselectedDomains() {
        HashSet<String> deselected = new HashSet<>();
        for (Entry<String, Boolean> entry : mCheckedState.entrySet()) {
            if (!entry.getValue()) deselected.add(entry.getKey());
        }
        return deselected;
    }

    @VisibleForTesting
    public ListView getSitesList() {
        return mSitesListView;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (mLargeIconBridge != null) {
            mLargeIconBridge.destroy();
        }
    }

    private int[] toIntArray(List<Integer> boxedList) {
        int[] result = new int[boxedList.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = boxedList.get(i);
        }
        return result;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // We check the domains and urls as well due to crbug.com/622879.
        if (savedInstanceState != null) {
            // The important domains and favicon URLs aren't currently saved, so if this dialog
            // is recreated from a saved instance they will be null. This method must return a
            // valid dialog, so these two array's are initialized, then the dialog is dismissed.
            // TODO(dmurph): save mImportantDomains and mFaviconURLs so that they can be restored
            // from a savedInstanceState and the dialog can be properly recreated rather than
            // dismissed.
            mImportantDomains = new String[0];
            mFaviconURLs = new String[0];
            dismiss();
        }
        mProfile = Profile.getLastUsedProfile().getOriginalProfile();
        mLargeIconBridge = new LargeIconBridge(mProfile);
        ActivityManager activityManager =
                ((ActivityManager) ContextUtils.getApplicationContext().getSystemService(
                        Context.ACTIVITY_SERVICE));
        int maxSize = Math.min(
                activityManager.getMemoryClass() / 16 * 25 * 1024, FAVICON_MAX_CACHE_SIZE_BYTES);
        mLargeIconBridge.createCache(maxSize);

        mAdapter = new ClearBrowsingDataAdapter(mImportantDomains, mFaviconURLs, getResources());
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == AlertDialog.BUTTON_POSITIVE) {
                    Intent data = new Intent();
                    List<String> deselectedDomains = new ArrayList<>();
                    List<Integer> deselectedDomainReasons = new ArrayList<>();
                    List<String> ignoredDomains = new ArrayList<>();
                    List<Integer> ignoredDomainReasons = new ArrayList<>();
                    for (Entry<String, Boolean> entry : mCheckedState.entrySet()) {
                        Integer reason = mImportantDomainsReasons.get(entry.getKey());
                        if (entry.getValue()) {
                            ignoredDomains.add(entry.getKey());
                            ignoredDomainReasons.add(reason);
                        } else {
                            deselectedDomains.add(entry.getKey());
                            deselectedDomainReasons.add(reason);
                        }
                    }
                    data.putExtra(DESELECTED_DOMAINS_TAG, deselectedDomains.toArray(new String[0]));
                    data.putExtra(
                            DESELECTED_DOMAIN_REASONS_TAG, toIntArray(deselectedDomainReasons));
                    data.putExtra(IGNORED_DOMAINS_TAG, ignoredDomains.toArray(new String[0]));
                    data.putExtra(IGNORED_DOMAIN_REASONS_TAG, toIntArray(ignoredDomainReasons));
                    getTargetFragment().onActivityResult(
                            getTargetRequestCode(), Activity.RESULT_OK, data);
                } else {
                    getTargetFragment().onActivityResult(getTargetRequestCode(),
                            Activity.RESULT_CANCELED, getActivity().getIntent());
                }
            }
        };
        // We create our own ListView, as AlertDialog doesn't let us set a message and a list
        // adapter at the same time.
        View messageAndListView = getActivity().getLayoutInflater().inflate(
                R.layout.clear_browsing_important_dialog_listview, null);
        mSitesListView = (ListView) messageAndListView.findViewById(R.id.select_dialog_listview);
        mSitesListView.setAdapter(mAdapter);
        mSitesListView.setOnItemClickListener(mAdapter);
        final AlertDialog.Builder builder =
                new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme)
                        .setTitle(R.string.storage_clear_site_storage_title)
                        .setPositiveButton(R.string.clear_browsing_data_important_dialog_button,
                                listener)
                        .setNegativeButton(R.string.cancel, listener)
                        .setView(messageAndListView);
        mDialog = builder.create();

        return mDialog;
    }
}