/*******************************************************************************
 * Copyright (c) 2015, 2019 Red Hat.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat - Initial Contribution
 *******************************************************************************/

package org.eclipse.linuxtools.internal.vagrant.ui.wizards;

import java.nio.file.Paths;
import java.util.Map;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.wizard.WizardPageSupport;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.linuxtools.internal.vagrant.core.EnvironmentsManager;
import org.eclipse.linuxtools.internal.vagrant.ui.SWTImagesFactory;
import org.eclipse.linuxtools.vagrant.core.IVagrantBox;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

public class CreateVMPage extends WizardPage {

	private final CreateVMPageModel model;
	private final DataBindingContext dbc;

	private CreateVMAdvancedComposite advanced;

	public CreateVMPage(IVagrantBox box) {
		super("createVMPage", //$NON-NLS-1$
				WizardMessages.getString("CreateVM.label"), //$NON-NLS-1$
				SWTImagesFactory.DESC_BANNER_REPOSITORY);
		setMessage(WizardMessages.getString("CreateVM.desc")); //$NON-NLS-1$
		this.model = new CreateVMPageModel();
		if (box != null) {
			this.model.setBoxRef(box.getName());
		}
		this.dbc = new DataBindingContext();
	}

	@Override
	public void dispose() {
		dbc.dispose();
		super.dispose();
	}

	public String getVMName() {
		return model.getVFileMode() ? null : this.model.getVMName();
	}

	public String getBoxReference() {
		return model.getVFileMode() ? null : this.model.getBoxRef();
	}

	public String getVMFile() {
		return model.getVFileMode() ? this.model.getVMFile() : null;
	}

	public Map<String, String> getEnvironment() {
		return model.getEnvironment();
	}

