package org.txm.backtomedia.editors.player;

import java.io.File;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Semaphore;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Scale;
import org.txm.backtomedia.commands.function.TripleRangeSlider;
import org.txm.backtomedia.preferences.BackToMediaPreferences;
import org.txm.utils.logger.Log;

import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.embed.swt.FXCanvas;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.scene.media.MediaView;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import vlcplayerrcp.MessagesMP;

public class JFXPlayer extends Composite implements IPlayer {
	
	protected static final String NOMEDIA = ""; //$NON-NLS-1$
	
	private MediaPlayer jfxPlayer;
	
	//private GLComposite videoComposite;
	
	private Scale rateField;
	
	private Label rateValueLabel;
	
	private Scale volumeField;
	
	private Label volumeValueLabel;
	
	private Button playButton;
	
	private Button repeatButton;
	
	protected String currentlyPlayed = ""; //$NON-NLS-1$
	
	// private String startTime, endTime;
	private int start, end;
	
	protected boolean hasEnded = false;
	
	private String previouslyPlayed = ""; //$NON-NLS-1$
	
	private boolean repeat = false;
	
	protected int volume = 100;
	
	private Button stopButton;
	
	Label timeLabel;
	
	TripleRangeSlider timeRange;
	
	boolean firstLengthEvent = true;
	
	long previous = 0;
	
	long time, mins, secs;
	
	public MediaPlayer getEmbeddedMediaPlayer() {
		return jfxPlayer;
	}
	
	Semaphore s = new Semaphore(1);
	
	private FXCanvas fxCanvas;
	
	Point previousP;
	
	protected boolean userStopped;
	
