package org.abapgit.adt.ui.internal.wizards;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.abapgit.adt.backend.ApackServiceFactory;
import org.abapgit.adt.backend.IApackGitManifestService;
import org.abapgit.adt.backend.IApackManifest;
import org.abapgit.adt.backend.IApackManifest.IApackDependency;
import org.abapgit.adt.backend.model.abapgitexternalrepo.AccessMode;
import org.abapgit.adt.backend.model.abapgitexternalrepo.IBranch;
import org.abapgit.adt.ui.internal.i18n.Messages;
import org.abapgit.adt.ui.internal.wizards.AbapGitWizard.CloneData;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

import com.sap.adt.tools.core.model.adtcore.IAdtObjectReference;
import com.sap.adt.tools.core.ui.packages.AdtPackageProposalProviderFactory;
import com.sap.adt.tools.core.ui.packages.AdtPackageServiceUIFactory;
import com.sap.adt.tools.core.ui.packages.IAdtPackageProposalProvider;
import com.sap.adt.tools.core.ui.packages.IAdtPackageServiceUI;
import com.sap.adt.util.ui.swt.AdtSWTUtilFactory;

public class AbapGitWizardPageBranchAndPackage extends WizardPage {

	private static final String PAGE_NAME = AbapGitWizardPageRepositoryAndCredentials.class.getName();

	private final IProject project;
	private final String destination;
	private final CloneData cloneData;
	private final String pullBranch;

	private Button checkbox_lnp;
	private Boolean chboxLinkAndPull;
	private TextViewer txtPackage;
	private ComboViewer comboBranches;

	private final Boolean pullAction;
	private boolean backButtonEnabled = true;
	private final ApackParameters lastApackCall;

	public AbapGitWizardPageBranchAndPackage(IProject project, String destination, CloneData cloneData, Boolean pullAction) {
		super(PAGE_NAME);
		this.project = project;
		this.destination = destination;
		this.cloneData = cloneData;
		this.pullBranch = cloneData.branch;
		this.pullAction = pullAction;
		this.lastApackCall = new ApackParameters();
		this.chboxLinkAndPull = false;


		setTitle(Messages.AbapGitWizardPageBranchAndPackage_title);
		setDescription(Messages.AbapGitWizardPageBranchAndPackage_description);
	}

	@Override
	public void createControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout(3, false);
		container.setLayout(layout);

		/////// BRANCH INPUT
		Label lblBranch = new Label(container, SWT.NONE);
		lblBranch.setText(Messages.AbapGitWizardPageBranchAndPackage_label_branch);
		AdtSWTUtilFactory.getOrCreateSWTUtil().setMandatory(lblBranch, true);
		GridDataFactory.swtDefaults().applyTo(lblBranch);

		this.comboBranches = new ComboViewer(container, SWT.BORDER);
		GridDataFactory.swtDefaults().span(2, 0).align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(this.comboBranches.getControl());
		this.comboBranches.setContentProvider(ArrayContentProvider.getInstance());
		this.comboBranches.setLabelProvider(new LabelProvider() {

			@Override
			public String getText(Object element) {
				if (element instanceof IBranch) {
					return ((IBranch) element).getName();
				}
				return super.getText(element);
			}

		});
		this.comboBranches.getCombo().addModifyListener(event -> {
			this.cloneData.branch = this.comboBranches.getCombo().getText();
			setPageComplete(true);
			if (!validateClientOnly()) {
				setPageComplete(false);
			}
			fetchApackManifest();
		});

		/////// Package INPUT
		Label lblPackage = new Label(container, SWT.NONE);
		lblPackage.setText(Messages.AbapGitWizardPageBranchAndPackage_label_package);
		AdtSWTUtilFactory.getOrCreateSWTUtil().setMandatory(lblPackage, true);
		GridDataFactory.swtDefaults().applyTo(lblPackage);

		this.txtPackage = new TextViewer(container, SWT.SINGLE | SWT.BORDER);
		GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(this.txtPackage.getTextWidget());
		this.txtPackage.getTextWidget().setText(""); //$NON-NLS-1$

