/*
 * Copyright (C) 2017 Tobias Brunner
 * HSR Hochschule fuer Technik Rapperswil
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */

package org.strongswan.android.ui;

import android.Manifest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Filter;
import android.widget.ListView;

import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.ui.adapter.SelectedApplicationEntry;
import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;

public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
{
	private SelectedApplicationsAdapter mAdapter;
	private SortedSet<String> mSelection;

	@Override
	public void onActivityCreated(@Nullable Bundle savedInstanceState)
	{
		super.onActivityCreated(savedInstanceState);
		setHasOptionsMenu(true);

		getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

		mAdapter = new SelectedApplicationsAdapter(getActivity());
		setListAdapter(mAdapter);
		setListShown(false);

		ArrayList<String> selection;
		if (savedInstanceState == null)
		{
			selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
		}
		else
		{
			selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
		}
		mSelection = new TreeSet<>(selection);

		LoaderManager.getInstance(this).initLoader(0, null, this);
	}

	@Override
	public void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);
		outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
	}

	/**
	 * Returns the package names of all selected apps
	 */
	public ArrayList<String> getSelectedApplications()
	{
		return new ArrayList<>(mSelection);
	}

	/**
	 * Track check state as ListView is unable to do that when using filters
	 */
	@Override
	public void onListItemClick(ListView l, View v, int position, long id)
	{
		super.onListItemClick(l, v, position, id);
		SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
		item.setSelected(!item.isSelected());
		if (item.isSelected())
		{
			mSelection.add(item.getInfo().packageName);
		}
		else
		{
			mSelection.remove(item.getInfo().packageName);
		}
	}

	/**
	 * Manually set the check state as ListView is unable to track that when using filters
	 */
	private void setCheckState()
	{
		for (int i = 0; i < getListView().getCount(); i++)
		{
			SelectedApplicationEntry item = mAdapter.getItem(i);
			getListView().setItemChecked(i, item.isSelected());
		}
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
	{
		MenuItem item = menu.add(R.string.search);
		item.setIcon(android.R.drawable.ic_menu_search);
		item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);

		SearchView sv = new SearchView(getActivity());
		sv.setOnQueryTextListener(this);
		item.setActionView(sv);

		super.onCreateOptionsMenu(menu, inflater);
	}

	@Override
	public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
	{
		return new InstalledPackagesLoader(getActivity(), mSelection);
	}

	@Override
	public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
	{
		mAdapter.setData(data.first);
		mSelection.removeAll(data.second);
		setCheckState();

		if (isResumed())
		{
			setListShown(true);
		}
		else
		{
			setListShownNoAnimation(true);
		}
	}

	@Override
	public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
	{
		mAdapter.setData(null);
	}

	@Override
	public boolean onQueryTextSubmit(String query)
	{
		return true;
	}

	@Override
	public boolean onQueryTextChange(String newText)
	{
		String search = TextUtils.isEmpty(newText) ? null : newText;
		mAdapter.getFilter().filter(search, new Filter.FilterListener()
		{
			@Override
			public void onFilterComplete(int count)
			{
				setCheckState();
			}
		});
		return true;
	}

	public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
	{
		private final PackageManager mPackageManager;
		private final SortedSet<String> mSelection;
		private Pair<List<SelectedApplicationEntry>, List<String>> mData;

		public InstalledPackagesLoader(Context context, SortedSet<String> selection)
		{
			super(context);
			mPackageManager = context.getPackageManager();
			mSelection = selection;
		}

		@Override
		public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
		{
			List<SelectedApplicationEntry> apps = new ArrayList<>();
			SortedSet<String> seen = new TreeSet<>();
			for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
			{
				/* skip apps that can't access the network anyway */
				if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
				{
					SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
					entry.setSelected(mSelection.contains(info.packageName));
					apps.add(entry);
					seen.add(info.packageName);
				}
			}
			Collections.sort(apps);
			/* check for selected packages that don't exist anymore */
			List<String> missing = new ArrayList<>();
			for (String pkg : mSelection)
			{
				if (!seen.contains(pkg))
				{
					missing.add(pkg);
				}
			}
			return new Pair<>(apps, missing);
		}

		@Override
		protected void onStartLoading()
		{
			if (mData != null)
			{	/* if we have data ready, deliver it directly */
				deliverResult(mData);
			}
			if (takeContentChanged() || mData == null)
			{
				forceLoad();
			}
		}

		@Override
		public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
		{
			if (isReset())
			{
				return;
			}
			mData = data;
			if (isStarted())
			{	/* if it is started we deliver the data directly,
				 * otherwise this is handled in onStartLoading */
				super.deliverResult(data);
			}
		}

		@Override
		protected void onReset()
		{
			mData = null;
			super.onReset();
		}
	}
}
