/*******************************************************************************
 * Copyright (c) 2013 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.views;

import static org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME;
import static org.jboss.tools.windup.model.domain.WindupConstants.DEFAULT;
import static org.jboss.tools.windup.model.domain.WorkspaceResourceUtils.findProject;

import java.io.File;

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

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.e4.ui.di.Focus;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
import org.eclipse.e4.ui.workbench.modeling.ISelectionListener;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.internal.browser.BrowserViewer;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.jboss.tools.windup.core.IWindupListener;
import org.jboss.tools.windup.core.services.WindupService;
import org.jboss.tools.windup.ui.Preferences;
import org.jboss.tools.windup.ui.WindupUIPlugin;
import org.jboss.tools.windup.ui.internal.Messages;
import org.jboss.tools.windup.ui.internal.Utils;

/**
 * <p>
 * A view to display Windup Reports.
 * </p>
 */
@SuppressWarnings("restriction")
public class WindupReportView implements IShowInTarget
{
	@Inject private MPart part;
	@Inject private WindupService windupService;
	@Inject private ESelectionService selectionService;
	
    /**
     * The ID of the view as specified by the extension.
     */
    public static final String ID = "org.jboss.tools.windup.ui.views.WindupReportView"; //$NON-NLS-1$
    
    private static final String PREVIOUS_REPORT_PREF = "previousReport"; //$NON-NLS-1$

    /**
     * <p>
     * The parent {@link Composite} for this view.
     * </p>
     */
    private Composite composite;

    /**
     * <p>
     * Widget used to display the HTML Windup report.
     * </p>
     */
    private BrowserViewer browserViewer;

    /**
     * <p>
     * Used to display messages to the user in the view.
     * </p>
     */
    private StyledText message;

    /**
     * <p>
     * Listens for selection changes so that the view can be synchronized with the current selection if so specified by the user.
     * </p>
     * 
     * @see #syncronizeViewWithCurrentSelection
     */
    private ISelectionListener selectionChangedListener;

    /**
     * <p>
     * Listener used to listen to changes in Windup reports, such as a new one being generated.
     * </p>
     */
    private IWindupListener reportListener;

    /**
     * <p>
     * <code>true</code> if the view should sync with the current workbench selection, <code>false</code> otherwise.
     * </p>
     */
    private boolean syncronizeViewWithCurrentSelection;

    /**
     * <p>
     * The current resource for which a Windup report is currently being displayed.
     * </p>
     */
    private IResource currentSelection;
    
    private String currentReportPath;

    /**
     * <p>
     * Required default constructor for this view since it is created via extension point.
     * </p>
     */
    public WindupReportView()
    {
        this.browserViewer = null;
        this.selectionChangedListener = null;
        this.currentSelection = null;
        this.syncronizeViewWithCurrentSelection = false;
        this.currentReportPath = "";
    }

    @Inject
    public void createPartControl(Composite composite)
    {
    	this.composite = composite;
        //this.composite = new Composite(parent, SWT.NONE);
//        GridLayout layout = new GridLayout(1, false);
//        this.composite.setLayout(layout);
        GridLayoutFactory.fillDefaults().margins(0, 0).applyTo(composite);;
        GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);

        // create the message label
        this.message = new StyledText(this.composite, SWT.WRAP);
        this.message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        this.message.setVisible(false);

        this.browserViewer = new BrowserViewer(this.composite, BrowserViewer.LOCATION_BAR|BrowserViewer.BUTTON_BAR);
        this.browserViewer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        // react to selection changes
        this.selectionChangedListener = new ISelectionListener() {
			@Override
			public void selectionChanged(MPart part, Object selection) {
				/*
                 * if update on selection and the current selection is not the same as the containing workbench part
                 */
                if (WindupReportView.this.syncronizeViewWithCurrentSelection && part != WindupReportView.this.part)
                {
                    /*
                     * if editor selection else if some other sort of selection
                     */
                    if (part instanceof IEditorPart)
                    {
                        IEditorInput input = ((IEditorPart) part).getEditorInput();
                        if (input instanceof IFileEditorInput)
                        {
                            updateSelection(new StructuredSelection(
                                        ((IFileEditorInput) input).getFile()));
                        }
                    }
                    else
                    {
                    	if (!(selection instanceof ISelection)) {
                    		selection = new StructuredSelection(selection);
                    	}
                    	else {
                    		updateSelection((ISelection)selection);
                    	}
                    }
                }			
			}
		};
		selectionService.addPostSelectionListener(selectionChangedListener);
		
        // react to Windup report generations
        this.reportListener = new IWindupListener()
        {
            @Override
            public void graphGenerated(IProject project)
            {
                /*
                 * if the current selection is in the project that just had a report generated, refresh the view
                 */
                if (WindupReportView.this.currentSelection != null
                            && WindupReportView.this.currentSelection.getProject().equals(project))
                {

                    // refresh has to take place in the display thread
                    Display.getDefault().syncExec(new Runnable()
                    {
                        public void run()
                        {
                            WindupReportView.this.refresh();
                        }
                    });
                }
            }
        };
        windupService.addWindupListener(reportListener);