		this.txtPackage.getTextWidget().addModifyListener(event -> {
			this.cloneData.packageRef = null;
			setPageComplete(true);
			if (!validateClientOnly()) {
				setPageComplete(false);
			}
		});

		IAdtPackageProposalProvider packageProposalProvider = AdtPackageProposalProviderFactory
				.createPackageProposalProvider(this.txtPackage);
		packageProposalProvider.setProject(this.project);

		Button btnPackage = new Button(container, SWT.PUSH);
		GridDataFactory.swtDefaults().applyTo(btnPackage);
		btnPackage.setText(Messages.AbapGitWizardPageBranchAndPackage_btn_browse);

		btnPackage.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				IAdtPackageServiceUI packageServiceUI = AdtPackageServiceUIFactory.getOrCreateAdtPackageServiceUI();
				IAdtObjectReference[] selectedPackages = packageServiceUI.openPackageSelectionDialog(e.display.getActiveShell(), false,
						AbapGitWizardPageBranchAndPackage.this.destination,
						AbapGitWizardPageBranchAndPackage.this.txtPackage.getTextWidget().getText());
				if (selectedPackages != null && selectedPackages.length > 0) {
					AbapGitWizardPageBranchAndPackage.this.txtPackage.getTextWidget().setText(selectedPackages[0].getName());
					AbapGitWizardPageBranchAndPackage.this.cloneData.packageRef = selectedPackages[0];
				}
			}
		});

		//-> Show checkbox only in link wizard
		if (!this.pullAction) {
			/////// CHECKBOX Link & Pull
			Label lblLnp = new Label(container, SWT.NONE);
			lblLnp.setText(Messages.AbapGitWizardPageBranchAndPackage_chbox_activate);
			lblLnp.setToolTipText(Messages.AbapGitWizardPageBranchAndPackage_chbox_activate_tooltip);
			GridDataFactory.swtDefaults().applyTo(lblLnp);

			this.checkbox_lnp = new Button(container, SWT.CHECK);
			GridDataFactory.swtDefaults().applyTo(this.checkbox_lnp);

			this.checkbox_lnp.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent event) {
					Button chbox = (Button) event.getSource();
					setLnpSequence(chbox.getSelection());
					getContainer().updateButtons();
				}
			});
		}

		setControl(container);

		if (this.cloneData.url != null) {

			this.comboBranches.getCombo().setEnabled(false);
			this.txtPackage.getTextWidget().setText(this.cloneData.packageRef.getName());
			AdtSWTUtilFactory.getOrCreateSWTUtil().setEditable(this.txtPackage.getControl(), false);

			btnPackage.setEnabled(false);

			//-> Disable back navigation if repo is public and we're in pull wizard
			if (this.cloneData.externalRepoInfo != null && this.cloneData.externalRepoInfo.getAccessMode() == AccessMode.PUBLIC) {
				setBackButtonEnabled(false);
			}
		}

		setPageComplete(true);
		if (!validateClientOnly()) {
			setPageComplete(false);
		}
	}

	private void setLnpSequence(boolean chboxValue) {
		this.chboxLinkAndPull = chboxValue;
	}

	public boolean getLnpSequence() {
		return this.chboxLinkAndPull;
	}

	public void setBackButtonEnabled(boolean enabled) {
		this.backButtonEnabled = enabled;
		getContainer().updateButtons();
	}

	@Override
	public IWizardPage getPreviousPage() {
		if (!this.backButtonEnabled) {
			return null;
		}
		return super.getPreviousPage();
	}

	private boolean validateClientOnly() {
		setMessage(null);

		if (this.comboBranches.getCombo().getText().isEmpty()) {
			setMessage(Messages.AbapGitWizardPageBranchAndPackage_combobox_branch_message, DialogPage.INFORMATION);
			return false;
		}

		if (this.txtPackage.getTextWidget().getText().isEmpty()) {
			setMessage(Messages.AbapGitWizardPageBranchAndPackage_text_package_message, DialogPage.INFORMATION);
			return false;
		}
		return true;
	}

	public boolean validateAll() {
		setPageComplete(true);
		if (!validateClientOnly()) {
			setPageComplete(false);
			return false;
		}
		if (this.cloneData.packageRef == null) {
			try {
				String packageName = this.txtPackage.getTextWidget().getText();
				getContainer().run(true, true, new IRunnableWithProgress() {

					@Override
					public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
						monitor.beginTask(Messages.AbapGitWizardPageBranchAndPackage_task_package_validation_message,
								IProgressMonitor.UNKNOWN);
						IAdtPackageServiceUI packageServiceUI = AdtPackageServiceUIFactory.getOrCreateAdtPackageServiceUI();
						if (packageServiceUI.packageExists(AbapGitWizardPageBranchAndPackage.this.destination, packageName, monitor)) {
							List<IAdtObjectReference> packageRefs = packageServiceUI
									.find(AbapGitWizardPageBranchAndPackage.this.destination, packageName, monitor);
							AbapGitWizardPageBranchAndPackage.this.cloneData.packageRef = packageRefs.stream().findFirst().orElse(null);
						}
					}
				});
				if (this.cloneData.packageRef == null) {
					setErrorMessage(Messages.AbapGitWizardPageBranchAndPackage_task_package_validation_error_message);
					setPageComplete(false);
					return false;
				}
			} catch (InvocationTargetException e) {
				setMessage(e.getTargetException().getMessage(), DialogPage.ERROR);
				setPageComplete(false);
				return false;
			} catch (InterruptedException e) {
				return false;
			}
		}
		return true;
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);

		if (visible) {
			this.comboBranches.setInput(null);
			this.comboBranches.setSelection(StructuredSelection.EMPTY);
			if (this.cloneData.externalRepoInfo != null) {
				List<IBranch> branches = this.cloneData.externalRepoInfo.getBranches();
				branches.removeIf(b -> b.getName().equals("HEAD")); //$NON-NLS-1$
				this.comboBranches.setInput(branches);
				if (!branches.isEmpty()) {
					IBranch selectedBranch = branches.stream().filter(b -> b.getIsHead().equals("X")).findFirst() //$NON-NLS-1$
							.orElse(branches.stream().findFirst().get());

					//PULL branch is pre populated
					if (this.pullBranch != null && selectedBranch.getName() != this.pullBranch) {
						selectedBranch = branches.stream().filter(b -> b.getName().equals(this.pullBranch)).findFirst().get();
					}

					this.comboBranches.setSelection(new StructuredSelection(selectedBranch));
				}
			}

			fetchApackManifest();
		}
	}

	private void fetchApackManifest() {
		if (this.cloneData.url.isEmpty() || this.cloneData.branch.isEmpty()) {
			return;
		}
		if (this.cloneData.url.equals(this.lastApackCall.url) && this.cloneData.branch.equals(this.lastApackCall.branch)) {
			return;
		}
		this.cloneData.apackManifest = null;
		try {
			getContainer().run(true, true, new IRunnableWithProgress() {

				@Override
				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {

					final HashMap<String, Boolean> dependencyCoverage = new HashMap<String, Boolean>();
					final List<IApackDependency> retrievedDependencies = new ArrayList<IApackDependency>();

					ApackParameters nextApackCall = new ApackParameters();
					nextApackCall.url = AbapGitWizardPageBranchAndPackage.this.cloneData.url;
					nextApackCall.branch = AbapGitWizardPageBranchAndPackage.this.cloneData.branch;

					AbapGitWizardPageBranchAndPackage.this.cloneData.apackManifest = retrieveApackManifest(monitor, dependencyCoverage,
							retrievedDependencies, nextApackCall);

					AbapGitWizardPageBranchAndPackage.this.lastApackCall.url = AbapGitWizardPageBranchAndPackage.this.cloneData.url;
					AbapGitWizardPageBranchAndPackage.this.lastApackCall.branch = AbapGitWizardPageBranchAndPackage.this.cloneData.branch;
				}

				private IApackManifest retrieveApackManifest(IProgressMonitor monitor, final HashMap<String, Boolean> dependencyCoverage,
						final List<IApackDependency> retrievedDependencies, ApackParameters apackParameters) {

					monitor.beginTask(NLS.bind(Messages.AbapGitWizardPageBranchAndPackage_task_apack_manifest_message,
							AbapGitWizardPageBranchAndPackage.this.cloneData.url), IProgressMonitor.UNKNOWN);
					IApackGitManifestService manifestService = ApackServiceFactory
							.createApackGitManifestService(AbapGitWizardPageBranchAndPackage.this.destination, monitor);
					IApackManifest myManifest = null;
					if (manifestService != null) {
						myManifest = manifestService.getManifest(apackParameters.url, apackParameters.branch,
								AbapGitWizardPageBranchAndPackage.this.cloneData.user,
								AbapGitWizardPageBranchAndPackage.this.cloneData.pass, monitor);
						dependencyCoverage.put(apackParameters.url, true);
						if (myManifest.hasDependencies()) {
							for (IApackDependency dependency : myManifest.getDescriptor().getDependencies()) {
								retrievedDependencies.add(dependency);
								retrieveDependentManifests(ApackParameters.createFromDependency(dependency), dependencyCoverage,
										retrievedDependencies, manifestService, monitor);
							}
							myManifest.getDescriptor().setDependencies(retrievedDependencies);
						}
					}
					return myManifest;

				}

				private void retrieveDependentManifests(ApackParameters apackParameters, final HashMap<String, Boolean> dependencyCoverage,
						final List<IApackDependency> retrievedDependencies, IApackGitManifestService manifestService,
						IProgressMonitor monitor) {
					monitor.beginTask(NLS.bind(Messages.AbapGitWizardPageBranchAndPackage_task_apack_manifest_message, apackParameters.url),
							IProgressMonitor.UNKNOWN);
					IApackManifest myManifest = manifestService.getManifest(apackParameters.url, apackParameters.branch,
							AbapGitWizardPageBranchAndPackage.this.cloneData.user, AbapGitWizardPageBranchAndPackage.this.cloneData.pass,
							monitor);
					dependencyCoverage.put(apackParameters.url, true);
					if (myManifest.hasDependencies()) {
						for (IApackDependency myDependency : myManifest.getDescriptor().getDependencies()) {
							if (!retrievedDependencies.contains(myDependency)) {
								retrievedDependencies.add(myDependency);
							}
							if (!dependencyCoverage.getOrDefault(myDependency.getGitUrl(), false)) {
								retrieveDependentManifests(ApackParameters.createFromDependency(myDependency), dependencyCoverage,
										retrievedDependencies, manifestService, monitor);
							}
						}
					}
				}
			});
			setPageComplete(true);
			setMessage(null);
		} catch (InvocationTargetException e) {
			setPageComplete(false);
			setMessage(e.getTargetException().getMessage(), DialogPage.ERROR);
		} catch (InterruptedException e) {
			// Call was aborted - no dependencies will be retrieved and used in the import
			setPageComplete(true);
		}
	}

	@Override
	public boolean canFlipToNextPage() {

		if (!this.pullAction) {
			if ((getLnpSequence() || this.cloneData.hasDependencies())
					&& validateClientOnly()) {
				return true;
			} else {
				return false;
			}
		}
		return super.canFlipToNextPage();
	}

	public boolean canFinishEarly() {

		if (!this.pullAction) {
			if (!getLnpSequence() && !this.cloneData.hasDependencies()
					&& validateClientOnly()) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}

	}

	private static class ApackParameters {

		public String url;
		public String branch;

		public static ApackParameters createFromDependency(IApackDependency dependency) {
			ApackParameters apackParameters = new ApackParameters();
			apackParameters.url = dependency.getGitUrl();
			apackParameters.branch = IApackManifest.MASTER_BRANCH;
			return apackParameters;
		}

	}

}