	@Override
	public void createControl(Composite parent) {
		ScrolledComposite scrollTop = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
		scrollTop.setExpandVertical(true);
		scrollTop.setExpandHorizontal(true);

		final Composite container = new Composite(scrollTop, SWT.NONE);
		GridLayoutFactory.fillDefaults().numColumns(3).margins(6, 6)
				.applyTo(container);

		// VM Name
		final Label vmNameLabel = new Label(container, SWT.NONE);
		vmNameLabel
				.setText(WizardMessages.getString("CreateVM.name.label")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).applyTo(vmNameLabel);

		final Text vmNameText = new Text(container, SWT.BORDER);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).span(2, 1).applyTo(vmNameText);
		vmNameText.setToolTipText(
				WizardMessages.getString("CreateVMPage.name.tooltip")); //$NON-NLS-1$
		// VM Name binding
		final IObservableValue<String> vmmNameObservable = BeanProperties
				.value(CreateVMPageModel.class, CreateVMPageModel.VM_NAME,
						String.class)
				.observe(model);
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(vmNameText),
				vmmNameObservable);

		// Box name
		final Label boxRefLabel = new Label(container, SWT.NONE);
		boxRefLabel
				.setText(WizardMessages.getString("CreateVMPage.boxRef.label")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).applyTo(boxRefLabel);

		final Text boxRefText = new Text(container, SWT.BORDER);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).applyTo(boxRefText);
		boxRefText.setToolTipText(
				WizardMessages.getString("CreateVMPage.boxRef.tooltip")); //$NON-NLS-1$
		// Box Name binding
		final IObservableValue<String> boxRefObservable = BeanProperties
				.value(CreateVMPageModel.class, CreateVMPageModel.BOX_REF,
						String.class)
				.observe(model);
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(boxRefText),
				boxRefObservable);

		// Box name search
		final Button boxSearchButton = new Button(container, SWT.NONE);
		boxSearchButton
				.setText(WizardMessages.getString("CreateVMPage.search.label")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).applyTo(boxSearchButton);
		boxSearchButton.addSelectionListener(onSearchImage());

		// VM File Checkbox
		final Button customVMFileButton = new Button(container, SWT.CHECK);
		customVMFileButton
				.setText(WizardMessages.getString("CreateVMPage.File.CheckBox")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).span(3, 1).applyTo(customVMFileButton);
		final IObservableValue<String> customVMFileObservable = BeanProperties
				.value(CreateVMPageModel.class, CreateVMPageModel.V_FILE_MODE,
						String.class)
				.observe(model);
		dbc.bindValue(
				WidgetProperties.widgetSelection().observe(customVMFileButton),
				customVMFileObservable);

		// VM File
		final Label boxLocLabel = new Label(container, SWT.NONE);
		boxLocLabel
				.setText(WizardMessages.getString("CreateVMPage.loc.label")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).applyTo(boxLocLabel);

		final Text boxLocText = new Text(container, SWT.BORDER);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).applyTo(boxLocText);
		boxLocText.setToolTipText(
				WizardMessages.getString("CreateVMPage.loc.tooltip")); //$NON-NLS-1$
		boxLocText.setEnabled(false);
		// Location binding
		final IObservableValue<String> boxLocObservable = BeanProperties
				.value(CreateVMPageModel.class, CreateVMPageModel.VM_FILE,
						String.class)
				.observe(model);
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(boxLocText),
				boxLocObservable);
		boxLocText.addModifyListener(e -> vmFileChanged(boxLocText.getText()));

		// Vagrantfile search
		final Button vgFilesearchButton = new Button(container, SWT.NONE);
		vgFilesearchButton
				.setText(WizardMessages.getString("CreateVMPage.search.label")); //$NON-NLS-1$
		vgFilesearchButton.setEnabled(false);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).applyTo(vgFilesearchButton);
		vgFilesearchButton.addSelectionListener(onSearchVMFile());

		customVMFileButton.addSelectionListener(
				onCheckCustomVMFile(vmNameText, boxRefText, boxLocText, vgFilesearchButton, boxSearchButton));

		dbc.addValidationStatusProvider(new CreateVMValidationStatusProvider(vmmNameObservable,
				boxRefObservable, boxLocObservable));

		advanced = new CreateVMAdvancedComposite(
				container, scrollTop, model);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(3, 1)
				.grab(true, false).applyTo(advanced);

		// setup validation support
		WizardPageSupport.create(this, dbc);

		scrollTop.setContent(container);
		Point point = container.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		scrollTop.setSize(point);
		scrollTop.setMinSize(point);
		setControl(container);
	}

	private SelectionListener onCheckCustomVMFile(Text vmNameText,
			Text boxRefText, Text boxLocText, Button searchButton,
			Button boxSearchButton) {
		return SelectionListener.widgetSelectedAdapter(e -> {
			if (e.widget instanceof Button) {
				Button bt = (Button) e.widget;
				if (bt.getSelection()) {
					vmNameText.setEnabled(false);
					boxRefText.setEnabled(false);
					boxSearchButton.setEnabled(false);
					searchButton.setEnabled(true);
					boxLocText.setEnabled(true);
					model.setVFileMode(true);
				} else {
					vmNameText.setEnabled(true);
					boxRefText.setEnabled(true);
					boxSearchButton.setEnabled(true);
					searchButton.setEnabled(false);
					boxLocText.setEnabled(false);
					model.setVFileMode(false);
				}
			}
		});
	}

	private SelectionListener onSearchImage() {
		return SelectionListener.widgetSelectedAdapter(e -> {
			FileDialog fd = new FileDialog(getShell());
			String location = fd.open();
			if (location != null && !location.isEmpty()) {
				model.setBoxRef(location);
			}
		});
	}

	private SelectionListener onSearchVMFile() {
		return SelectionListener.widgetSelectedAdapter(e -> {
			FileDialog fd = new FileDialog(getShell());
			String location = fd.open();
			if (location != null && !location.isEmpty()) {
				model.setVMFile(location);
				vmFileChanged(location);
			}
		});
	}

	private void vmFileChanged(String vagrantFile) {
		IPath folder = new Path(vagrantFile).removeLastSegments(1);
		Map<String, String> existingEnv = EnvironmentsManager.getSingleton()
				.getEnvironment(folder.toFile());
		if (existingEnv != null) {
			model.setEnvironment(existingEnv);
			advanced.refresh();
		}
	}

	private class CreateVMValidationStatusProvider extends MultiValidator {

		private IObservableValue<String> vmNameOb, boxRefOb, boxLocOb;

		public CreateVMValidationStatusProvider(
				IObservableValue<String> vmNameOb,
				IObservableValue<String> boxRefOb,
				IObservableValue<String> boxLocOb) {
			this.vmNameOb = vmNameOb;
			this.boxRefOb = boxRefOb;
			this.boxLocOb = boxLocOb;
		}

		@Override
		public IObservableList<IObservable> getTargets() {
			// Work around for NPE triggered by DialogPageSupport.dispose()
			return new WritableList<>();
		}

		@Override
		protected IStatus validate() {
			String vmName = vmNameOb.getValue();
			String boxRef = boxRefOb.getValue();
			String boxLoc = boxLocOb.getValue();
			if (!model.getVFileMode()) {
				if (vmName == null || vmName.isEmpty()) {
					return ValidationStatus.error(WizardMessages
							.getString("CreateVMPage.emptyVMName")); //$NON-NLS-1$
				} else if (boxRef == null || boxRef.isEmpty()) {
					return ValidationStatus.error(WizardMessages
							.getString("CreateVMPage.emptyBoxRef")); //$NON-NLS-1$
				}
			} else {
				if (boxLoc == null || boxLoc.isEmpty()) {
					return ValidationStatus.error(WizardMessages
							.getString("CreateVMPage.emptyVagrantfile")); //$NON-NLS-1$
				}
				if (!Paths.get(boxLoc).toFile().canRead()) {
					return ValidationStatus.error(WizardMessages
							.getString("CreateVMPage.invalidVagrantfile")); //$NON-NLS-1$
				}
			}
			return ValidationStatus.ok();
		}

	}
}
