package com.kurukurupapa.tryandroid.fw.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kurukurupapa.tryandroid.fw.DbHelper;
import com.kurukurupapa.tryandroid.fw.FwException;
import com.kurukurupapa.tryandroid.fw.OnClickListenerAdapter;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.preference.PreferenceActivity;
import android.view.View;
import android.widget.Button;
import android.widget.SimpleAdapter;

public class ActivityUtil {

	public static View findViewByIdName(Activity activity, Class<?> rIdClass,
			String idName) {
		// 対応するリソースIDを取得する
		Integer id = RUtil.getResourceIdOrNull(rIdClass, idName);
		if (id == null) {
			return null;
		}
		// Viewを取得する
		return activity.findViewById(id);
	}

	/**
	 * Viewのフィールド名からリソースID名を作成
	 *
	 * @param viewField
	 * @return
	 */
	private static String getIdName(Field viewField) {
		return StringUtil.toUnderScore(viewField.getName());
	}

	/**
	 * Activityに対応するレイアウト名を取得
	 *
	 * @param activity
	 * @return
	 */
	private static String getLayoutName(Activity activity) {
		return StringUtil.toUnderScore(activity.getClass().getSimpleName());
	}

	/**
	 * フィールド名からOnClickイベント用メソッド名を作成
	 *
	 * @param field
	 * @return
	 */
	private static String getOnClickMethodName(Field field) {
		return field.getName() + "_OnClick";
	}

	private static String getPreferenceXmlName(PreferenceActivity activity) {
		return StringUtil.toUnderScore(activity.getClass().getSimpleName());
	}

	/**
	 * <p>
	 * Activityオブジェクトの初期化
	 * </p>
	 * <p>
	 * レイアウトXMLからUIを設定する。 レイアウトXMLの名前は、Activityクラスの名前をアンダースコア表記にした文字列。
	 * （例："XxxActivity"クラスの場合、"xxx_activity"）
	 * 内部では、Activity#setContentViewメソッドを使用している。
	 * </p>
	 * <p>
	 * Viewクラスのフィールドには、Viewオブジェクトを紐付ける。
	 * フィールド名をアンダースコア表記にした文字列と、レイアウトXMLに定義されたリソース名とを対応させる。
	 * （例："xxxActivityEditText"フィールドの場合、"xxx_activity_edit_text"リソース。）
	 * </p>
	 * <p>
	 * Buttonクラスのフィールドでは、ボタンクリック時に、Activityのメソッドが呼ばれるように設定する。
	 * 呼び出すActivityのメソッド名は、フィールド名＋"_OnClick"。
	 * （例："startButton"フィールドの場合、"startButton_OnClick"メソッド。）
	 * </p>
	 * <p>
	 * DatabaseHelperクラスのフィールドでは、DatabaseHelperオブジェクトを設定する。
	 * </p>
	 *
	 * @param activity
	 */
	public static void initializeActivity(Activity activity) {
		// レイアウト設定
		Class<?> rClass = RUtil.getRClass(activity);
		String layoutName = ActivityUtil.getLayoutName(activity);
		activity.setContentView(RUtil.getResourceId(
				RUtil.getRLayoutClass(rClass), layoutName));

		for (Field field : activity.getClass().getDeclaredFields()) {

			// フィールドがViewクラスの場合、インスタンスを紐付ける。
			if (View.class.isAssignableFrom(field.getType())) {
				setViewField(activity, field, rClass);
			}

			// Buttonクラスの場合、OnClickListenerを設定する。
			if (Button.class.isAssignableFrom(field.getType())) {
				setOnClickListener(activity, field);
			}

			// DatabaseHelperクラスの場合、DatabaseHelperインスタンスを設定する。
			if (DbHelper.class.isAssignableFrom(field.getType())) {
				setDatabaseHelper(activity, field);
			}

		}
	}

	public static void initializeActivity(PreferenceActivity activity) {
		// 設定画面の項目設定
		Class<?> rClass = RUtil.getRClass(activity);
		String xmlName = ActivityUtil.getPreferenceXmlName(activity);
		activity.addPreferencesFromResource(RUtil.getResourceId(
				RUtil.getRXmlClass(rClass), xmlName));
	}

	private static void setDatabaseHelper(Activity activity, Field field) {
		ReflectionUtil.setFieldValue(activity, field, new DbHelper(activity));
	}

