/*******************************************************************************
 * Copyright (c) 2018 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.windup.ui.internal.issues;

import static org.jboss.tools.windup.model.domain.WindupMarker.EFFORT;
import static org.jboss.tools.windup.model.domain.WindupMarker.RULE_ID;
import static org.jboss.tools.windup.model.domain.WindupMarker.SEVERITY;
import static org.jboss.tools.windup.model.domain.WindupMarker.SOURCE_SNIPPET;
import static org.jboss.tools.windup.model.domain.WindupMarker.TITLE;
import static org.jboss.tools.windup.ui.internal.Messages.noIssueDetails;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.internal.browser.BrowserViewer;
import org.jboss.tools.windup.model.domain.WindupMarker;
import org.jboss.tools.windup.ui.WindupUIPlugin;
import org.jboss.tools.windup.ui.internal.services.MarkerService;
import org.jboss.tools.windup.windup.Hint;
import org.jboss.tools.windup.windup.Issue;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.DocumentType;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.markdown4j.Markdown4jProcessor;
import org.osgi.framework.Bundle;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * View for displaying the details of an Issue.
 */
@SuppressWarnings("restriction")
public class IssueDetailsView {
	
	public static final String ID = "org.jboss.tools.windup.ui.issue.details";
	
	private FormToolkit toolkit;
	private DetailsComposite detailsComposite;
	private Composite placeholder;
	private Composite stack;
	
	@Inject private MarkerService markerService;
	
	private ScrolledForm form;

	@PostConstruct
	private void create(Composite parent, IEclipseContext context) {
		toolkit = new FormToolkit(Display.getDefault());
		form = toolkit.createScrolledForm(parent);
		GridLayoutFactory.fillDefaults().applyTo(form);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
		GridLayoutFactory.fillDefaults().applyTo(form.getBody());
		stack = toolkit.createComposite(form.getBody());
		toolkit.paintBordersFor(stack);
		stack.setLayout(new StackLayout());
		GridDataFactory.fillDefaults().grab(true, true).applyTo(stack);
		IEclipseContext child = context.createChild();
		child.set(FormToolkit.class, toolkit);
		child.set(Composite.class, stack);
		detailsComposite = ContextInjectionFactory.make(DetailsComposite.class, child);
		placeholder = toolkit.createComposite(stack);
		toolkit.createLabel(placeholder, noIssueDetails);
		GridLayoutFactory.fillDefaults().applyTo(placeholder);
		setTop(placeholder);
	}
	
	@PreDestroy
	private void dispose() {
		toolkit.dispose();
	}
	
	@Inject
	public void showIssueDetails(@Optional IMarker marker) {
		if (detailsComposite != null && !detailsComposite.isDisposed()) {
			Composite top = placeholder;
			if (marker != null) {
				Issue issue = markerService.find(marker);
				if (issue != null) {
					detailsComposite.setIssue(marker, issue);
					top = detailsComposite;
				}
			}
			setTop(top);
		}
	}
	
	private void setTop(Control top) {
		((StackLayout)stack.getLayout()).topControl = top;
		stack.layout(true, true);
		form.reflow(true);
	}
	
	public static void addPrism(Document doc) {
		try {
			Bundle bundle = WindupUIPlugin.getDefault().getBundle();
			Elements codeElements = doc.getElementsByTag("code");
			codeElements.forEach(element -> {
				Set<String> classNames = element.classNames();
				Set<String> newNames = Sets.newHashSet();
				classNames.forEach(className -> {
					// prismjs requires prefix, i'm not sure about another/easier workaround.
					newNames.add("language-"+className);
				});
				element.classNames(newNames);
			});
			
			DocumentType type = new DocumentType("html", "", "", "");
			doc.insertChildren(0, Lists.newArrayList(type));
			
			Element head = doc.head();
			Element css = doc.createElement("link");
			
		    URL fileURL = FileLocator.find(bundle, new Path("html/prism.css"), null);
		    String srcPath = FileLocator.resolve(fileURL).getPath();
			
			css.attr("href", srcPath);
			css.attr("rel", "stylesheet");
			head.appendChild(css);
			
			Element body = doc.body();
			Element script = doc.createElement("script");
			
			fileURL = FileLocator.find(bundle, new Path("html/prism.js"), null);
			srcPath = FileLocator.resolve(fileURL).getPath();
			
			script.attr("src", srcPath);
			body.appendChild(script);
		} catch (Exception e) {
			WindupUIPlugin.log(e);
		}
	}
	
	private static class DetailsComposite extends Composite {

		private IMarker marker;
		private BrowserViewer browserViewer;
		
		@Inject
		public DetailsComposite(Composite parent, FormToolkit toolkit) {
			super(parent, SWT.NONE);
			GridLayoutFactory.fillDefaults().applyTo(this);
			GridDataFactory.fillDefaults().grab(true, true).applyTo(this);
			browserViewer = new BrowserViewer(this, BrowserViewer.LOCATION_BAR|BrowserViewer.BUTTON_BAR);
	        browserViewer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		}
		
		public void setIssue(IMarker marker, Issue issue) {
			this.marker = marker;
			refresh(issue);
		}
		
		private void refresh(Issue issue) {
			try {
				String html = buildText(issue);
				File htmlFile = File.createTempFile("tmp", ".html");
				Document doc = Jsoup.parse(html);
				IssueDetailsView.addPrism(doc);
				FileUtils.write(htmlFile, doc.html());
				browserViewer.setURL(htmlFile.getAbsolutePath());;
			} catch (Exception e) {
				WindupUIPlugin.log(e);
			}
		}
		
		private String buildText(Issue issue) {
			StringBuilder builder = new StringBuilder();
			builder.append("<h3>Title</h3>"); //$NON-NLS-1$
			builder.append(marker.getAttribute(TITLE, noIssueDetails));
			String header = issue instanceof Hint ? "<h3>Message</h3>" : "<h3>Description</h3>"; //$NON-NLS-1$ //$NON-NLS-2$
			builder.append(header); //$NON-NLS-1$
			Markdown4jProcessor markdownProcessor = new Markdown4jProcessor();
			try {
				builder.append(markdownProcessor.process(marker.getAttribute(WindupMarker.DESCRIPTION, noIssueDetails)));
			} catch (IOException e) {
				e.printStackTrace();
			}
			builder.append("<h3>Category ID</h3>");
			builder.append(marker.getAttribute(SEVERITY, noIssueDetails));
			builder.append("<h3>Level of Effort</h3>");
			builder.append(marker.getAttribute(EFFORT, noIssueDetails));
			builder.append("<h3>Rule ID</h3>");
			builder.append(marker.getAttribute(RULE_ID, noIssueDetails));
			builder.append("<h3>More Information</h3>");
			if (issue.getLinks().isEmpty()) {
				builder.append(noIssueDetails);
			}
			for (final org.jboss.tools.windup.windup.Link link : issue.getLinks()) {
				builder.append("<p>" + link.getDescription() + "<br/>");
				builder.append("<ul><li>");
				builder.append("<a href=\"" + link.getUrl() + "\"" + ">" + link.getUrl() + "</a>");
				builder.append("</ul></li></p>");
			}
			if (issue instanceof Hint) {
				builder.append("<h3>Source</h3>");
				builder.append(marker.getAttribute(SOURCE_SNIPPET, noIssueDetails));
			}
			return builder.toString();
		}
	}
}
