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.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.PlatformUI;
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.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.ITextEditor;

import aQute.bnd.build.model.BndEditModel;
import bndtools.Plugin;
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 final Image imgError = PlatformUI.getWorkbench()
        .getSharedImages()
        .getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
    private final Image imgWarning = PlatformUI.getWorkbench()
        .getSharedImages()
        .getImage(ISharedImages.IMG_OBJS_WARN_TSK);

    private final ImageDescriptor imgErrorOverlay = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/error_co.gif");
    private final ImageDescriptor imgWarningOverlay = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/warning_co.gif");

    private final Map<String, Integer> messageSeverityMap = new LinkedHashMap<String, Integer>();
    private final Map<String, IAction[]> messageFixesMap = new HashMap<String, IAction[]>();
    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(new Runnable() {
                    @Override
                    public void run() {
                        loadProblems();
                        reportProblemsInHeader();
                    }
                });
        }
    }

}