	private static void setOnClickListener(Activity activity, Field field) {
		// Activityに定義された、Buttonクラスなどのオブジェクトを取得
		Object obj = ReflectionUtil.getFieldValue(activity, field);
		if (obj == null) {
			return;
		}

		// 取得したオブジェクトに、OnClickListenerAdapterを設定
		// ActivityのButtonフィールド名+"_OnClick"メソッドを呼び出すようになる
		String methodName = getOnClickMethodName(field);
		if (ReflectionUtil.existMethod(activity.getClass(), methodName,
				View.class)) {
			ReflectionUtil.invokeMethod(obj, "setOnClickListener",
					new OnClickListenerAdapter(activity, methodName),
					View.OnClickListener.class);
			LogUtil.v("ButtonのOnClickListenerを設定しました。onClickメソッド="
					+ activity.getClass().getName() + "#" + methodName);
			return;
		} else {
			LogUtil.v("ButtonのonClickメソッド設定処理をスキップしました。"
					+ "見つからなかったonClickメソッド=" + activity.getClass().getName()
					+ "#" + methodName);
		}

		// 取得したオブジェクトに、OnClickListenerを設定
		// Activityの"onClick"メソッドを呼び出すようになる
		if (View.OnClickListener.class.isAssignableFrom(activity.getClass())) {
			ReflectionUtil.invokeMethod(obj, "setOnClickListener", activity,
					View.OnClickListener.class);
			LogUtil.v("ButtonのOnClickListenerを設定しました。onClickメソッド="
					+ activity.getClass().getName() + "#onClick(View)");
			return;
		} else {
			LogUtil.v("ButtonのOnClickListener設定処理をスキップしました。"
					+ "見つからなかったonClickメソッド=" + activity.getClass().getName()
					+ "#onClick(View)");
		}
	}

	private static void setViewField(Activity activity, Field viewField,
			Class<?> rClass) {
		// 対応するViewを取得する。
		String idName = getIdName(viewField);
		View view = findViewByIdName(activity, RUtil.getRIdClass(rClass),
				idName);
		if (view == null) {
			LogUtil.w("The view was not found. activity="
					+ activity.getClass().getName() + ", field="
					+ viewField.getName() + ", idName=" + idName + ", rClass="
					+ rClass.getName());
			return;
		}

		// フィールドにViewを設定
		ReflectionUtil.setFieldValue(activity, viewField, view);
	}

	/**
	 * ノーティファケーション作成
	 *
	 * @param context
	 * @param icon
	 *            通知ウインドウに表示するアイコン。
	 * @param title
	 *            通知ウインドウに表示するタイトル。
	 * @param text
	 *            通知ウインドウに表示する概要。
	 * @param flags
	 *            通知ウインドウ表示時のオプション設定。複数オプション設定する場合は、OR演算子"|"でつなげる。<BR>
	 *            オプションの値は、Notification.FLAG_ONGOING_EVENT（"実行中"グループに配置）、
	 *            Notification.FLAG_NO_CLEAR（"Clear notifications"
	 *            ボタンでクリアさせない）など。
	 * @param activityClass
	 *            通知の展開メッセージ選択時に表示するアクティビティ
	 * @return
	 */
	public static Notification createNotification(Context context, int icon,
			CharSequence title, CharSequence text, int flags,
			Class<? extends Activity> activityClass) {

		// 通知の展開メッセージ
		PendingIntent contentIntent = createPendingIntent(context,
				activityClass, 0, 0);

		// ステータスバー通知の設定
		Notification notification = new Notification(icon, text,
				System.currentTimeMillis());
		// 通知ウインドウの設定
		notification.setLatestEventInfo(context, title, text, contentIntent);
		// 通知ウインドウ表示時のオプション設定
		notification.flags |= flags;

		return notification;
	}

	public static PendingIntent createPendingIntent(Context context,
			Class<?> cls, int requestCode, int flags) {
		Intent intent = new Intent(context, cls);
		PendingIntent pendingIntent = PendingIntent.getActivity(context,
				requestCode, intent, flags);
		return pendingIntent;
	}

	/**
	 * ノーティファケーション通知
	 *
	 * @param context
	 * @param notificationId
	 * @param notification
	 */
	public static void notifyNotification(Context context, int notificationId,
			Notification notification) {
		getNotificationManager(context).notify(notificationId, notification);
	}

