package com.unprompted.tildefriends;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.StrictMode;
import android.text.InputType;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

public class TildeFriendsActivity extends Activity {
	static TildeFriendsActivity s_activity;
	TildeFriendsWebView web_view;
	String base_url;
	String port_file_path;
	Thread server_thread;
	ServiceConnection service_connection;
	FileObserver observer;

	private ValueCallback<Uri[]> upload_message;
	private final static int FILECHOOSER_RESULT = 1;
	private float touch_down_y;

	static {
		Log.w("tildefriends", "Calling system.loadLibrary().");
		System.loadLibrary("tildefriends");
		Log.w("tildefriends", "system.loadLibrary() completed.");
	}

	public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
	public static native int tf_sandbox_main(int pipe_fd);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		s_activity = this;
		StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
			.detectLeakedClosableObjects()
			.penaltyLog()
			.build());
		super.onCreate(savedInstanceState);
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);

		web_view = (TildeFriendsWebView)findViewById(R.id.web);
		set_status("Extracting executable...");
		Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString()));
		Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
		Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));

		port_file_path = getFilesDir().toString() + "/port.txt";
		new File(port_file_path).delete();
		base_url = "http://127.0.0.1:12345/";

		TildeFriendsActivity activity = this;

		Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
		observer = make_file_observer(getFilesDir().toString(), port_file_path);
		observer.startWatching();

		set_status("Starting server...");
		server_thread = new Thread(new Runnable() {
			@Override
			public void run() {
				Log.w("tildefriends", "Calling tf_server_main.");
				int result = tf_server_main(
					getFilesDir().toString(),
					getPackageResourcePath().toString(),
					port_file_path,
					(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE));
				Log.w("tildefriends", "tf_server_main returned " + result + ".");
			}
		});
		server_thread.start();

		web_view.getSettings().setJavaScriptEnabled(true);
		web_view.getSettings().setDatabaseEnabled(true);
		web_view.getSettings().setDomStorageEnabled(true);

		set_database_path();

		web_view.setDownloadListener(new DownloadListener() {
			public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
				Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")");
				String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
				if (url.startsWith("data:") && url.indexOf(',') != -1) {
					String b64 = url.substring(url.indexOf(',') + 1);
					byte[] data = Base64.decode(b64, Base64.DEFAULT);
					Log.w("tildefriends", "Downloaded " + data.length + " bytes.");
					File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
					try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
						stream.write(data);
					} catch (java.io.IOException e) {
						Log.w("tildefriends", "IOException: " + e.toString());
					}
					Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
				} else {
					DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
					request.setMimeType(mime_type);
					String cookies = CookieManager.getInstance().getCookie(url);
					request.addRequestHeader("cookie", cookies);
					request.addRequestHeader("User-Agent", userAgent);
					request.setDescription("Downloading file...");
					request.setTitle(file_name);
					request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
					request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, content_disposition, mime_type));
					DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
					dm.enqueue(request);
					Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
				}
			}
		});

		web_view.setWebChromeClient(new WebChromeClient() {
			@Override
			public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
				new AlertDialog.Builder(view.getContext())
					.setTitle("Tilde Friends")
					.setMessage(message)
					.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							result.confirm();
						}
					})
					.create()
					.show();
				return true;
			}

			@Override
			public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
				new AlertDialog.Builder(view.getContext())
					.setTitle("Tilde Friends")
					.setMessage(message)
					.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							result.confirm();
						}
					})
					.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							result.cancel();
						}
					})
					.create()
					.show();
				return true;
			}

			@Override
			public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
				EditText input = new EditText(view.getContext());
				input.setInputType(InputType.TYPE_CLASS_TEXT);
				input.setText(defaultValue);

				new AlertDialog.Builder(view.getContext())
					.setTitle("Tilde Friends")
					.setMessage(message)
					.setView(input)
					.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							result.confirm(input.getText().toString());
						}
					})
					.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							result.cancel();
						}
					})
					.create()
					.show();
				return true;
			}

			/*
			** https://stackoverflow.com/questions/5907369/file-upload-in-webview
			** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
			*/

			@Override
			public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
				upload_message = message;
				Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
				intent.addCategory(Intent.CATEGORY_OPENABLE);
				intent.setType("*/*");
				TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
				return true;
			}

			@Override
			public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
				Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
				return true;
			}
		});

		web_view.setWebViewClient(new WebViewClient() {
			public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
			{
				if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
					return false;
				} else {
					view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
					return true;
				}
			}
		});

		TextView refresh = (TextView)findViewById(R.id.refresh);
		refresh.setVisibility(View.GONE);
		refresh.setText("REFRESH");
	}

	@Override
	protected void onDestroy()
	{
		super.onDestroy();
		s_activity = null;
	}

	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);
		web_view.saveState(outState);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState)
	{
		super.onRestoreInstanceState(savedInstanceState);
		web_view.restoreState(savedInstanceState);
	}

	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
		if (requestCode == FILECHOOSER_RESULT) {
			if (upload_message == null) {
				return;
			}
			upload_message.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
			upload_message = null;
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && web_view.canGoBack()) {
			web_view.goBack();
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_FORWARD && web_view.canGoForward()) {
			web_view.goForward();
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_REFRESH) {
			web_view.reload();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		final int k_drag_distance = 160;
		switch (event.getActionMasked()) {
		case MotionEvent.ACTION_DOWN:
			touch_down_y = event.getY();
			web_view.clearOverscrolledY();
			break;
		case MotionEvent.ACTION_MOVE:
			{
				float delta = web_view.getOverscrolledY() ? event.getY() - touch_down_y : 0.0f;
				TextView refresh = (TextView)findViewById(R.id.refresh);
				LayoutParams layout = refresh.getLayoutParams();
				layout.height =
					Math.min(Math.max((int)delta, 0), k_drag_distance) +
					(delta > k_drag_distance ? (int)Math.sqrt(delta - k_drag_distance) : 0);
				refresh.setLayoutParams(layout);
				refresh.setVisibility(layout.height > 0 ? View.VISIBLE : View.GONE);
			}
			break;
		case MotionEvent.ACTION_UP:
			{
				float delta = web_view.getOverscrolledY() ? event.getY() - touch_down_y : 0.0f;
				if (delta > getWindow().getDecorView().getHeight() / 4) {
					web_view.reload();
				}
				TextView refresh = (TextView)findViewById(R.id.refresh);
				refresh.setVisibility(View.GONE);
			}
			break;
		}
		return super.dispatchTouchEvent(event);
	}

	private int read_port(String path) {
		try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
			String line = reader.readLine();
			if (line != null) {
				return Integer.parseInt(line);
			}
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (java.io.FileNotFoundException e) {
			Log.w("tildefriends", "Port file does not yet exist.");
		} catch (java.io.IOException e) {
			e.printStackTrace();
		}
		return -1;
	}

	private void set_status(String text) {
		TextView text_view = (TextView)findViewById(R.id.text);
		web_view.setVisibility(View.GONE);
		text_view.setVisibility(View.VISIBLE);
		text_view.setText(text);
	}

	private void hide_status() {
		TextView text_view = (TextView)findViewById(R.id.text);
		web_view.setVisibility(View.VISIBLE);
		text_view.setVisibility(View.GONE);
	}

	public static void start_sandbox(int pipe_fd) {
		Log.w("tildefriends", "starting service with fd: " + pipe_fd);
		Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
		s_activity.service_connection = new ServiceConnection() {
			@Override
			public void onBindingDied(ComponentName name) {
				Log.w("tildefriends", "onBindingDied");
			}

			@Override
			public void onNullBinding(ComponentName name) {
				Log.w("tildefriends", "onNullBinding");
			}

			@Override
			public void onServiceConnected(ComponentName name, IBinder binder) {
				Log.w("tildefriends", "onServiceConnected");
				Parcel data = Parcel.obtain();
				try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(pipe_fd)) {
					data.writeParcelable(pfd, 0);
				} catch (java.io.IOException e) {
					Log.w("tildefriends", "IOException: " + e);
				}
				try {
					binder.transact(TildeFriendsSandboxService.START_CALL,  data, null, IBinder.FLAG_ONEWAY);
				} catch (RemoteException e) {
					Log.w("tildefriends", "RemoteException");
				} finally {
					data.recycle();
				}
			}

			@Override
			public void onServiceDisconnected(ComponentName name) {
				Log.w("tildefriends", "onServiceDisconnected");
			}
		};
		s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
	}

	public static void stop_sandbox() {
		Log.w("tildefriends", "stop_sandbox");
		if (s_activity.service_connection != null) {
			s_activity.unbindService(s_activity.service_connection);
			s_activity.service_connection = null;
		}
	}

	private void check_port_file(String path) {
		int port = read_port(port_file_path);
		if (port >= 0) {
			base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
			runOnUiThread(() -> {
				hide_status();
				web_view.loadUrl(base_url);
			});
			observer = null;
		} else {
			runOnUiThread(() -> {
				set_status("Waiting to connect...");
			});
		}
	}

	@SuppressWarnings("deprecation")
	private FileObserver make_file_observer(String dir, String path) {
		FileObserver file_observer = new FileObserver(dir, FileObserver.ALL_EVENTS) {
			@Override
			public void onEvent(int event, String file) {
				if (observer != null) {
					check_port_file(path);
				}
			}
		};
		check_port_file(path);
		return file_observer;
	}

	@SuppressWarnings("deprecation")
	private void set_database_path()
	{
		if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
			web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
		}
	}
}
