package biz.bokhorst.xprivacy;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;

@SuppressLint("DefaultLocale")
public class ApplicationInfoEx implements Comparable<ApplicationInfoEx> {
	private int mUid;
	private TreeMap<String, ApplicationInfo> mMapAppInfo = null;
	private Map<String, PackageInfo> mMapPkgInfo = new HashMap<String, PackageInfo>();

	// Cache
	private Boolean mInternet = null;
	private Boolean mFrozen = null;
	private long mInstallTime = -1;
	private long mUpdateTime = -1;

	public static final int STATE_ATTENTION = 0;
	public static final int STATE_CHANGED = 1;
	public static final int STATE_SHARED = 2;

	public ApplicationInfoEx(Context context, int uid) {
		mUid = uid;
		mMapAppInfo = new TreeMap<String, ApplicationInfo>();
		PackageManager pm = context.getPackageManager();
		String[] packages = pm.getPackagesForUid(uid);
		if (packages != null)
			for (String packageName : packages)
				try {
					ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
					mMapAppInfo.put(pm.getApplicationLabel(appInfo).toString(), appInfo);
				} catch (NameNotFoundException ignored) {
				}
	}

	public static List<ApplicationInfoEx> getXApplicationList(Context context, ProgressDialog dialog) {
		// Get references
		PackageManager pm = context.getPackageManager();

		// Get app list
		SparseArray<ApplicationInfoEx> mapApp = new SparseArray<ApplicationInfoEx>();
		List<ApplicationInfoEx> listApp = new ArrayList<ApplicationInfoEx>();
		List<ApplicationInfo> listAppInfo = pm.getInstalledApplications(PackageManager.GET_META_DATA);
		if (dialog != null)
			dialog.setMax(listAppInfo.size());
		for (int app = 0; app < listAppInfo.size(); app++) {
			if (dialog != null)
				dialog.setProgress(app + 1);

			ApplicationInfo appInfo = listAppInfo.get(app);
			Util.log(null, Log.INFO, "package=" + appInfo.packageName + " uid=" + appInfo.uid);

			ApplicationInfoEx appInfoEx = new ApplicationInfoEx(context, appInfo.uid);
			if (mapApp.get(appInfoEx.getUid()) == null) {
				mapApp.put(appInfoEx.getUid(), appInfoEx);
				listApp.add(appInfoEx);
			}
		}

		// Sort result
		Collections.sort(listApp);
		return listApp;
	}

	public ArrayList<String> getApplicationName() {
		return new ArrayList<String>(mMapAppInfo.navigableKeySet());
	}

	public String getApplicationName(String packageName) {
		for (Entry<String, ApplicationInfo> entry : mMapAppInfo.entrySet())
			if (entry.getValue().packageName.equals(packageName))
				return entry.getKey();
		return "";
	}

	public List<String> getPackageName() {
		List<String> listPackageName = new ArrayList<String>();
		for (ApplicationInfo appInfo : mMapAppInfo.values())
			listPackageName.add(appInfo.packageName);
		return listPackageName;
	}

	private void getPackageInfo(Context context, String packageName) throws NameNotFoundException {
		PackageManager pm = context.getPackageManager();
		mMapPkgInfo.put(packageName, pm.getPackageInfo(packageName, 0));
	}

	public List<String> getPackageVersionName(Context context) {
		List<String> listVersionName = new ArrayList<String>();
		for (String packageName : this.getPackageName())
			try {
				getPackageInfo(context, packageName);
				String version = mMapPkgInfo.get(packageName).versionName;
				if (version == null)
					listVersionName.add("???");
				else
					listVersionName.add(version);
			} catch (NameNotFoundException ex) {
				listVersionName.add(ex.getMessage());
			}
		return listVersionName;
	}

	public String getPackageVersionName(Context context, String packageName) {
		try {
			getPackageInfo(context, packageName);
			String version = mMapPkgInfo.get(packageName).versionName;
			if (version == null)
				return "???";
			else
				return version;
		} catch (NameNotFoundException ex) {
			return ex.getMessage();
		}
	}

	public List<Integer> getPackageVersionCode(Context context) {
		List<Integer> listVersionCode = new ArrayList<Integer>();
		for (String packageName : this.getPackageName())
			try {
				getPackageInfo(context, packageName);
				listVersionCode.add(mMapPkgInfo.get(packageName).versionCode);
			} catch (NameNotFoundException ex) {
				listVersionCode.add(0);
			}
		return listVersionCode;
	}

	public Drawable getIcon(Context context) {
		// Pick first icon
		if (mMapAppInfo.size() > 0)
			return mMapAppInfo.firstEntry().getValue().loadIcon(context.getPackageManager());
		else
			return new ColorDrawable(Color.TRANSPARENT);
	}