	public JFXPlayer(Composite parent, int style) {
		super(parent, style);
		this.setLayout(new GridLayout(11, false));
		
		// very important to avoid error when re-opening the player
		javafx.application.Platform.setImplicitExit(false);
		
		// THE PLAYER
		// if (RuntimeUtil.isMac()) {
		// try {
		// LibC.INSTANCE.setenv("VLC_PLUGIN_PATH", "/Applications/VLC.app/Contents/MacOS/plugins", 1);
		// }
		// catch (Exception ex) {
		// ex.printStackTrace();
		// }
		// }
//		videoComposite = new GLComposite(this, SWT.NONE, "Video");
//		GridData gdata = new GridData(SWT.FILL, SWT.FILL, true, true);
//		gdata.horizontalSpan = 11;
//		videoComposite.setLayoutData(gdata);
//		jfxPlayer = null;
//		
//		fxCanvas.addDisposeListener(new DisposeListener() {
//			
//			@Override
//			public void widgetDisposed(DisposeEvent e) {
//				if (jfxPlayer != null) {
//					jfxPlayer.dispose();
//					jfxPlayer = null;
//					fxCanvas.setScene(null);
//				}
//			}
//		});
		
		fxCanvas = new FXCanvas(this, SWT.BORDER);
		GridData gdata2 = new GridData(SWT.FILL, SWT.FILL, true, true);
		gdata2.horizontalSpan = 11;

		fxCanvas.setLayoutData(gdata2);
		
		fxCanvas.addMouseListener(new MouseListener() {
			
			@Override
			public void mouseUp(MouseEvent e) {
				if (jfxPlayer != null) {
					if (jfxPlayer.getStatus().equals(Status.PAUSED)) {
						resume();
					}
					else if (jfxPlayer.getStatus().equals(Status.PLAYING)) {
						pause();
					}
				}
			}
			
			@Override
			public void mouseDown(MouseEvent e) {}
			
			@Override
			public void mouseDoubleClick(MouseEvent e) {}
		});
		
		fxCanvas.addListener(SWT.Resize, new Listener() {
			
			@Override
			public void handleEvent(Event e) {
				resizeView();
			}
		});
		
		// THE CONTROL BUTTONS
		playButton = new Button(this, SWT.PUSH);
		GridData playLayoutData = new GridData(SWT.FILL, SWT.CENTER, false, false);
		playButton.setLayoutData(playLayoutData);
		playButton.setText(MessagesMP.play);
		playButton.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				userStopped = false;
				if (currentlyPlayed.length() == 0) {
					selectMedia();
					playButton.setText(MessagesMP.pause);
					playButton.getParent().layout();
				}
				else if (jfxPlayer != null && jfxPlayer.getStatus().equals(Status.PLAYING)) {
					pause();
				}
				else if (jfxPlayer != null && hasEnded) {
					if (jfxPlayer == null) return;
					replay();
				}
				else {
					resume();
				}
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		// Button browseButton = new Button(this,SWT.PUSH);
		// browseButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
		// browseButton.setText("Open...");
		// browseButton.addSelectionListener(new SelectionListener() {
		// @Override
		// public void widgetSelected(SelectionEvent e) {
		// selectMedia();
		// }
		//
		// @Override
		// public void widgetDefaultSelected(SelectionEvent e) {}
		// });
		
		stopButton = new Button(this, SWT.PUSH);
		stopButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
		stopButton.setText(MessagesMP.stop);
		stopButton.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				if (jfxPlayer != null) {
					userStopped = true;
					jfxPlayer.stop();
				}
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		timeLabel = new Label(this, SWT.NONE);
		timeLabel.setText("00:00"); //$NON-NLS-1$
		
		timeRange = new TripleRangeSlider(this, SWT.None);
		timeRange.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		timeRange.setToolTipText(MessagesMP.time_range);
		timeRange.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				TripleRangeSlider.SELECTED_KNOB sk = timeRange.getLastSelectedKnob();
				switch (sk) {
					case UPPER:
						end = timeRange.getUpperValue();
						
						jfxPlayer.setStopTime(Duration.seconds(end));
						if (end < time) {
							// System.out.println("Upper changed: fix time");
							time = end;
							jfxPlayer.seek(Duration.seconds(time));
						}
						break;
					case LOWER:
						start = timeRange.getLowerValue();
						jfxPlayer.setStartTime(Duration.seconds(start));
						if (start > time) {
							// System.out.println("Lower changed: fix time");
							time = start;
							jfxPlayer.seek(Duration.seconds(time));
						}
						break;
					case MIDDLE:
						
						time = timeRange.getMiddleValue();
						// System.out.println("Middle changed: fix time: " + time);
						
						jfxPlayer.seek(Duration.seconds(time));
						
						break;
					default:
						// nothing
				}
				// System.out.println("time range: "+start+" -> "+end+" time="+time);
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		repeatButton = new Button(this, SWT.CHECK);
		repeatButton.setText(MessagesMP.repeat);
		repeat = BackToMediaPreferences.getInstance().getBoolean(BackToMediaPreferences.REPEAT);
		repeatButton.setSelection(repeat);
		repeatButton.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				repeat = repeatButton.getSelection();
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		Label l = new Label(this, SWT.NONE);
		l.setText(MessagesMP.rate);
		
		rateField = new Scale(this, SWT.BORDER);
		GridData gdata4 = new GridData(SWT.FILL, SWT.CENTER, false, false);
		gdata4.widthHint = 100;
		rateField.setLayoutData(gdata4);
		rateField.setMaximum(140);
		rateField.setMinimum(70);
		rateField.setSelection(100);
		rateField.setPageIncrement(5);
		
		rateField.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				float rate = rateField.getSelection() / 100.0f;
				jfxPlayer.setRate(rate);
				rateValueLabel.setText("" + rateField.getSelection() + "%");
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		rateValueLabel = new Label(this, SWT.NONE);
		rateValueLabel.setText("100%");//$NON-NLS-1$
		
		l = new Label(this, SWT.NONE);
		l.setText(MessagesMP.volume);
		
		volumeField = new Scale(this, SWT.BORDER);
		gdata4 = new GridData(SWT.FILL, SWT.CENTER, false, false);
		gdata4.widthHint = 100;
		volumeField.setLayoutData(gdata4);
		volumeField.setMinimum(0);
		volumeField.setMaximum(100);
		volumeField.setSelection(volume);
		volumeField.setPageIncrement(5);
		volumeField.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				jfxPlayer.setVolume(volumeField.getSelection());
				volume = volumeField.getSelection();
				volumeValueLabel.setText("" + volume + "%");
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		});
		
		volumeValueLabel = new Label(this, SWT.NONE);
		volumeValueLabel.setText("100%");
		
		// vlcPlayer.events().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
		//
		// @Override
		// public void opening(MediaPlayer mediaPlayer) {
		// if (videoComposite.isDisposed()) return;
		// Log.finer("Opening media...");
		// }
		//
		// @Override
		// public void finished(MediaPlayer mediaPlayer) {
		// if (videoComposite.isDisposed()) return;
		// Log.finer("Finished playing media...");
		// if (repeat) {
		// replay();
		// }
		// else {
		// hasEnded = true;
		// }
		// }
		// });
		
		
		//
		// vlcPlayer.events().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
		//
		// @Override
		// public void timeChanged(MediaPlayer mediaPlayer, final long arg1) {
		//
		// }
		//
		// @Override
		// public void lengthChanged(MediaPlayer mediaPlayer, final long arg1) {
		// if (videoComposite.isDisposed()) return;
		//
		// if (firstLengthEvent) {
		// firstLengthEvent = false;
		//
		// // initialize time range widget limits
		// timeRange.getDisplay().syncExec(new Runnable() {
		//
		// @Override
		// public void run() {
		// if (timeRange.isDisposed()) return;
		// timeRange.setMaximum((int) arg1);
		// // if (start == end) end = (int)arg1;
		//
		// if (end > 0 && start != end) {
		// timeRange.setUpperValue(end);
		// }
		// else {
		// timeRange.setUpperValue((int) arg1);
		// }
		//
		// timeRange.setLowerValue(start);
		// // System.out.println("Range: "+start+" -> "+end+" song length "+arg1);
		// }
		// });
		// }
		// }
		// });
	}
	
	protected void resizeView() {
		if (view != null) {
			Point p = fxCanvas.getSize();
			// if (previousP == null || (p.x != previousP.x && p.y != previousP.y)) { // ensure size changed
			view.setFitHeight(p.y);
			view.setFitWidth(p.x);
			previousP = p;
			// }
		}
	}
	
	public void resume() {
		if (jfxPlayer != null) {
			jfxPlayer.play();
			playButton.setText(MessagesMP.pause);
			playButton.getParent().layout();
		}
	}
	
	public void pause() {
		if (jfxPlayer != null) {
			jfxPlayer.pause();
			playButton.setText(MessagesMP.resume);
			playButton.getParent().layout();
		}
	}
	
	private void updateTimeLabel() {
		mins = time / 60;
		secs = (time) % 60;
		timeLabel.setText(String.format("%02d:%02d", mins, secs)); //$NON-NLS-1$
		timeLabel.update();
	}
	
	public void replay() {
		if (currentlyPlayed.length() > 0) {
			jfxPlayer.seek(Duration.seconds(start));
			playButton.setText(MessagesMP.pause);
			playButton.getParent().layout();
		}
	}
	
	protected void selectMedia() {
		Log.fine(MessagesMP.select_file);
		
		FileDialog fd = new FileDialog(JFXPlayer.this.getShell(), SWT.OPEN);
		fd.setText(MessagesMP.select_file_title);
		File f = new File(previouslyPlayed);
		if (f.isDirectory()) fd.setFilterPath(f.getPath());
		else fd.setFilterPath(f.getParent());
		
		String[] filterExt = { "*.*", "*.mp3", "*.mp4", "*.avi", "*.ogg", "*.ogv" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		fd.setFilterExtensions(filterExt);
		String selected = fd.open();
		if (selected == null) {
			System.out.println(MessagesMP.cancel);
			return;
		}
		
		currentlyPlayed = selected;
		previouslyPlayed = selected;
		Log.fine(MessagesMP.opening + currentlyPlayed);
		play(new File(currentlyPlayed).toURI().toString(), 0, 0);
	}
	
	/**
	 * 
	 * @param mrl
	 * @param time msec start time
	 * @param endtime msec end time
	 */
	@Override
	public boolean play(String mrl, int time, int endtime) {
		Log.fine(MessagesMP.bind(MessagesMP.playing, new Object[] { mrl, time, endtime }));
		return play(mrl, "" + time, "" + endtime); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	@Override
	public boolean play(String mrl, int time) {
		return play(mrl, time, time);
	}
	
	public void hideStopButton() {
		if (this.stopButton != null && !this.stopButton.isDisposed()) {
			this.stopButton.dispose();
		}
	}
	
	DateTimeFormatter hhmmssFormatter = DateTimeFormatter.ISO_LOCAL_TIME;
	
	private MediaView view;
	
	private Group group;
	
	private Scene scene;
	
	private String mrl;
	
	private Media media;
	
	@Override
	public final void setCredentials(String login, String mdp) {
		Authenticator.setDefault(new Authenticator() {
			
			@Override
			public PasswordAuthentication getPasswordAuthentication() {
				// System.out.println("PA: login=" + login + " mdp=" + mdp + " mrl=" + mrl + " req=" + getRequestingHost());
				if (login != null && mdp != null && mrl.contains("://" + getRequestingHost() + "/")) {
					return new PasswordAuthentication(login, mdp.toCharArray());
				}
				return null;
			}
		});
	}
	
	/**
	 * 
	 * @param mrl
	 * @param startTime "0.0" or ""hh:mm:ss" format
	 * @param endTime "0.0" or ""hh:mm:ss" format
	 */
	@Override
	public boolean play(String mrl, String startTime, String endTime) {
		
		if (mrl.contains("{0}:{1}@")) {
			mrl = mrl.replace("{0}:{1}@", "");
		}
		
		this.mrl = mrl;
		if (startTime.matches("[0-9]+.[0-9]+")) {
			start = (int) (Float.parseFloat(startTime));
		}
		else if (startTime.matches("[0-9]+:[0-9]+:[0-9]+")) {
			if (startTime.indexOf(":") == 1) {
				startTime = "0" + startTime;
			}
			LocalTime time1 = LocalTime.parse(startTime, hhmmssFormatter);
			start = ((time1.getHour() * 60 * 60) + (time1.getMinute() * 60) + time1.getSecond());
		}
		
		if (endTime.matches("[0-9]+.[0-9]+")) {
			end = (int) (Float.parseFloat(endTime));
		}
		else if (endTime.matches("[0-9]+:[0-9]+:[0-9]+")) {
			if (endTime.indexOf(":") == 1) endTime = "0" + endTime;
			LocalTime time1 = LocalTime.parse(endTime, hhmmssFormatter);
			end = ((time1.getHour() * 60 * 60) + (time1.getMinute() * 60) + time1.getSecond());
		}
		
		// if (start == end) end = -1;
		
		Log.finer(MessagesMP.bind(MessagesMP.playing, new Object[] { mrl, startTime, endTime }));
		// vlcPlayer.submit(new Runnable() {
		//
		// @Override
		// public void run() {
		// String[] options = { " :live-caching=200" }; // reduce video stream cache duration to 200ms
		
		Log.finer("Preparing media...");
		try {
			javafx.application.Platform.runLater(new Runnable() {
				
				@Override
				public void run() {
					Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
						
						@Override
						public void uncaughtException(Thread t, Throwable e) {
							System.out.println("JavaFX uncaught exception: " + e);
						}
					});
				}
			});
			media = new Media(mrl);
			if (jfxPlayer != null) {
				try {
					jfxPlayer.stop();
				}
				catch (Exception e) {
					
				}
			}
			
			Platform.setImplicitExit(false); // This is important to avoid Thread exception when re-opening a player
			jfxPlayer = new MediaPlayer(media);
			
			if (jfxPlayer.getError() == null) {
				jfxPlayer.setOnError(new Runnable() {
					
					@Override
					public void run() {
						System.out.println("JFX Player Error:");
						jfxPlayer.getError().printStackTrace();
					}
				});
			}
			else {
				System.out.println("Warning: could not retrieve media error logger");
			}
			
			//initializePlayerListeners();
			jfxPlayer.setAutoPlay(false);
			
			view = new MediaView(jfxPlayer);
			
			HBox buttonSection = new HBox(view);
		    buttonSection.setPadding(new Insets(2));
			group = new Group(buttonSection);
			//group.setAutoSizeChildren(true);
			// scene = new Scene(group, Color.rgb(fxCanvas.getBackground().getRed(), fxCanvas.getBackground().getGreen(), fxCanvas.getBackground().getBlue()));
			scene = new Scene(group, Color.rgb(0, 0, 0));
			
			fxCanvas.setScene(scene);
			
			//view.setPreserveRatio(true);
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		
		jfxPlayer.setOnReady(new Runnable() {
			
			@Override
			public void run() {
				
				timeRange.setMinimum(0);
				Duration d = jfxPlayer.getMedia().getDuration();
				if (d.toSeconds() > 0) {
					timeRange.setMaximum((int) d.toSeconds());
				}
				timeRange.setLowerValue(start);
				timeRange.setMiddleValue(start);
				
				jfxPlayer.setVolume(volume);
				
				jfxPlayer.setStartTime(Duration.seconds(start));
				//jfxPlayer.seek(Duration.seconds(start));
				if (end > start) {
					timeRange.setUpperValue(end);
					jfxPlayer.setStopTime(Duration.seconds(end));
				}
				else if (d.toSeconds() > 0) {
					timeRange.setUpperValue((int) jfxPlayer.getMedia().getDuration().toSeconds());
				}
				Log.info("Playing media: "+media.getSource());
				
				jfxPlayer.play();
				//resizeView();
				
				// if (new File(mrl + ".srt").exists()) { //$NON-NLS-1$
				// vlcPlayer.sub.subpictures().setSubTitleFile(mrl + ".srt"); //$NON-NLS-1$
				// }
				currentlyPlayed = JFXPlayer.this.mrl;
				previouslyPlayed = JFXPlayer.this.mrl;
				
				if (!playButton.isDisposed()) {
					playButton.setText(MessagesMP.pause);
					playButton.getParent().layout(true);
				}
				firstLengthEvent = true;
			}
		});
		
		return true;
	}
	
	private void initializePlayerListeners() {
		
		jfxPlayer.currentTimeProperty().addListener(new InvalidationListener() {
			
			@Override
			public void invalidated(Observable ov) {
				if (fxCanvas.isDisposed()) return;
				if (jfxPlayer == null) return;
				
				Duration currentTime = jfxPlayer.getCurrentTime();
				time = (int) currentTime.toSeconds();
				if (previous == time) {
					return;
				}
				previous = time;
				
				timeLabel.getDisplay().syncExec(new Runnable() {
					
					@Override
					public void run() {
						if (timeRange.isDisposed()) {
							return;
						}
						if (!timeRange.isDragMiddleKnob()) {
							timeRange.setMiddleValue((int) time);
						}
						updateTimeLabel();
						
						// if (time >= end && end != start) {
						// if (repeat) {
						// vlcPlayer.seek(Duration.seconds(start));
						// }
						// else {
						// vlcPlayer.stop();
						// }
						// }
					}
				});
			}
		});
	}
	
	@Override
	public void setRepeat(boolean repeat) {
		this.repeat = repeat;
		if (this.repeat) {
			jfxPlayer.setCycleCount(Integer.MAX_VALUE);
		}
		else {
			jfxPlayer.setCycleCount(0);
		}
		if (!repeatButton.isDisposed()) {
			repeatButton.setSelection(repeat);
		}
	}
	
	@Override
	public void stop() {
		if (jfxPlayer == null) return; // nothing to do
		
		
		userStopped = true;
		jfxPlayer.stop();
		
		jfxPlayer.dispose();
		jfxPlayer = null;
		if (!fxCanvas.isDisposed()) {
			fxCanvas.setScene(null);
		}
		currentlyPlayed = NOMEDIA;
		if (!playButton.isDisposed()) {
			playButton.setText(MessagesMP.play);
			playButton.getParent().layout();
		}
	}
	
	@Override
	public boolean isMediaLoaded() {
		return jfxPlayer != null && jfxPlayer.getStatus().equals(Status.READY);
	}

	@Override
	public void seek(float time) {
		
		this.jfxPlayer.seek(Duration.seconds(time));
	}

	@Override
	public void setVolume(double volume) {
		
		this.jfxPlayer.setVolume(volume);
	}

	@Override
	public void setRate(float rate) {
		
		this.jfxPlayer.setRate(rate);
	}

	@Override
	public boolean isPlaying() {
		
		return jfxPlayer.getStatus().equals(Status.PLAYING);
	}

	@Override
	public void setStartTime(float seconds) {
		
		this.jfxPlayer.setStartTime(Duration.seconds(seconds));
		
	}

	@Override
	public void setStopTime(float seconds) {
		
		this.jfxPlayer.setStopTime(Duration.seconds(seconds));
		
	}

}
