package bndtools.editor.pages;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.bndtools.api.BndtoolsConstants;
import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.build.api.BuildErrorDetailsHandler;
import org.bndtools.build.api.BuildErrorDetailsHandlers;
import org.bndtools.core.ui.ExtendedFormEditor;
import org.bndtools.core.ui.IFormPageFactory;
import org.bndtools.core.ui.icons.Icons;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.IMessageManager;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.texteditor.ITextEditor;

import aQute.bnd.build.model.BndEditModel;
import bndtools.editor.BndEditor;
import bndtools.editor.common.IPriority;
import bndtools.editor.common.MDSashForm;
import bndtools.editor.project.BuildOperationsPart;
import bndtools.editor.project.BuildPathPart;
import bndtools.editor.project.SubBundlesPart;
import bndtools.utils.MessageHyperlinkAdapter;

public class ProjectBuildPage extends FormPage implements IPriority, IResourceChangeListener {
	private static final ILogger			logger				= Logger.getLogger(ProjectBuildPage.class);

	private final BndEditModel				model;
	private static final Image				imgError			= Icons.image(ISharedImages.IMG_OBJS_ERROR_TSK);
	private static final Image				imgWarning			= Icons.image(ISharedImages.IMG_OBJS_WARN_TSK);
	private static final ImageDescriptor	imgErrorOverlay		= Icons.desc("icons/error_co.gif");
	private static final ImageDescriptor	imgWarningOverlay	= Icons.desc("icons/warning_co.gif");

	private final Map<String, Integer>		messageSeverityMap	= new LinkedHashMap<>();
	private final Map<String, IAction[]>	messageFixesMap		= new HashMap<>();
	private int								problemSeverity		= 0;

	private Image							pageImage			= null;
	private final ExtendedFormEditor		editor;

	public static final IFormPageFactory	FACTORY				= new IFormPageFactory() {
																	@Override
																	public IFormPage createPage(
																		ExtendedFormEditor editor, BndEditModel model,
																		String id) throws IllegalArgumentException {
																		return new ProjectBuildPage(editor, model, id,
																			"Build");
																	}

																	@Override
																	public boolean supportsMode(Mode mode) {
																		return mode == Mode.project;
																	}
																};

	private ProjectBuildPage(ExtendedFormEditor editor, BndEditModel model, String id, String title) {
		super(editor, id, title);
		this.editor = editor;
		this.model = model;
	}

	@Override
	protected void createFormContent(IManagedForm managedForm) {
		managedForm.setInput(model);

		FormToolkit tk = managedForm.getToolkit();
		ScrolledForm form = managedForm.getForm();
		form.setText("Project Build");
		tk.decorateFormHeading(form.getForm());
		form.getForm()
			.addMessageHyperlinkListener(new MessageHyperlinkAdapter(getEditor()));

		GridLayout layout;
		GridData gd;

		// Create Controls
		Composite body = form.getBody();
		body.setLayout(new FillLayout());

		MDSashForm sashForm = new MDSashForm(body, SWT.HORIZONTAL, managedForm);
		sashForm.setSashWidth(6);
		tk.adapt(sashForm, false, false);
		sashForm.hookResizeListener();

		Composite leftPanel = tk.createComposite(sashForm);
		gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		leftPanel.setLayoutData(gd);

		layout = new GridLayout(1, false);
		leftPanel.setLayout(layout);

		SubBundlesPart subBundlesPart = new SubBundlesPart(leftPanel, tk,
			ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED | Section.DESCRIPTION);
		managedForm.addPart(subBundlesPart);
		gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		subBundlesPart.getSection()
			.setLayoutData(gd);

		BuildPathPart buildPathPart = new BuildPathPart(leftPanel, tk,
			ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED | Section.DESCRIPTION);
		managedForm.addPart(buildPathPart);
		gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		gd.widthHint = 50;
		gd.heightHint = 50;
		buildPathPart.getSection()
			.setLayoutData(gd);

		Composite rightPanel = tk.createComposite(sashForm);
		gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		rightPanel.setLayoutData(gd);

		BuildOperationsPart buildOpsPart = new BuildOperationsPart(rightPanel, tk,
			ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED | Section.DESCRIPTION);
		managedForm.addPart(buildOpsPart);

		gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		buildOpsPart.getSection()
			.setLayoutData(gd);

		layout = new GridLayout(1, false);
		rightPanel.setLayout(layout);

		reportProblemsInHeader();
	}

	@Override
	public void init(IEditorSite site, IEditorInput input) {
		super.init(site, input);

		loadProblems();
	}