	/**
	 * ノーティフィケーションの登録
	 *
	 * @param context
	 * @param notificationId
	 *            ノーティフィケーションを識別するID。キャンセル時にも使用する。
	 * @param icon
	 *            通知ウインドウに表示するアイコン。
	 * @param title
	 *            通知ウインドウに表示するタイトル。
	 * @param description
	 *            通知ウインドウに表示する概要。
	 * @param flags
	 *            通知ウインドウ表示時のオプション設定。複数オプション設定する場合は、OR演算子"|"でつなげる。<BR>
	 *            オプションの値は、Notification.FLAG_ONGOING_EVENT（"実行中"グループに配置）、
	 *            Notification.FLAG_NO_CLEAR（"Clear notifications"
	 *            ボタンでクリアさせない）など。
	 * @param activityClass
	 *            通知ウインドウをクリックしたときに表示するアクティビティのクラス
	 */
	public static void notifyNotification(Context context, int notificationId,
			int icon, CharSequence title, CharSequence description, int flags,
			Class<? extends Activity> activityClass) {
		Notification notification = createNotification(context, icon, title,
				description, flags, activityClass);
		notifyNotification(context, notificationId, notification);
	}

	/**
	 * ノーティフィケーションのキャンセル
	 *
	 * @param context
	 * @param notificationId
	 */
	public static void cancelNotification(Context context, int notificationId) {
		getNotificationManager(context).cancel(notificationId);
	}

	private static NotificationManager getNotificationManager(Context context) {
		return (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
	}

	public static void setAlarm(Context context,
			Class<? extends Service> serviceClass, String action,
			long intervalMillisec) {
		// インテント作成
		PendingIntent pendingIntent = createPendingIntent(context,
				serviceClass, action);

		// 次回タイマー設定
		getAlarmManager(context)
				.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
						SystemClock.elapsedRealtime() + intervalMillisec,
						pendingIntent);
	}

	public static void cancelAlarm(Context context,
			Class<? extends Service> serviceClass, String action) {
		// インテント作成
		PendingIntent pendingIntent = createPendingIntent(context,
				serviceClass, action);

		// 既に登録済みのタイマーをキャンセルする
		getAlarmManager(context).cancel(pendingIntent);
	}

	private static PendingIntent createPendingIntent(Context context,
			Class<? extends Service> serviceClass, String action) {
		// インテントを作成
		Intent nextIntent = new Intent();
		nextIntent.setClass(context, serviceClass);
		// nextIntent.setClassName(serviceClass.getPackage().getName(),
		// serviceClass.getName());
		nextIntent.setAction(action);

		// 次回インテントを呼び出すためのインテントを作成
		PendingIntent pendingIntent = PendingIntent.getService(context, 0,
				nextIntent, 0);
		return pendingIntent;
	}

	private static AlarmManager getAlarmManager(Context context) {
		return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
	}

	public static void showAboutDialog(Context context, CharSequence appName,
			CharSequence appVersion) {
		showAboutDialog(context, appName.toString(), appVersion.toString());
	}

