//	Contour - Android drawing teacher application
//	Copyright (C) 2014  Johannes Riedmann
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//	
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//	
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.j0h1.contour.admin.client;

import gwtupload.client.IUploadStatus.Status;
import gwtupload.client.IUploader;
import gwtupload.client.IUploader.UploadedInfo;
import gwtupload.client.SingleUploader;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.j0h1.contour.admin.client.service.ImageService;
import com.j0h1.contour.admin.client.service.ImageServiceAsync;

/**
 * View that contains the UI elements and logic to add a new image.
 * @author Johannes Riedmann
 *
 */
public class AddImageView extends Composite {

	private static AddImageViewUiBinder uiBinder = GWT
			.create(AddImageViewUiBinder.class);

	@UiField TextBox txtTitleEN;
	@UiField TextBox txtTitleDE;
	@UiField VerticalPanel vpnOriginalImage;
	@UiField VerticalPanel vpnContainer;
	@UiField VerticalPanel vpnEdgeImage;
	@UiField FlowPanel fpnEdgePreview;
	@UiField VerticalPanel vpnDefineThreshold;
	@UiField Label lblStatus;
	@UiField Image imgEdgePreview;
	@UiField Image imgOriginalPreview;
	@UiField Button btnSave;
	@UiField TextBox txtThreshold;
	@UiField FlowPanel fpnOriginalPreview;

	private FlowPanel fpnImagePreview;
	private SingleUploader defaultUploader;

	private String edgeImageString;
	private String originalImageString;
	private String type;
	private String fileEnding;

	private final String IMAGE_WIDTH = "300px";

	interface AddImageViewUiBinder extends UiBinder<Widget, AddImageView> {
	}

	public AddImageView() {
		initWidget(uiBinder.createAndBindUi(this));

		// create custom upload widget
		defaultUploader = new SingleUploader();
		vpnOriginalImage.add(defaultUploader);

		// add container for image to VerticalPanel
		fpnImagePreview = new FlowPanel();
		vpnOriginalImage.add(fpnImagePreview);

		vpnEdgeImage.setVisible(false);

		imgEdgePreview.setWidth(IMAGE_WIDTH);
		imgOriginalPreview.setWidth(IMAGE_WIDTH);

		// initial value for threshold
		txtThreshold.setText("60");

		// add a finish handler which will load the image once the upload
		// finishes
		defaultUploader.addOnFinishUploadHandler(onFinishUploaderHandler);
	}

	// load the image in the document and in the case of success attach it to
	// the viewer
	private IUploader.OnFinishUploaderHandler onFinishUploaderHandler = 
			new IUploader.OnFinishUploaderHandler() {
		public void onFinish(IUploader uploader) {
			if (uploader.getStatus() == Status.SUCCESS) {
				// retrieve information from server
				UploadedInfo info = uploader.getServerInfo();
				type = info.ctype;
				
				// extract fileEnding for OpenCV (necessary for image encoding)
				String fileName = defaultUploader.getFileName();
				fileEnding = "." + fileName.split("\\.")[1];
				
				// query HTTP get for base64 encoded image String
				getOriginalImageString(info.fileUrl, info.name, 
						new AsyncCallback<String>() {
							@Override
							public void onFailure(Throwable caught) {
								setStatusLabel("Unable to get uploaded image from server.", true);
							}

							@Override
							public void onSuccess(String result) {
								afterOriginalImageReceived(result);
							}
				});
			}
		}
	};
	
	/**
	 * This method is called after the asynchronous callback from the server is received.
	 * The result of the callback has to be handled outside of the onSuccess method in order
	 * to store the original image string.
	 * @param originalImageString Result of asynchronous callback (uploaded image String).
	 */
	private void afterOriginalImageReceived(String originalImageString) {
		this.originalImageString = originalImageString;
		
		// show uploaded image in image widget
		imgOriginalPreview.setUrl("data:" + type + ";base64," + originalImageString);

		// as soon as upload is finished, add edge image part
		vpnEdgeImage.setVisible(true);

		// calculate edge image (default threshold = 60)
		calculateEdgeImage(60);
	}

