/*
 * Copyright (C) 2015 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.afwsamples.testdpc.policy.networking;

import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.ListFragment;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.Html;
import android.text.Spanned;
import android.text.format.Formatter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.afwsamples.testdpc.R;
import com.afwsamples.testdpc.common.OnBackPressedHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Fragment for checking network usage of apps using {@link android.app.usage.NetworkStatsManager}
 */
@TargetApi(VERSION_CODES.M)
public class NetworkUsageStatsFragment extends ListFragment implements View.OnClickListener,
        AdapterView.OnItemSelectedListener, OnBackPressedHandler {

    private static final String TAG = "TestDPC.NetworkUsageStatsFragment";

    // See @array/network_stats_queries_array
    private static final int QUERY_DEVICE_POS = 1;
    private static final int QUERY_PROFILE_POS = 2;
    private static final int QUERY_APPSUMMARY_POS = 3;
    private static final int QUERY_APPHISTORY_POS = 4;

    private NetworkStatsManager mNetstatsManager;
    private PackageManager mPackageManager;

    private Spinner mQuerySpinner;
    private TextView mExplanation;
    private Date mStartDate;
    private Date mEndDate;
    private Button mStartDateButton;
    private Button mEndDateButton;
    private TextView mDataUsageSummary;
    private ListView mDataUsageList;
    private List<List<NetworkStats.Bucket>> mListData;
    private ArrayAdapter<List<NetworkStats.Bucket>> mListAdapter;
    private ListView mAppHistoryList;
    private DateFormat mDateStringFormat;
    private DateFormat mHourMinuteDateFormat;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDateStringFormat = new SimpleDateFormat("*dd/MM/yyyy*");
        mHourMinuteDateFormat = new SimpleDateFormat("kk:mm");
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.network_usage_stats, container, false);

        getActivity().getActionBar().setTitle(R.string.data_usage);

        mPackageManager = getActivity().getPackageManager();
        mNetstatsManager = (NetworkStatsManager)getActivity().getSystemService(
                Context.NETWORK_STATS_SERVICE);

        mListData = new ArrayList<>();
        mListAdapter = new ArrayAdapter<List<NetworkStats.Bucket>>(getActivity(),
                R.layout.data_usage_item, android.R.id.title, mListData) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View appView = convertView;
                if (convertView == null) {
                    appView = inflater.inflate(R.layout.data_usage_item, parent, false);
                    final TextView title = (TextView) appView.findViewById(android.R.id.title);
                    final TextView summary = (TextView) appView.findViewById(android.R.id.summary);
                    final TextView state = (TextView) appView.findViewById(R.id.state);
                    ImageView imageView = (ImageView) appView.findViewById(android.R.id.icon);
                    appView.setTag(new ViewHolder(title, summary, state, imageView));
                }
                List<NetworkStats.Bucket> item = getItem(position);
                bindView(appView, item);
                return appView;
            }
        };
        mQuerySpinner = (Spinner) view.findViewById(R.id.query_type_spinner);
        mQuerySpinner.setOnItemSelectedListener(this);
        mExplanation = (TextView) view.findViewById(R.id.explanation);
        mStartDate = getTodayPlus(0, 0);
        mEndDate = getTodayPlus(Calendar.DAY_OF_MONTH, 1);
        mStartDateButton = (Button) view.findViewById(R.id.start_date_button);
        if (mStartDateButton != null) {
            mStartDateButton.setOnClickListener(this);
        }
        mEndDateButton = (Button) view.findViewById(R.id.end_date_button);
        if (mEndDateButton != null) {
            mEndDateButton.setOnClickListener(this);
        }
        updateButtonsText();
        mDataUsageSummary = (TextView) view.findViewById(R.id.data_usage_summary);
        mDataUsageList = (ListView) view.findViewById(android.R.id.list);
        mDataUsageList.setAdapter(mListAdapter);
        mAppHistoryList = (ListView) view.findViewById(R.id.app_history);
        return view;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_date_button: {
                pickDate(mStartDate);
            } break;
            case R.id.end_date_button: {
                pickDate(mEndDate);
            } break;
        }
    }

    @Override
    public boolean onBackPressed() {
        if (mAppHistoryList.getVisibility() == View.VISIBLE) {
            // Go back to previous view in the network usage stats fragment.
            transitionAppHistoryView(View.GONE);
            // Returns true if onBackPressed was executed and we do not need to call the
            // onBackPressed superclass method.
            return true;
        }
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        NetworkStats result = null;
        NetworkStats.Bucket bucket = null;
        mDataUsageSummary.setVisibility(View.GONE);
        mDataUsageList.setVisibility(View.GONE);
        mAppHistoryList.setVisibility(View.GONE);
        mExplanation.setText("");
        try {
            switch (pos) {
                case QUERY_DEVICE_POS: {
                    bucket = mNetstatsManager.querySummaryForDevice(
                            ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
                            mEndDate.getTime());
                    mExplanation.setText(R.string.network_stats_device_summary_explanation);
                    mDataUsageSummary.setText(getString(R.string.network_stats_device_summary,
                            formatSize(bucket.getRxBytes()), bucket.getRxPackets(),
                            formatSize(bucket.getTxBytes()), bucket.getTxPackets()));
                    mDataUsageSummary.setVisibility(View.VISIBLE);
                } break;
                case QUERY_PROFILE_POS: {
                    bucket = mNetstatsManager.querySummaryForUser(
                            ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
                            mEndDate.getTime());
                    mExplanation.setText(R.string.network_stats_profile_summary_explanation);
                    mDataUsageSummary.setText(getString(R.string.network_stats_profile_summary,
                            formatSize(bucket.getRxBytes()), bucket.getRxPackets(),
                            formatSize(bucket.getTxBytes()), bucket.getTxPackets()));
                    mDataUsageSummary.setVisibility(View.VISIBLE);
                } break;
                case QUERY_APPSUMMARY_POS: {
                    result = mNetstatsManager.querySummary(
                            ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
                            mEndDate.getTime());
                    mListData.clear();
                    if (result != null) {
                        while (result.hasNextBucket()) {
                            bucket = new NetworkStats.Bucket();
                            result.getNextBucket(bucket);
                            mListData.add(Arrays.asList(new NetworkStats.Bucket[] { bucket }));
                        }
                    }
                    mListAdapter.notifyDataSetChanged();
                    mDataUsageList.setVisibility(View.VISIBLE);
                } break;
                case QUERY_APPHISTORY_POS: {
                    result = mNetstatsManager.queryDetails(
                            ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
                            mEndDate.getTime());
                    mListData.clear();
                    SparseArray<List<NetworkStats.Bucket>> uidMap = new SparseArray<>();
                    if (result != null) {
                        while (result.hasNextBucket()) {
                            bucket = new NetworkStats.Bucket();
                            result.getNextBucket(bucket);
                            final int uid = bucket.getUid();
                            List<NetworkStats.Bucket> list = uidMap.get(uid);
                            if (list == null) {
                                list = new ArrayList<NetworkStats.Bucket>();
                                mListData.add(list);
                                uidMap.put(uid, list);
                            }
                            list.add(bucket);
                        }
                    }
                    mListAdapter.notifyDataSetChanged();
                    mDataUsageList.setVisibility(View.VISIBLE);
                } break;
            }
        } catch (SecurityException e) {
            showErrorDialog(getString(R.string.network_stats_security_error_msg));
        } catch (RemoteException | NumberFormatException e) {
            showErrorDialog(e.toString());
        } finally {
            if (result != null) {
                result.close();
            }
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // NOP
    }

    @Override
    public void onListItemClick (ListView l, View v, int position, long id) {
        List<NetworkStats.Bucket> item = mListAdapter.getItem(position);
        transitionAppHistoryView(View.GONE);
        if (item != null && item.size() > 1) {
            transitionAppHistoryView(View.VISIBLE);
            ArrayAdapter<NetworkStats.Bucket> adapter = new ArrayAdapter<NetworkStats.Bucket>(
                    getActivity(), android.R.layout.two_line_list_item, android.R.id.title, item) {
                        @Override
                        public View getView(int position, View convertView, ViewGroup parent) {
                            View view = convertView;
                            if (convertView == null) {
                                view = getActivity().getLayoutInflater().inflate(
                                        R.layout.network_usage_app_history_item, parent, false);
                            }
                            NetworkStats.Bucket item = getItem(position);
                            Date startDate = new Date(item.getStartTimeStamp());
                            Date endDate = new Date(item.getEndTimeStamp());
                            TextView text1 = (TextView) view.findViewById(R.id.text1);
                            TextView text2 = (TextView) view.findViewById(R.id.text2);
                            text1.setText(getDateString(startDate, endDate));
                            text2.setText(getString(R.string.network_stats_bucket_usage,
                                    formatSize(item.getRxBytes()), item.getRxPackets(),
                                    formatSize(item.getTxBytes()), item.getTxPackets()));
                            return view;
                        }

                private Spanned getDateString(Date startDate, Date endDate) {
                    String startDateString = mDateStringFormat.format(startDate);
                    String startHourMinuteString = mHourMinuteDateFormat.format(startDate);
                    String endHourMinuteString = mHourMinuteDateFormat.format(endDate);
                    String resultString = "<b>" + startDateString + "</b> " +
                            startHourMinuteString + " - " + endHourMinuteString;
                    return Html.fromHtml(resultString);
                }
            };
            mAppHistoryList.setAdapter(adapter);
        }
    }

    private void showErrorDialog(CharSequence message) {
        new AlertDialog.Builder(getActivity())
                .setTitle(R.string.network_stats_error_title)
                .setIconAttribute(android.R.attr.alertDialogIcon)
                .setMessage(message)
                .setPositiveButton(android.R.string.ok, null)
                .show();
    }

    private void pickDate(final Date target) {
        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(target);
        DatePickerDialog dialog = new DatePickerDialog(getActivity(),
                new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int monthOfYear,
                            int dayOfMonth) {
                        calendar.set(Calendar.YEAR, year);
                        calendar.set(Calendar.MONTH, monthOfYear);
                        calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
                        target.setTime(calendar.getTimeInMillis());
                        updateButtonsText();
                    }
                }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
                calendar.get(Calendar.DAY_OF_MONTH));
        dialog.show();
    }

    private void updateButtonsText() {
        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
        if (mStartDateButton != null) {
            mStartDateButton.setText(dateFormat.format(mStartDate));
        }
        if (mEndDateButton != null) {
            mEndDateButton.setText(dateFormat.format(mEndDate));
        }
    }

    private Date getTodayPlus(int calendarField, int valueToAdd) {
        final Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        if (valueToAdd > 0) {
            calendar.add(calendarField, valueToAdd);
        }
        return calendar.getTime();
    }

    private void bindView(View appView, List<NetworkStats.Bucket> item) {
        final ViewHolder viewHolder = (ViewHolder) appView.getTag();
        final TextView title = viewHolder.title;
        final TextView summary = viewHolder.summary;
        final TextView state = viewHolder.state;
        final ImageView imageView = viewHolder.image;
        final Resources res = getResources();
        Drawable icon = res.getDrawable(android.R.drawable.ic_dialog_info, null);
        NetworkStats.Bucket bucket = item.get(0);
        final int uid = bucket.getUid();
        switch (uid) {
            case NetworkStats.Bucket.UID_REMOVED: {
                title.setText(R.string.network_stats_uid_removed);
            } break;
            case NetworkStats.Bucket.UID_TETHERING: {
                title.setText(R.string.network_stats_uid_tethering);
            } break;
            case android.os.Process.SYSTEM_UID: {
                title.setText(R.string.network_stats_uid_system);
            } break;
            default: {
                title.setText(getString(R.string.network_stats_uid, uid));
                icon = mPackageManager.getDefaultActivityIcon();
                final String[] packageNames = mPackageManager.getPackagesForUid(uid);
                final int length = packageNames != null ? packageNames.length : 0;
                try {
                    if (length == 1) {
                        final String pkgName = packageNames[0];
                        final ApplicationInfo info = mPackageManager.getApplicationInfo(pkgName,
                                0 /* no flags */);
                        if (info != null) {
                            title.setText(info.loadLabel(mPackageManager));
                            icon = info.loadIcon(mPackageManager);
                        }
                    } else {
                        for (int i = 0; i < length; i++) {
                            final String packageName = packageNames[i];
                            final PackageInfo packageInfo = mPackageManager.getPackageInfo(
                                    packageName, 0 /* no flags */);
                            final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
                                    packageName, 0 /* no flags */);

                            if (appInfo != null && packageInfo != null) {
                                if (packageInfo.sharedUserLabel != 0) {
                                    title.setText(mPackageManager.getText(packageName,
                                            packageInfo.sharedUserLabel,
                                            packageInfo.applicationInfo));
                                    icon = appInfo.loadIcon(mPackageManager);
                                }
                            }
                        }
                    }
                } catch (PackageManager.NameNotFoundException e) {
                    // keep the default activity icon
                }
            }
        }
        imageView.setImageDrawable(icon);
        final int bucketsCount = item.size();
        if (bucketsCount == 1) {
            summary.setText(formatSize(bucket.getRxBytes() + bucket.getTxBytes()));
            switch (bucket.getState()) {
                case NetworkStats.Bucket.STATE_FOREGROUND: {
                    state.setText(R.string.network_stats_foreground_state);
                } break;
                case NetworkStats.Bucket.STATE_DEFAULT: {
                    state.setText(R.string.network_stats_default_state);
                } break;
                case NetworkStats.Bucket.STATE_ALL: {
                    state.setText(R.string.network_stats_combined_state);
                } break;
            }
        } else {
            summary.setText(getString(R.string.network_stats_items, bucketsCount));
            state.setText(R.string.network_stats_combined_state);
        }
    }

    private void transitionAppHistoryView(int appHistoryVisibility) {
        mAppHistoryList.setVisibility(appHistoryVisibility);
        mDataUsageList.setVisibility(
                appHistoryVisibility == View.VISIBLE ? View.GONE : View.VISIBLE);
    }

    private String formatSize(long sizeBytes) {
        return Formatter.formatFileSize(getActivity(), sizeBytes);
    }

    private static class ViewHolder {
        public final TextView title;
        public final TextView summary;
        public final TextView state;
        public final ImageView image;
        public ViewHolder(final TextView title, final TextView summary, final TextView state,
                final ImageView image) {
            this.title = title;
            this.summary = summary;
            this.state = state;
            this.image = image;
        }
    }
}