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.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.ViewTreeObserver;
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.LinkedBlockingQueue;
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 create_thread;
	Thread server_thread;
	Thread log_thread;
	ServiceConnection service_connection;
	FileObserver observer;
	LinkedBlockingQueue<String> log_queue = new LinkedBlockingQueue<String>();

	private ValueCallback<Uri[]> upload_message;
	private final static int FILECHOOSER_RESULT = 1;
	private float touch_down_y;
	private boolean ready = false;
	private boolean loaded = false;
	private boolean shutting_down = false;

	static {
		log("Calling system.loadLibrary().");
		System.loadLibrary("tildefriends");
		log("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);

	public static void log(String message) {
		if (s_activity != null && s_activity.log_queue != null && message != null) {
			try {
				s_activity.log_queue.put(message);
			} catch (InterruptedException e) {
				android.util.Log.w("tildefriends", message);
			}
		}
	}

	private void createThread() {
		web_view = (TildeFriendsWebView)findViewById(R.id.web);
		log(String.format("getFilesDir() is %s", getFilesDir().toString()));
		log(String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
		log(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;

		server_thread = new Thread(new Runnable() {
			@Override
			public void run() {
				log("Watching for changes in: " + getFilesDir().toString());
				observer = make_file_observer(getFilesDir().toString(), port_file_path);
				observer.startWatching();

				log("Calling tf_server_main.");
				int result = tf_server_main(
					getFilesDir().toString(),
					getPackageResourcePath().toString(),
					port_file_path,
					(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE));
				log("tf_server_main returned " + result + ".");
			}
		});
		server_thread.start();

		runOnUiThread(() -> {
			web_view.getSettings().setJavaScriptEnabled(true);
			web_view.getSettings().setDomStorageEnabled(true);

			set_database_enabled();
			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("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("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("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(consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
					return true;
				}
			});

			web_view.setWebViewClient(new WebViewClient() {
				@Override
				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;
					}
				}

				@Override
				public void onPageFinished(WebView view, String url) {
					s_activity.loaded = true;
				}
			});
		});

		s_activity.create_thread = null;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		s_activity = this;
		super.onCreate(savedInstanceState);
		log_thread = new Thread(new Runnable() {
			@Override
			public void run() {
				while (!s_activity.shutting_down) {
					try {
						String message = log_queue.take();
						if (message != null) {
							android.util.Log.w("tildefriends", message);
						} else {
							break;
						}
					} catch (InterruptedException e) {
					}
				}
			}
		});
		log_thread.start();
		StrictMode.setThreadPolicy(
			new StrictMode.ThreadPolicy.Builder()
				.detectAll()
				.penaltyDialog()
				.penaltyLog()
				.build());
		StrictMode.setVmPolicy(
			new StrictMode.VmPolicy.Builder()
				.detectLeakedClosableObjects()
				.detectAll()
				.penaltyLog()
				.build());
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);

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

		final View content = findViewById(android.R.id.content);
		content.getViewTreeObserver().addOnPreDrawListener(
			new ViewTreeObserver.OnPreDrawListener() {
				@Override
				public boolean onPreDraw() {
					if (s_activity.ready && s_activity.loaded) {
						content.getViewTreeObserver().removeOnPreDrawListener(this);
						return true;
					} else {
						return false;
					}
				}
			}
		);

		create_thread = new Thread(new Runnable() {
			@Override
			public void run() {
				s_activity.createThread();
			}
		});
		create_thread.start();
	}

	@Override
	protected void onDestroy()
	{
		try {
			shutting_down = true;
			if (log_queue != null) {
					log_queue.put("Goodbye.");
			}
			log_thread.join();
		} catch (InterruptedException e) {
		}
		log_thread = null;
		s_activity = null;
		super.onDestroy();
	}

	@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) {
		} catch (java.io.IOException e) {
			e.printStackTrace();
		}
		return -1;
	}

	public static void start_sandbox(int pipe_fd) {
		log("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("onBindingDied");
			}

			@Override
			public void onNullBinding(ComponentName name) {
				log("onNullBinding");
			}

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

			@Override
			public void onServiceDisconnected(ComponentName name) {
				log("onServiceDisconnected");
			}
		};
		s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
	}

	public static void stop_sandbox() {
		log("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(() -> {
				ready = true;
				web_view.loadUrl(base_url + "login/auto");
			});
			observer.stopWatching();
			observer = null;
		}
	}

	@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());
		}
	}

	@SuppressWarnings("deprecation")
	private void set_database_enabled()
	{
		web_view.getSettings().setDatabaseEnabled(true);
	}
}