	/**
	 * Performs an asynchronous HTTP callback to the server to receive
	 * the uploaded image as bas64 encoded String.
	 * @param fileUrl	URI of String
	 * @param fileName	uploaded filename as key of HashMap on the server
	 * @param callback	response from server
	 */
	public void getOriginalImageString(String fileUrl, String fileName, 
			final AsyncCallback<String> callback) {
		RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, fileUrl);
		try {
			builder.sendRequest(fileName, new RequestCallback() {
				
				@Override
				public void onResponseReceived(Request request, Response response) {
					callback.onSuccess(response.getText());
				}

				@Override
				public void onError(Request request, Throwable exception) {
					callback.onFailure(exception);
				}
			});
		} catch (RequestException e) {
			setStatusLabel("Unable to get uploaded image from server.", true);
		}
	}

	/**
	 * Calculates a base64 encoded image String for the uploaded image file.
	 * @param thresh 	Threshold bigger than 0 and smaller or equal to 100 
	 * 					that is used for edge detection.
	 */
	public void calculateEdgeImage(int thresh) {
		ImageServiceAsync imageService = GWT.create(ImageService.class);

		AsyncCallback<String> callback = new AsyncCallback<String>() {
			public void onFailure(Throwable caught) {
				vpnDefineThreshold.setVisible(false);
			}

			public void onSuccess(String result) {
				// load result to image widget
				imgEdgePreview.setUrl("data:" + type + ";base64," + result);
				lblStatus.setVisible(false);
				btnSave.setEnabled(true);
				
				// set global String to persist it
				edgeImageString = result;
			}
		};

		imageService.generateEdgeImage(originalImageString, fileEnding, thresh, callback);
	}

	/**
	 * Method that is invoked, when generate Button is clicked.
	 * Executes edge detection on image data.
	 * @param event	ClickEvent received from Button.
	 */
	@UiHandler("btnGenerate")
	public void generateClicked(ClickEvent event) {
		// threshold for edge image generation should be a number > 0 and <= 100
		if (txtThreshold.getText().length() > 0
				&& txtThreshold.getText().length() <= 3) {
			try {
				int thresh = Integer.parseInt(txtThreshold.getText().trim());
				if (thresh > 0 && thresh <= 100) {
					calculateEdgeImage(thresh);
				} else {
					setStatusLabel("Enter a threshold between 1 and 100", true);
				}
			} catch (NumberFormatException ex) {
				setStatusLabel("Threshold should be a number between 1 and 100", true);
			}
		} else {
			setStatusLabel("Enter a threshold between 1 and 100", true);
		}
	}

	/**
	 * Method that is invoked, when save Button is clicked.
	 * Persists image to database.
	 * @param event ClickEvent received from Button.
	 */
	@UiHandler("btnSave")
	public void submitImage(ClickEvent event) {
		ImageServiceAsync imageService = GWT.create(ImageService.class);

		AsyncCallback<Void> callback = new AsyncCallback<Void>() {
			public void onFailure(Throwable caught) {
				setStatusLabel(caught.getMessage(), true);
			}

			@Override
			public void onSuccess(Void result) {
				setStatusLabel("Image successfully added.", false);
			}
		};

		if (!txtTitleEN.getText().isEmpty()
				&& !txtTitleDE.getText().isEmpty()) {
			try {
				imageService.persist(txtTitleEN.getText().trim(),
						txtTitleDE.getText().trim(), originalImageString,
						edgeImageString, callback);
			} catch (Exception e) {
				setStatusLabel(e.getMessage(), true);
			}
		} else {
			setStatusLabel("Specify an image title in english and german.", true);
		}
	}

	/**
	 * Set the status label to a given text for feedback.
	 * @param text		Exception/success message.
	 * @param isError 	Flag that defines if the current message is a
	 * 					error message.
	 */
	private void setStatusLabel(String text, boolean isError) {
		lblStatus.setVisible(true);
		lblStatus.setText(text);
		
		if (isError) {
			lblStatus.getElement().getStyle().setColor("red");
		} else {
			lblStatus.getElement().getStyle().setColor("green");
		}
		
		Timer t = new Timer() {
			public void run() {
				lblStatus.setVisible(false);
			}
		};

		t.schedule(3000);
	}

}