	public static void showAboutDialog(Context context, String appName,
			String appVersion) {
		String message = appName + "\nVersion " + appVersion;
		new AlertDialog.Builder(context)
				.setTitle("アプリについて")
				.setMessage(message)
				.setPositiveButton(android.R.string.ok,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								// 何もしないでダイアログを閉じる
							}
						}).create().show();
	}

	public static void showDialog(Context context, CharSequence title,
			CharSequence message) {
		new AlertDialog.Builder(context)
				.setTitle(title)
				.setMessage(message)
				.setPositiveButton(android.R.string.ok,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								// 何もしないでダイアログを閉じる
							}
						}).create().show();
	}

	public static void showDialogWithOkCancel(Context context, String title,
			String message, DialogInterface.OnClickListener okListener,
			DialogInterface.OnClickListener cancelListener) {
		new AlertDialog.Builder(context).setTitle(title).setMessage(message)
				.setPositiveButton(android.R.string.ok, okListener)
				.setNegativeButton(android.R.string.cancel, cancelListener)
				.create().show();
	}

	public static void showDialogWithItems(Context context, String title,
			String[] items, DialogInterface.OnClickListener okListener) {
		new AlertDialog.Builder(context).setTitle(title)
				.setItems(items, okListener)
				.setNegativeButton(android.R.string.cancel, null).create()
				.show();
	}

	public static void showDialogWithMultiChoiceItems(Context context,
			String title, String[] items, boolean[] checkedItems,
			DialogInterface.OnClickListener okListener) {
		showDialogWithMultiChoiceItems(context, title, items, checkedItems,
				okListener, null);
	}

	public static void showDialogWithMultiChoiceItems(Context context,
			String title, String[] items, boolean[] checkedItems,
			DialogInterface.OnClickListener okListener,
			DialogInterface.OnClickListener cancelListener) {
		final boolean[] tmp = checkedItems;

		DialogInterface.OnMultiChoiceClickListener choiceListener = new DialogInterface.OnMultiChoiceClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which,
					boolean isChecked) {
				tmp[which] = isChecked;
			}
		};

		new AlertDialog.Builder(context).setTitle(title)
				.setMultiChoiceItems(items, checkedItems, choiceListener)
				.setPositiveButton(android.R.string.ok, okListener)
				.setNegativeButton(android.R.string.cancel, cancelListener)
				.create().show();
	}

	public static void startService(Context context,
			Class<? extends Service> serviceClass, String action) {
		// インテント作成
		Intent intent = new Intent(context, serviceClass);
		intent.setAction(action);

		// サービス呼び出し
		context.startService(intent);
	}

	public static SimpleAdapter createSimpleListItem1Adapter(Activity activity,
			List<String> data) {
		return new SimpleAdapter(activity, conv(data),
				android.R.layout.simple_list_item_1, new String[] { "text1" },
				new int[] { android.R.id.text1 });
	}

	private static List<Map<String, String>> conv(List<String> textList) {
		List<Map<String, String>> data = new ArrayList<Map<String, String>>();
		for (String e : textList) {
			Map<String, String> m = new HashMap<String, String>();
			m.put("text1", e);
			data.add(m);
		}
		return data;
	}

	/**
	 * ListAdapterオブジェクト生成<br>
	 * 大きなフォントで1行
	 *
	 * @param activity
	 * @param list
	 * @param textKey
	 * @return
	 */
	public static SimpleAdapter createSimpleListItem1Adapter(Activity activity,
			List<? extends Map<String, Object>> list, String textKey) {
		return new SimpleAdapter(activity, list,
				android.R.layout.simple_list_item_1, new String[] { textKey },
				new int[] { android.R.id.text1 });
	}

	/**
	 * ListAdapterオブジェクト生成<br>
	 * 1行が大きなフォント、2行目が小さなフォント
	 *
	 * @param activity
	 * @param data
	 * @param key1
	 * @param key2
	 * @return
	 */
	public static SimpleAdapter createSimpleListItem2Adapter(Activity activity,
			List<? extends Map<String, String>> data, String key1, String key2) {
		return new SimpleAdapter(activity, data,
				android.R.layout.simple_list_item_2,
				new String[] { key1, key2 }, new int[] { android.R.id.text1,
						android.R.id.text2 });
	}

	/**
	 * ListAdapterオブジェクト生成<br>
	 * 小さなフォントで2行
	 *
	 * @param activity
	 * @param data
	 * @param key1
	 * @param key2
	 * @return
	 */
	public static SimpleAdapter createTwoLineListItemAdapter(Activity activity,
			List<? extends Map<String, String>> data, String key1, String key2) {
		return new SimpleAdapter(activity, data,
				android.R.layout.two_line_list_item,
				new String[] { key1, key2 }, new int[] { android.R.id.text1,
						android.R.id.text2 });
	}

	/**
	 * ListAdapterオブジェクト生成<br>
	 * 小さなフォントで1行
	 *
	 * @param activity
	 * @param list
	 * @param textKey
	 * @return
	 */
	public static SimpleAdapter createTestListItemAdapter(Activity activity,
			List<? extends Map<String, Object>> list, String textKey) {
		return new SimpleAdapter(activity, list,
				android.R.layout.test_list_item, new String[] { textKey },
				new int[] { android.R.id.text1 });
	}

	public static void startActivityForResult(Activity activity,
			Class<? extends Activity> cls, Bundle extras, int requestCode) {
		Intent intent = new Intent(activity, cls);
		if (extras != null) {
			intent.putExtras(extras);
		}
		activity.startActivityForResult(intent, requestCode);
	}

	public static void startActivityForResult(Activity activity,
			Class<? extends Activity> cls, int requestCode) {
		startActivityForResult(activity, cls, null, requestCode);
	}

	public static void startActivity(Context context, PackageManager pm,
			String packageName) {
		try {
			Intent intent = pm.getLaunchIntentForPackage(packageName);
			context.startActivity(intent);
		} catch (ActivityNotFoundException e) {
			throw new FwException("アプリが見つかりません。packageName=" + packageName, e);
		} catch (Exception e) {
			throw new FwException("起動失敗。packageName=" + packageName, e);
		}
	}

	/**
	 * メールアプリを起動してメール送信
	 *
	 * @param context
	 * @param toArr
	 * @param subject
	 * @param text
	 * @param file
	 *            複数ファイルを添付したいときはZIPファイルに圧縮アーカイブして指定する。
	 * @param type
	 *            添付ファイルのタイプ。"text/plain", "image/jpeg"など。
	 */
	public static void startActivityForMailSend(Context context,
			String[] toArr, String subject, String text, File file, String type) {
		Intent intent = new Intent();
		if (subject != null) {
			intent.putExtra(Intent.EXTRA_SUBJECT, subject);
		}
		if (text != null) {
			intent.putExtra(Intent.EXTRA_TEXT, text);
		}
		if (file == null) {
			// 標準メールアプリ
			intent.setAction(Intent.ACTION_SENDTO);
			// 宛先はカンマ区切りの文字列として渡す
			StringBuilder sb = new StringBuilder();
			for (String to : toArr) {
				// 最後に不要なカンマが付いていても問題ない
				sb.append(to + ",");
			}
			intent.setData(Uri.parse("mailto:" + sb.toString()));

			// 標準メールアプリ呼び出し
			context.startActivity(intent);
		} else {
			// 標準メールアプリでは、添付ファイルをつけて、直接呼び出すことが出来ないため、
			// 添付ファイルが存在する場合は、アプリを選択して送信。
			intent.setAction(Intent.ACTION_SEND);
			// 宛先
			if (toArr != null && toArr.length > 0) {
				intent.putExtra(Intent.EXTRA_EMAIL, toArr);
			}
			// 添付ファイル
			// 複数ファイルのまま添付する方法は不明。配列で渡すと例外が発生する。
			intent.putExtra(Intent.EXTRA_STREAM,
					Uri.parse("file://" + file.getAbsolutePath()));
			intent.setType(type);

			// アプリ呼び出し
			context.startActivity(Intent.createChooser(intent,
					"メールアプリを選択してください"));
		}
	}

	/**
	 * 標準メールアプリでメール送信
	 *
	 * @param activity
	 * @param to
	 * @param subject
	 * @param text
	 */
	public static void startActivityForMailSend(Activity activity, String to,
			String subject, String text) {
		startActivityForMailSend(activity, new String[] { to }, subject, text,
				null, null);
	}

	/**
	 * 端末本体の記憶領域にファイル保存
	 *
	 * @param context
	 * @param name
	 *            ファイル名。ディレクトリ区切りを含めることは出来ない。
	 * @param mode
	 *            ContextのMODE_PRIVATE, MODE_APPEND, MODE_WORLD_READABLE,
	 *            MODE_WORLD_WRITEABLEを指定する。
	 * @param data
	 * @throws IOException
	 */
	public static void saveFile(Context context, String name, int mode,
			String data) throws IOException {
		FileOutputStream fos = null;
		try {
			fos = context.openFileOutput(name, mode);
			fos.write(data.getBytes());
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					LogUtil.e("ファイルクローズ失敗", e);
				}
			}
		}

		LogUtil.i("File saved: " + getFile(context, name).getAbsolutePath());
	}

	/**
	 * 外部ストレージにファイル保存
	 *
	 * @param context
	 * @param name
	 *            ファイル名。ディレクトリ区切り含めること可能。
	 * @param data
	 * @throws IOException
	 */
	public static void saveFileOnExternalStorage(Context context, String name,
			String data) throws IOException {
		// 外部ストレージ内の保存先フォルダ
		// 一般的にフォルダ名にはパッケージ名を使用するらしい
		File dir = new File(Environment.getExternalStorageDirectory(),
				context.getPackageName());

		// 始めてファイル保存する場合を考慮してフォルダ作成する
		dir.mkdirs();

		// ファイル名
		File file = new File(dir.getAbsolutePath(), name);

		// ファイル書き込み
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(file);
			fos.write(data.getBytes());
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					LogUtil.e("ファイルクローズ失敗", e);
				}
			}
		}

		LogUtil.i("File saved: " + file.getAbsolutePath());
	}

	/**
	 * 端末本体記憶領域のファイル名取得
	 *
	 * @param context
	 * @param name
	 * @return
	 */
	public static File getFile(Context context, String name) {
		return new File("/data/data/" + context.getPackageName() + "/files",
				name);
	}

	/**
	 * 外部ストレージ内のファイル名取得
	 *
	 * @param context
	 * @param name
	 * @return
	 */
	public static File getFileOnExternalStorage(Context context, String name) {
		return new File(Environment.getExternalStorageDirectory() + "/"
				+ context.getPackageName(), name);
	}
}
