package de.tudarmstadt.informatik.hostage.ui;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.DatePicker;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
import de.tudarmstadt.informatik.hostage.R;
import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
import de.tudarmstadt.informatik.hostage.logging.Record;
import de.tudarmstadt.informatik.hostage.logging.formatter.TraCINgFormatter;

/**
 * ViewLog shows Statistics about the recorded Attacks. It also offers the user
 * several options to perform actions with the database. This includes:<br>
 * - show records,<br>
 * - export records,<br>
 * - upload records,<br>
 * - and delete records.
 * 
 * @author Lars Pandikow
 */
public class ViewLog extends Activity {

	private final SimpleDateFormat sdf = new SimpleDateFormat(
			"MMM dd,yyyy HH:mm", Locale.US);

	private SharedPreferences pref;
	private Editor editor;

	/**
	 * Creates a Dialog that lets the user decide which criteria he want to use
	 * to delete records. Then calls the corresponding method.<br>
	 * The possible criteria are coded in /res/values/arrays.xml To add a
	 * criteria add a String to the array and extend the switch statement.
	 * 
	 * @param view
	 *            View elements which triggers the method call.
	 * @see ViewLog#deleteByBSSID()
	 * @see ViewLog#deleteByDate()
	 * @see ViewLog#deleteAll()
	 */
	public void deleteLog(View view) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.gui_delete_dialog_title);
		builder.setItems(R.array.delete_criteria,
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int position) {
						switch (position) {
						case 0:
							deleteByBSSID();
							break;
						case 1:
							deleteByDate();
							break;
						case 2:
							deleteAll();
						}
					}
				});
		builder.create();
		builder.show();
	}

	/**
	 * Creates a Dialog to choose export format. Then calls
	 * {@link ViewLog#exportDatabase(int)}
	 * 
	 * @param view
	 *            View elements which triggers the method call.
	 */
	public void exportDatabase(View view) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.gui_export_dialog_title);
		builder.setItems(R.array.format, new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int position) {
				exportDatabase(position);
			}
		});
		builder.create();
		builder.show();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
		case R.id.action_settings:
			startActivity(new Intent(this, SettingsActivity.class));
			break;
		case R.id.action_about:
			startActivity(new Intent(this, AboutActivity.class));
			break;
		default:
		}
		return super.onOptionsItemSelected(item);
	}

	/**
	 * Starts a ViewLogTable Activity.
	 * 
	 * @param view
	 *            View elements which triggers the method call.
	 * @see ViewLogTable
	 */
	public void showLog(View view) {
		startActivity(new Intent(this, ViewLogTable.class));
		// TODO Delete

	}

	/**
	 * Uploads a JSON Representation of each attack to a server, specified in
	 * the preferences.<br>
	 * The method only uploads information about attacks that have net been
	 * uploaded yet.<br>
	 * The local and remote IP of each record will be replaced with the external
	 * IP of then device at the time of the upload. For the Upload it uses a
	 * HttpPost with a HttpsClient, which does not validate any certificates.<br>
	 * The Upload runs in its own Thread.<br>
	 * While uploading the method also creates a notification to inform the user
	 * about the status of the upload.<br>
	 * <b>Only uploads Records with a external IP that is not null!
	 * 
	 * @param view
	 *            View elements which triggers the method call.
	 * @see de.tudarmstadt.informatik.hostage.net.MySSLSocketFactory
	 */
	public void uploadDatabase(View view) {
		// Create a Notification
		final NotificationCompat.Builder builder;
		final NotificationManager mNotifyManager;
		final int lastUploadedAttackId = pref.getInt("LAST_UPLOADED_ATTACK_ID",
				-1);
		int currentAttackId = pref.getInt("ATTACK_ID_COUNTER", 0);
		// Check if there are new records to upload
		if (lastUploadedAttackId == currentAttackId - 1) {
			// Inform user that no upload is necessary
			Toast.makeText(this, "All data have already been uploaded.",
					Toast.LENGTH_SHORT).show();
			return;
		}
		// Build and show Upload Notification in notification bar
		builder = new NotificationCompat.Builder(this)
				.setContentTitle(this.getString(R.string.app_name))
				.setContentText("Upload in progress...")
				.setTicker("Uploading Data...")
				.setSmallIcon(R.drawable.ic_launcher)
				.setWhen(System.currentTimeMillis());
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
		stackBuilder.addParentStack(MainActivity.class);
		stackBuilder.addNextIntent(new Intent());
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
				PendingIntent.FLAG_UPDATE_CURRENT);
		builder.setContentIntent(resultPendingIntent);
		builder.setAutoCancel(true);
		builder.setOnlyAlertOnce(false);
		mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		mNotifyManager.notify(2, builder.build());
		final Context context = this;
		// Create a new Thread for upload
		new Thread(new Runnable() {
			@Override
			public void run() {
				// get RecordList
				ArrayList<Record> recordList = null;// logger.getRecordOfEachAttack(lastUploadedAttackId);
				final int progressMax = recordList.size();
				Log.i("SQLLogger", "Logs to upload: " + progressMax);

				int progressBarStatus = 0;
				int retry_counter = 0;
				while (progressBarStatus < progressMax) {
					Record record = recordList.get(progressBarStatus);
					// Only upload records with a saved external ip
					if (record.getExternalIP() != null) {
						retry_counter = 0;
						if (HelperUtils.uploadSingleRecord(context, record)) {
							// Update Notification progress bar
							progressBarStatus++;
							builder.setProgress(progressMax, progressBarStatus,
									false);
							// Update the progress bar
							mNotifyManager.notify(2, builder.build());
						} else {
							retry_counter++;
							if (retry_counter == 3) {
								retry_counter = 0;
								progressBarStatus++;
							}
						}
					}
				}

				if (progressBarStatus == progressMax) {
					// When the loop is finished, updates the notification
					builder.setContentText("Upload complete")
							.setTicker("Upload complete")
							// Removes the progress bar
							.setProgress(0, 0, false);
					mNotifyManager.notify(2, builder.build());
				}
			}
		}).start();
		editor.putInt("LAST_UPLOADED_ATTACK_ID", currentAttackId - 1);
		editor.commit();
	}

	/**
	 * Shows a Dialog to confirm that the database should be cleared. If
	 * confirmed {@link SQLLogger#clearData()} is called.
	 * 
	 * @see ILogger#clearData()
	 */
	private void deleteAll() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setMessage(R.string.gui_dialog_clear_database)
				.setPositiveButton(R.string.gui_clear,
						new DialogInterface.OnClickListener() {
							@Override
							@SuppressLint("NewApi")
							public void onClick(DialogInterface dialog, int id) {
								// Clear all Data
								// logger.clearData();
								editor.putInt("ATTACK_ID_COUNTER", 0);
								editor.putInt("LAST_UPLOADED_ATTACK_ID", -1);
								editor.commit();
								Toast.makeText(getApplicationContext(),
										"Database cleared!", Toast.LENGTH_SHORT)
										.show();
								// Recreate the activity
								if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
									recreate();
								} else {
									Intent intent = getIntent();
									finish();
									startActivity(intent);
								}
							}
						})
				.setNegativeButton(R.string.gui_cancel,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog, int id) {
								// User cancelled the dialog
							}
						});
		// Create the AlertDialog object
		builder.create();
		builder.show();
	}

	/**
	 * Shows a List with all recorded BSSIDs. If a BSSID is selected the method
	 * calls {@link ILogger#deleteByBSSID(String)} to delete all records with
	 * the chosen BSSID
	 * 
	 * @see ILogger#deleteByBSSID
	 */
	private void deleteByBSSID() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		final String[] bssidArray = null;// logger.getAllBSSIDS();
		final String[] strings = new String[bssidArray.length];
		for (int i = 0; i < bssidArray.length; i++) {
			strings[i] = bssidArray[i] + " (" // + logger.getSSID(bssidArray[i])
					+ ")";
		}
		builder.setTitle(R.string.gui_delete_dialog_title);
		builder.setItems(strings, new DialogInterface.OnClickListener() {
			@Override
			@SuppressLint("NewApi")
			public void onClick(DialogInterface dialog, int position) {

				// logger.deleteByBSSID(bssidArray[position]);
				Toast.makeText(
						getApplicationContext(),
						"All entries with bssid '" + bssidArray[position]
								+ "' deleted.", Toast.LENGTH_SHORT).show();
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
					recreate();
				} else {
					Intent intent = getIntent();
					finish();
					startActivity(intent);
				}
			}
		});
		builder.create();
		builder.show();
	}

	/**
	 * Creates a DatePicking Dialog where the user can choose a date. Afterwards
	 * {@link ViewLog#deleteByDate(int, int, int)} is called.
	 */
	private void deleteByDate() {
		showDialog(0);
	}

	/**
	 * Shows a Dialog with the given date and ask him to confirm deleting
	 * records that are older than the shown date. When the user confirms
	 * {@link ILogger#deleteByDate(long)} is called with a long representation
	 * of the Date. If the user cancels the Dialog nothing happens and the
	 * dialog disappears.
	 * 
	 * @param year
	 * @param monthOfYear
	 * @param dayOfMonth
	 */
	private void deleteByDate(int year, int monthOfYear, int dayOfMonth) {
		TimePicker timePicker = new TimePicker(this);
		final Calendar calendar = Calendar.getInstance();
		calendar.set(year, monthOfYear, dayOfMonth,
				timePicker.getCurrentHour(), timePicker.getCurrentMinute(), 0);
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.gui_dialog_clear_database_date)
				.setMessage(sdf.format(calendar.getTime()))
				.setPositiveButton(R.string.gui_delete,
						new DialogInterface.OnClickListener() {
							@Override
							@SuppressLint("NewApi")
							public void onClick(DialogInterface dialog, int id) {
								long time = calendar.getTimeInMillis();
								// Delete Data
								// logger.deleteByDate(time);
								Toast.makeText(getApplicationContext(),
										"Data sets deleted!",
										Toast.LENGTH_SHORT).show();
								// Recreate the activity
								if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
									recreate();
								} else {
									Intent intent = getIntent();
									finish();
									startActivity(intent);
								}
							}
						})
				.setNegativeButton(R.string.gui_cancel,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog, int id) {
								// User cancelled the dialog
							}
						});
		// Create the AlertDialog object
		builder.create();
		builder.show();
	}

	/**
	 * Exports all records in a given format. Before exporting checks export
	 * location from preferences.
	 * 
	 * @param format
	 *            Integer coded export format
	 * @see Record#toString(int)
	 */
	private void exportDatabase(int format) {
		try {
			FileOutputStream log;
			String filename = "hostage_" + format + "_"
					+ System.currentTimeMillis() + ".log";
			boolean externalStorage = pref.getBoolean("pref_external_storage",
					false);
			String externalLocation = pref.getString("pref_external_location",
					"");
			if (externalStorage) {
				String root = Environment.getExternalStorageDirectory()
						.toString();
				if (root != null && HelperUtils.isExternalStorageWritable()) {
					File dir = new File(root + externalLocation);
					dir.mkdirs();
					File file = new File(dir, filename);
					log = new FileOutputStream(file);
				} else {
					Toast.makeText(this, "Could not write to SD Card",
							Toast.LENGTH_SHORT).show();
					return;
				}

			} else {
				log = this.openFileOutput(
						"hostage_" + format + "_" + System.currentTimeMillis()
								+ ".log", Context.MODE_PRIVATE);
			}

			ArrayList<Record> records = null;// logger.getAllRecords();
			for (Record record : records) {
				log.write((record.toString((format == 1) ? TraCINgFormatter
						.getInstance() : null)).getBytes());
			}
			log.flush();
			log.close();
			Toast.makeText(
					this,
					externalStorage ? filename + " saved on external memory! "
							+ externalLocation : filename
							+ " saved on internal memory!", Toast.LENGTH_LONG)
					.show();
		} catch (Exception e) {
			Toast.makeText(this, "Could not write to SD Card",
					Toast.LENGTH_SHORT).show();
			e.printStackTrace();
		}
	}

	/**
	 * Initializes the Statistics. Creates a table row for every protocol and
	 * checks the dabase for the attack count. Calls
	 * {@link ViewLog#setFirstAndLastAttack()} to set the TextViews.
	 * 
	 * @see ILogger#getAttackCount()
	 * @see ILogger#getAttackPerProtocolCount(String)
	 */
	private void initStatistic() {
		TableLayout table = (TableLayout) findViewById(R.id.layoutContainer);

		ArrayList<String> protocols = new ArrayList<String>();
		protocols.add("Total");
		protocols.addAll(Arrays.asList(getResources().getStringArray(
				R.array.protocols)));

		for (String protocol : protocols) {
			TableRow row = new TableRow(this);
			TextView protocolName = new TextView(this);
			protocolName.setBackgroundResource(R.color.dark_grey);
			protocolName.setText(protocol);
			protocolName.setTextAppearance(this,
					android.R.style.TextAppearance_Medium);
			protocolName.setPadding(6, 0, 0, 0);
			row.addView(protocolName);
			TextView value = new TextView(this);
			value.setBackgroundResource(R.color.light_grey);
			value.setTextAppearance(this, android.R.style.TextAppearance_Medium);
			value.setGravity(Gravity.RIGHT);
			value.setPadding(3, 0, 3, 0);
			row.addView(value);
			if (protocol.equals("Total")) {
				value.setText(""); // + logger.getAttackCount());
			} else {
				value.setText("");// +
									// logger.getAttackPerProtocolCount(protocol));
			}
			table.addView(row);
		}
		setFirstAndLastAttack();
	}

	/**
	 * Sets the TextViews for first and last attack.
	 * 
	 * @see ILogger#getSmallestAttackId()
	 * @see ILogger#getHighestAttackId()
	 * @see ILogger#getRecordOfAttackId(long)
	 */
	private void setFirstAndLastAttack() {
		Record firstAttack = null;// logger.getRecordOfAttackId(logger.getSmallestAttackId());
		Record lastAttack = null;// logger.getRecordOfAttackId(logger.getHighestAttackId());
		if (firstAttack != null) {
			Date resultdate = new Date(firstAttack.getTimestamp());
			TextView text = (TextView) findViewById(R.id.textFirstAttackValue);
			text.setText(sdf.format(resultdate));
			text = (TextView) findViewById(R.id.textLastAttackValue);
			resultdate = new Date(lastAttack.getTimestamp());
			text.setText(sdf.format(resultdate));
		} else {
			TextView text = (TextView) findViewById(R.id.textFirstAttackValue);
			text.setText("-");
			text = (TextView) findViewById(R.id.textLastAttackValue);
			text.setText("-");
		}
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_viewlog);
		pref = PreferenceManager.getDefaultSharedPreferences(this);
		editor = pref.edit();
		initStatistic();
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case 0:
			final Calendar cal = Calendar.getInstance();
			int pYear = cal.get(Calendar.YEAR);
			int pMonth = cal.get(Calendar.MONTH);
			int pDay = cal.get(Calendar.DAY_OF_MONTH);
			return new DatePickerDialog(this,
					new DatePickerDialog.OnDateSetListener() {
						@Override
						public void onDateSet(DatePicker view, int year,
								int monthOfYear, int dayOfMonth) {
							deleteByDate(year, monthOfYear, dayOfMonth);
						}
					}, pYear, pMonth, pDay);
		}
		return null;
	}
}