        // store view preferences
        IPreferenceStore preferenceStore = getPreferenceStore();
        if (preferenceStore.contains(Preferences.REPORTVIEW_SYNC_SELECTION))
        {
            this.syncronizeViewWithCurrentSelection = preferenceStore.getBoolean(Preferences.REPORTVIEW_SYNC_SELECTION);
        }
        else
        {
            preferenceStore.setDefault(Preferences.REPORTVIEW_SYNC_SELECTION, this.syncronizeViewWithCurrentSelection);
        }
        String previousReport = preferenceStore.getString(PREVIOUS_REPORT_PREF);
        boolean showPlaceholder = true;
        if (!previousReport.isEmpty()) {
        	File file = new File(previousReport);
    		if (file.exists()) {
    			showPlaceholder = false;
    			showReport(previousReport, true);
    		}
        }
        if (showPlaceholder) {
        	showMessage("No report available.", true);
        }
    }
    
    public void setSynchronizeSelection(boolean sync) {
    	this.syncronizeViewWithCurrentSelection = sync;
    }

    @Focus
    public void setFocus()
    {
        this.browserViewer.setFocus();
    }

    @PreDestroy
    public void dispose()
    {
        // remove listeners
    	selectionService.removePostSelectionListener(this.selectionChangedListener);
        windupService.removeWindupListener(this.reportListener);
        getPreferenceStore().setValue(PREVIOUS_REPORT_PREF, currentReportPath);
    }
    
    public void updateConfiguration(ILaunchConfiguration configuration) {
		try {
			String projectName = configuration.getAttribute(ATTR_PROJECT_NAME, DEFAULT);
			IProject project = findProject(projectName);
	    	displayReport(project, false);
		} catch (CoreException e) {
			WindupUIPlugin.log(e);
		}
    }

    /**
     * <p>
     * React to a selection change.
     * </p>
     * 
     * @param selection selection change to react to
     * 
     * @return <code>true</code> if the view can react to the given selection, </code>false</code> otherwise
     */
    public boolean updateSelection(ISelection selection)
    {
        boolean canReact = false;

        IResource selectedResource = Utils.getSelectedResource(selection);
        if (selectedResource != null)
        {
            canReact = true;
            this.displayReport(selectedResource, false);
        }

        return canReact;
    }

    /**
     * @see org.eclipse.ui.part.IShowInTarget#show(org.eclipse.ui.part.ShowInContext)
     */
    @Override
    public boolean show(ShowInContext context)
    {
        return this.updateSelection(context.getSelection());
    }

    /**
     * <p>
     * Forces the view to update the report.
     * </p>
     */
    public void refresh()
    {
    	if (currentSelection != null) {
    		this.displayReport(this.currentSelection, true);
    	}
    }

    /**
     * <p>
     * Displays the Windup report for the given {@link IResource}.
     * </p>
     * 
     * @param resource {@link IResource} to display the Windup report for
     * @param force if <code>true</code> then refresh the view even if the the view is already displaying the report for the given resource, if
     *            <code>false</code> and the view is already displaying the report for the given resource then do nothing
     */
    private synchronized void displayReport(IResource resource, boolean force)
    {
        // don't change displayed report if current selection is same as newly selected resource
        if (this.currentSelection != resource || force)
        {
            this.currentSelection = resource;

            if (windupService.reportExists(resource))
            {
                IPath reportPath = windupService.getReportLocation(resource);

                if (reportPath != null)
                {
                    this.showReport(reportPath, true);
                }
                else
                {
                    this.showMessage(Messages.report_has_no_information_on_resource, true);
                }
            }
            else
            {
                this.showMessage(Messages.windup_report_has_not_been_generated, true);
            }
        }
    }

    /**
     * <p>
     * Displays the report in the view and optionally hides the current message.
     * </p>
     * 
     * @param reportPath {@link IPath} to the report to show
     * @param hideMessage <code>true</code> to hide the current message, <code>false</code> to show the message with the report
     */
    public void showReport(IPath reportPath, boolean hideMessage) {
        showReport(reportPath.toString(), hideMessage);
    }
    
    public void showReport(String path, boolean hideMessage) {
    		this.currentReportPath = path;
        this.browserViewer.setURL(path);
        this.browserViewer.setVisible(true);
        ((GridData) this.browserViewer.getLayoutData()).exclude = false;

        this.message.setVisible(!hideMessage);
        ((GridData) this.message.getLayoutData()).exclude = hideMessage;

        this.composite.layout(true);
    }

    /**
     * <p>
     * Displays the given message in the view and optionally hides the report.
     * </p>
     * 
     * @param message text message to display to user in the view
     * @param hideReport <code>true</code> to hide the report, <code>false</code> to show the message with the report
     */
    public void showMessage(String message, boolean hideReport)
    {
        this.message.setText(message);
        this.message.setVisible(true);
        ((GridData) this.message.getLayoutData()).exclude = false;

        this.browserViewer.setVisible(!hideReport);
        if (hideReport) {
        		currentReportPath = "";
        }
        
        ((GridData) this.browserViewer.getLayoutData()).exclude = hideReport;

        this.composite.layout(true);
    }

    /**
     * @return the plugins {@link IPreferenceStore} instance
     */
    private IPreferenceStore getPreferenceStore()
    {
        return WindupUIPlugin.getDefault().getPreferenceStore();
    }
}