	public Bitmap getIconBitmap(Context context) {
		if (mMapAppInfo.size() > 0) {
			try {
				final ApplicationInfo appInfo = mMapAppInfo.firstEntry().getValue();
				if (appInfo.icon == 0)
					appInfo.icon = android.R.drawable.sym_def_app_icon;
				final Resources resources = context.getPackageManager().getResourcesForApplication(appInfo);

				final BitmapFactory.Options options = new BitmapFactory.Options();
				options.inJustDecodeBounds = true;
				BitmapFactory.decodeResource(resources, appInfo.icon, options);

				final int pixels = Math.round(Util.dipToPixels(context, 48));
				options.inSampleSize = Util.calculateInSampleSize(options, pixels, pixels);
				options.inJustDecodeBounds = false;
				return BitmapFactory.decodeResource(resources, appInfo.icon, options);
			} catch (NameNotFoundException ex) {
				Util.bug(null, ex);
				return null;
			}
		} else
			return null;
	}

	public boolean hasInternet(Context context) {
		if (mInternet == null) {
			mInternet = false;
			PackageManager pm = context.getPackageManager();
			for (ApplicationInfo appInfo : mMapAppInfo.values())
				if (pm.checkPermission("android.permission.INTERNET", appInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
					mInternet = true;
					break;
				}
		}
		return mInternet;
	}

	public boolean isFrozen(Context context) {
		if (mFrozen == null) {
			PackageManager pm = context.getPackageManager();
			boolean enabled = false;
			for (ApplicationInfo appInfo : mMapAppInfo.values())
				try {
					int setting = pm.getApplicationEnabledSetting(appInfo.packageName);
					enabled = (enabled || setting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
					enabled = (enabled || setting == PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
					if (enabled)
						break;
				} catch (IllegalArgumentException ignored) {
				}
			mFrozen = !enabled;
		}
		return mFrozen;
	}

	public int getUid() {
		return mUid;
	}

	@SuppressLint("FieldGetter")
	public int getState(Context context) {
		return Integer.parseInt(PrivacyManager.getSetting(-getUid(), PrivacyManager.cSettingState,
				Integer.toString(STATE_CHANGED)));
	}

	public long getInstallTime(Context context) {
		if (mInstallTime == -1) {
			long now = System.currentTimeMillis();
			mInstallTime = now;
			for (String packageName : this.getPackageName())
				try {
					getPackageInfo(context, packageName);
					long time = mMapPkgInfo.get(packageName).firstInstallTime;
					if (time < mInstallTime)
						mInstallTime = time;
				} catch (NameNotFoundException ex) {
				}
			if (mInstallTime == now)
				// no install time, so assume it is old
				mInstallTime = 0;
		}
		return mInstallTime;
	}

	public long getUpdateTime(Context context) {
		if (mUpdateTime == -1) {
			mUpdateTime = 0;
			for (String packageName : this.getPackageName())
				try {
					getPackageInfo(context, packageName);
					long time = mMapPkgInfo.get(packageName).lastUpdateTime;
					if (time > mUpdateTime)
						mUpdateTime = time;
				} catch (NameNotFoundException ex) {
				}
		}
		return mUpdateTime;
	}

	@SuppressLint("FieldGetter")
	public long getModificationTime(Context context) {
		return Long.parseLong(PrivacyManager.getSetting(-getUid(), PrivacyManager.cSettingModifyTime, "0"));
	}

	public boolean isSystem() {
		boolean mSystem = false;
		for (ApplicationInfo appInfo : mMapAppInfo.values()) {
			mSystem = ((appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0);
			mSystem = mSystem || appInfo.packageName.equals(this.getClass().getPackage().getName());
			mSystem = mSystem || appInfo.packageName.equals(this.getClass().getPackage().getName() + ".pro");
			mSystem = mSystem || appInfo.packageName.equals("de.robv.android.xposed.installer");
		}
		return mSystem;
	}

	public boolean isShared() {
		for (ApplicationInfo appInfo : mMapAppInfo.values())
			if (PrivacyManager.isShared(appInfo.uid))
				return true;
		return false;
	}

	public boolean isIsolated() {
		for (ApplicationInfo appInfo : mMapAppInfo.values())
			if (PrivacyManager.isIsolated(appInfo.uid))
				return true;
		return false;
	}

	@Override
	@SuppressLint("FieldGetter")
	public String toString() {
		return String.format("%d %s", getUid(), TextUtils.join(", ", getApplicationName()));
	}

	@Override
	public int compareTo(ApplicationInfoEx other) {
		// Locale respecting sorter
		Collator collator = Collator.getInstance(Locale.getDefault());
		return collator.compare(TextUtils.join(", ", getApplicationName()),
				TextUtils.join(", ", other.getApplicationName()));
	}
}