	void loadProblems() {
		IResource resource = ResourceUtil.getResource(getEditorInput());
		problemSeverity = 0;
		messageSeverityMap.clear();

		if (resource != null) {
			try {
				IMarker[] markers;

				markers = resource.findMarkers(BndtoolsConstants.MARKER_BND_PROBLEM, true, 0);
				loadMarkers(markers);

				markers = resource.findMarkers(BndtoolsConstants.MARKER_BND_PATH_PROBLEM, true, 0);
				loadMarkers(markers);

				markers = resource.findMarkers(BndtoolsConstants.MARKER_BND_MISSING_WORKSPACE, true, 0);
				loadMarkers(markers);
			} catch (CoreException e) {
				logger.logError("Error retrieving problem markers", e);
			}
		}

		ImageDescriptor editorImgOverlay;
		if (problemSeverity >= IMarker.SEVERITY_ERROR) {
			pageImage = imgError;
			editorImgOverlay = imgErrorOverlay;
		} else if (problemSeverity >= IMarker.SEVERITY_WARNING) {
			pageImage = imgWarning;
			editorImgOverlay = imgWarningOverlay;
		} else {
			pageImage = null;
			editorImgOverlay = null;
		}
		editor.updatePageTitle(this);
		editor.setOverlayTitleImage(editorImgOverlay);
	}

	private void loadMarkers(IMarker[] markers) {
		for (IMarker marker : markers) {
			int severity = marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
			String message = marker.getAttribute(IMarker.MESSAGE, "");

			messageSeverityMap.put(message, severity);

			generateFixes(message, marker);

			problemSeverity = Math.max(problemSeverity, severity);
		}
	}

	// look for available quick-fixes from the bnd build error details
	private void generateFixes(String message, final IMarker marker) {
		boolean fixable = marker.getAttribute(BuildErrorDetailsHandler.PROP_HAS_RESOLUTIONS, false);
		if (!fixable)
			return;

		ITextEditor sourcePage = editor.getSourcePage();
		if (sourcePage == null)
			return;

		String type = marker.getAttribute("$bndType", (String) null);
		if (type == null)
			return;

		BuildErrorDetailsHandler handler = BuildErrorDetailsHandlers.INSTANCE.findHandler(type);
		if (handler == null)
			return;

		List<IAction> fixes = new LinkedList<>();
		List<ICompletionProposal> proposals = handler.getProposals(marker);
		if (proposals != null) {
			for (ICompletionProposal proposal : proposals) {
				fixes.add(
					new ApplyCompletionProposalAction(proposal, editor.getSourcePage(), editor, BndEditor.SOURCE_PAGE));
			}
		}

		// If no source completion fixes were found, add the marker resolution
		// actions.
		if (fixes.isEmpty()) {
			List<IMarkerResolution> resolutions = handler.getResolutions(marker);
			for (final IMarkerResolution resolution : resolutions) {
				Action action = new Action(resolution.getLabel()) {
					@Override
					public void run() {
						resolution.run(marker);
					}
				};
				fixes.add(action);
			}
		}

		messageFixesMap.put(message, fixes.toArray(new IAction[0]));
	}

	void reportProblemsInHeader() {
		IManagedForm mform = getManagedForm();
		if (mform == null)
			return;

		if (mform.getForm()
			.isDisposed())
			// We were called asynchronously after dialog was closed
			return;

		IMessageManager manager = mform.getMessageManager();
		manager.removeMessages();

		for (Entry<String, Integer> entry : messageSeverityMap.entrySet()) {
			// severities in IMessageProvider are 1 higher than in IMarker
			int mappedSeverity = entry.getValue() + 1;

			IAction[] fixes = messageFixesMap.get(entry.getKey()); // may be
																	// null

			manager.addMessage(entry.getKey(), entry.getKey(), fixes, mappedSeverity);
		}
	}

	@Override
	public int getPriority() {
		if (problemSeverity >= IMarker.SEVERITY_ERROR)
			return 10;
		if (problemSeverity >= IMarker.SEVERITY_WARNING)
			return 5;
		return 0;
	}

	@Override
	public Image getTitleImage() {
		return pageImage;
	}

	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResource myResource = ResourceUtil.getResource(getEditorInput());

		IResourceDelta delta = event.getDelta();
		if (delta == null)
			return;
		IPath fullPath = myResource.getFullPath();
		delta = delta.findMember(fullPath);
		if (delta == null)
			return;

		if ((delta.getKind() & IResourceDelta.CHANGED) != 0 && (delta.getFlags() & IResourceDelta.MARKERS) != 0) {
			getEditorSite().getShell()
				.getDisplay()
				.asyncExec(() -> {
					loadProblems();
					reportProblemsInHeader();
				});
		}
	}

}
