/*******************************************************************************
 * Copyright (c) 2013, 2018 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Marc-Andre Laperle - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.importexport;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.AbstractTracePackageWizardPage;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageBookmarkElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageExperimentElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageFilesElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageLabelProvider;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageSupplFileElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageSupplFilesElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageTraceElement;
import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfCommonProjectElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement;

/**
 * Wizard page for the export trace package wizard
 *
 * @author Marc-Andre Laperle
 */
public class ExportTracePackageWizardPage extends AbstractTracePackageWizardPage {

    private static final int CONTENT_COL_WIDTH = 300;
    private static final int SIZE_COL_WIDTH = 100;

    private static final String ZIP_EXTENSION = ".zip"; //$NON-NLS-1$
    private static final String TAR_EXTENSION = ".tar"; //$NON-NLS-1$
    private static final String TAR_GZ_EXTENSION = ".tar.gz"; //$NON-NLS-1$
    private static final String TGZ_EXTENSION = ".tgz"; //$NON-NLS-1$

    private static final String ICON_PATH = "icons/wizban/export_wiz.png"; //$NON-NLS-1$

    /**
     * The page name, can be referenced from other pages
     */
    public static final String PAGE_NAME = "ExportTracePackageWizardPage"; //$NON-NLS-1$
    // dialog store id constants
    private static final String STORE_COMPRESS_CONTENTS_ID = PAGE_NAME + ".STORE_COMPRESS_CONTENTS_ID"; //$NON-NLS-1$
    private static final String STORE_FORMAT_ID = PAGE_NAME + ".STORE_FORMAT_ID"; //$NON-NLS-1$

    private Button fCompressContentsCheckbox;
    private Button fZipFormatButton;
    private Button fTargzFormatButton;
    private Label fApproximateSizeLabel;
    private List<TmfCommonProjectElement> fSelectedTraces;

    /**
     * Constructor for the export trace package wizard page
     *
     * @param selection
     *            the current object selection
     * @param selectedTraces
     *            the selected traces from the selection
     */
    public ExportTracePackageWizardPage(IStructuredSelection selection, List<TmfCommonProjectElement> selectedTraces) {
        super(PAGE_NAME, Messages.ExportTracePackageWizardPage_Title, Activator.getDefault().getImageDescripterFromPath(ICON_PATH), selection);
        fSelectedTraces = new ArrayList<>(selectedTraces);
    }

    /**
     * Set the selected trace from the previous page to be displayed in the
     * element viewer
     *
     * @param selectedTraces
     *            the selected trace
     */
    public void setSelectedTraces(List<TmfTraceElement> selectedTraces) {
        if (!fSelectedTraces.containsAll(selectedTraces) || !selectedTraces.containsAll(fSelectedTraces)) {
            fSelectedTraces = new ArrayList<>(selectedTraces);
            CheckboxTreeViewer elementViewer = getElementViewer();
            elementViewer.setInput(createElementViewerInput());
            elementViewer.expandToLevel(2);
            setAllChecked(elementViewer, false, true);
            updateApproximateSelectedSize();
        }
    }

    @Override
    public void createControl(Composite parent) {

        initializeDialogUnits(parent);

        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout());
        composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL));

        createElementViewer(composite);
        createButtonsGroup(composite);
        createFilePathGroup(composite, Messages.ExportTracePackageWizardPage_ToArchive, SWT.SAVE);
        createOptionsGroup(composite);

        restoreWidgetValues();
        setMessage(Messages.ExportTracePackageWizardPage_ChooseContent);

        updateApproximateSelectedSize();
        updatePageCompletion();

        setControl(composite);
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible) {
            updatePageCompletion();
        } else {
            setPageComplete(false);
        }
    }

    /**
     * Restore widget values to the values that they held last time this wizard
     * was used to completion.
     */
    @Override
    protected void restoreWidgetValues() {
        super.restoreWidgetValues();

        IDialogSettings settings = getDialogSettings();
        if (settings != null) {
            if (getFilePathCombo().getItemCount() > 0) {
                String item = getFilePathCombo().getItem(0);
                setFilePathValue(item);
            }
            fCompressContentsCheckbox.setSelection(settings.getBoolean(STORE_COMPRESS_CONTENTS_ID));
            fZipFormatButton.setSelection(settings.getBoolean(STORE_FORMAT_ID));
            fTargzFormatButton.setSelection(!settings.getBoolean(STORE_FORMAT_ID));
            updateWithFilePathSelection();
        }
    }

    @Override
    protected void createFilePathGroup(Composite parent, String label, int fileDialogStyle) {
        super.createFilePathGroup(parent, label, fileDialogStyle);

        getFilePathCombo().addModifyListener(e -> updatePageCompletion());
    }

    private void createOptionsGroup(Composite parent) {
        Group optionsGroup = new Group(parent, SWT.NONE);
        optionsGroup.setLayout(new RowLayout(SWT.VERTICAL));
        optionsGroup.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
                | GridData.GRAB_HORIZONTAL));
        optionsGroup.setText(Messages.ExportTracePackageWizardPage_Options);

        SelectionAdapter listener = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                updateWithFilePathSelection();
            }
        };

        fZipFormatButton = new Button(optionsGroup, SWT.RADIO | SWT.LEFT);
        fZipFormatButton.setText(Messages.ExportTracePackageWizardPage_SaveInZipFormat);
        fZipFormatButton.setSelection(true);
        fZipFormatButton.addSelectionListener(listener);

        fTargzFormatButton = new Button(optionsGroup, SWT.RADIO | SWT.LEFT);
        fTargzFormatButton.setText(Messages.ExportTracePackageWizardPage_SaveInTarFormat);
        fTargzFormatButton.setSelection(false);
        fTargzFormatButton.addSelectionListener(listener);

        fCompressContentsCheckbox = new Button(optionsGroup, SWT.CHECK | SWT.LEFT);
        fCompressContentsCheckbox.setText(Messages.ExportTracePackageWizardPage_CompressContents);
        fCompressContentsCheckbox.setSelection(true);
        fCompressContentsCheckbox.addSelectionListener(listener);
    }

    @Override
    protected void createElementViewer(Composite parent) {
        super.createElementViewer(parent);

        CheckboxTreeViewer elementViewer = getElementViewer();
        elementViewer.getTree().setHeaderVisible(true);
        // Content column
        TreeViewerColumn column = new TreeViewerColumn(elementViewer, SWT.NONE);
        column.getColumn().setWidth(CONTENT_COL_WIDTH);
        column.getColumn().setText(Messages.ExportTracePackageWizardPage_ContentColumnName);
        column.setLabelProvider(new TracePackageLabelProvider());

        // Size column
        column = new TreeViewerColumn(elementViewer, SWT.NONE);
        column.getColumn().setWidth(SIZE_COL_WIDTH);
        column.getColumn().setText(Messages.ExportTracePackageWizardPage_SizeColumnName);
        column.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                TracePackageElement tracePackageElement = (TracePackageElement) element;
                long size = tracePackageElement.getSize(false);
                if (size == 0) {
                    return null;
                }
                int level = 0;
                TracePackageElement curElement = tracePackageElement.getParent();
                while (curElement != null) {
                    curElement = curElement.getParent();
                    ++level;
                }

                return indent(getHumanReadable(size), level);
            }

            private String indent(String humanReadable, int level) {
                StringBuilder s = new StringBuilder(humanReadable);
                for (int i = 0; i < level; ++i) {
                    final String indentStr = "  "; //$NON-NLS-1$
                    s.insert(0, indentStr);
                }
                return s.toString();
            }
        });

        elementViewer.setInput(createElementViewerInput());
        elementViewer.expandToLevel(2);
        setAllChecked(elementViewer, false, true);
    }

    @Override
    protected void updateApproximateSelectedSize() {
        long checkedSize = 0;
        TracePackageElement[] tracePackageElements = (TracePackageElement[]) getElementViewer().getInput();
        for (TracePackageElement element : tracePackageElements) {
            checkedSize += element.getSize(true);
        }
        checkedSize = Math.max(0, checkedSize);
        fApproximateSizeLabel.setText(MessageFormat.format(Messages.ExportTracePackageWizardPage_ApproximateSizeLbl, getHumanReadable(checkedSize)));
    }

    /**
     * Get the human readable string for a size in bytes. (KB, MB, etc).
     *
     * @param size
     *            the size to print in human readable,
     * @return the human readable string
     */
    private static String getHumanReadable(long size) {
        String humanSuffix[] = { Messages.ExportTracePackageWizardPage_SizeByte, Messages.ExportTracePackageWizardPage_SizeKilobyte,
                Messages.ExportTracePackageWizardPage_SizeMegabyte, Messages.ExportTracePackageWizardPage_SizeGigabyte,
                Messages.ExportTracePackageWizardPage_SizeTerabyte };
        long curSize = size;

        int suffixIndex = 0;
        while (curSize >= 1024) {
            curSize /= 1024;
            ++suffixIndex;
        }

        return Long.toString(curSize) + " " + humanSuffix[suffixIndex]; //$NON-NLS-1$
    }

    @Override
    protected Object createElementViewerInput() {
        List<TracePackageTraceElement> traceElements = new ArrayList<>();
        for (TmfCommonProjectElement projectElement : fSelectedTraces) {
            TracePackageTraceElement traceElement;
            if (projectElement instanceof TmfExperimentElement) {
                TmfExperimentElement experimentElement = (TmfExperimentElement) projectElement;
                traceElement = new TracePackageExperimentElement(null, experimentElement);
                for (TmfTraceElement expTrace : experimentElement.getTraces()) {
                    ((TracePackageExperimentElement) traceElement).addExpTrace(expTrace.getElementPath());
                }
            } else {
                traceElement = new TracePackageTraceElement(null, projectElement);
            }

            // Trace files
            TracePackageFilesElement filesElement = new TracePackageFilesElement(traceElement, projectElement.getResource());
            filesElement.setChecked(true);

            // Supplementary files
            try {
                String supplementaryFolder = projectElement.getResource().getPersistentProperty(TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER);
                TracePackageSupplFilesElement suppFilesElement = new TracePackageSupplFilesElement(null);
                IResource[] supplementaryResources = projectElement.getSupplementaryResources();
                for (IResource res : supplementaryResources) {
                    String name = supplementaryFolder == null ? res.getName() : res.getLocation().makeRelativeTo(new Path(supplementaryFolder)).toString();
                    new TracePackageSupplFileElement(res, name, suppFilesElement);
                }
                if (supplementaryFolder != null) {
                    // If the supplementary folder path is in a shadow project,
                    // the resources must be taken from the parent project.
                    IFolder projectSupplementaryFolder = projectElement.getProject().getSupplementaryFolder();
                    IPath path = new Path(supplementaryFolder).append(TmfCommonConstants.TRACE_PROPERTIES_FOLDER);
                    IFolder propertiesFolder = projectSupplementaryFolder.getFolder(path.makeRelativeTo(projectSupplementaryFolder.getLocation()));
                    if (propertiesFolder.exists()) {
                        for (IResource res : propertiesFolder.members()) {
                            String name = res.getLocation().makeRelativeTo(new Path(supplementaryFolder)).toString();
                            new TracePackageSupplFileElement(res, name, suppFilesElement);
                        }
                    }
                }
                if (suppFilesElement.getChildren().length > 0) {
                    traceElement.addChild(suppFilesElement);
                }
            } catch (CoreException e) {
                // Should not happen
                Activator.getDefault().logError("Error finding supplementary files", e); //$NON-NLS-1$
            }

            // Bookmarks
            IFile bookmarksFile = projectElement.getBookmarksFile();
            if (bookmarksFile != null && bookmarksFile.exists()) {
                IMarker[] findMarkers;
                try {
                    findMarkers = bookmarksFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO);
                    if (findMarkers.length > 0) {
                        new TracePackageBookmarkElement(traceElement, null);
                    }
                } catch (CoreException e) {
                    // Should not happen since we just checked bookmarksFile.exists() but log it just in case
                    Activator.getDefault().logError("Error finding bookmarks", e); //$NON-NLS-1$
                }
            }

            traceElements.add(traceElement);

        }

        return traceElements.toArray(new TracePackageTraceElement[] {});
    }

    @Override
    protected final Composite createButtonsGroup(Composite parent) {
        Composite buttonGroup = super.createButtonsGroup(parent);

        // Add the label on the same row of the select/deselect all buttons
        fApproximateSizeLabel = new Label(buttonGroup, SWT.RIGHT);
        GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
        layoutData.grabExcessHorizontalSpace = true;
        fApproximateSizeLabel.setLayoutData(layoutData);

        return buttonGroup;
    }

    /**
     * Save widget values to Dialog settings
     */
    @Override
    protected void saveWidgetValues() {
        super.saveWidgetValues();

        IDialogSettings settings = getDialogSettings();
        if (settings != null) {
            settings.put(STORE_COMPRESS_CONTENTS_ID, fCompressContentsCheckbox.getSelection());
            settings.put(STORE_FORMAT_ID, fZipFormatButton.getSelection());
        }
    }

    private String getOutputExtension() {
        if (fZipFormatButton.getSelection()) {
            return ZIP_EXTENSION;
        } else if (fCompressContentsCheckbox.getSelection()) {
            return TAR_GZ_EXTENSION;
        } else {
            return TAR_EXTENSION;
        }
    }

    @Override
    protected void updateWithFilePathSelection() {
        String filePathValue = getFilePathValue();
        if (filePathValue.isEmpty()) {
            return;
        }

        filePathValue = stripKnownExtension(filePathValue);
        filePathValue = filePathValue.concat(getOutputExtension());

        setFilePathValue(filePathValue);
    }

    private static String stripKnownExtension(String str) {
        String ret = str;
        if (str.endsWith(TAR_GZ_EXTENSION)) {
            ret = ret.substring(0, ret.lastIndexOf('.'));
        }

        if (ret.endsWith(ZIP_EXTENSION) || ret.endsWith(TAR_EXTENSION) || ret.endsWith(TGZ_EXTENSION)) {
            ret = ret.substring(0, ret.lastIndexOf('.'));
        }

        return ret;
    }

    /**
     * Finish the wizard page
     *
     * @return true on success
     */
    public boolean finish() {
        if (!checkForOverwrite()) {
            return false;
        }

        saveWidgetValues();

        TracePackageTraceElement[] traceExportElements = (TracePackageTraceElement[]) getElementViewer().getInput();
        boolean useCompression = fCompressContentsCheckbox.getSelection();
        boolean useTar = fTargzFormatButton.getSelection();
        String fileName = getFilePathValue();
        final TracePackageExportOperation exporter = new TracePackageExportOperation(traceExportElements, useCompression, useTar, fileName);

        try {
            getContainer().run(true, true, exporter::run);

            IStatus status = exporter.getStatus();
            if (status.getSeverity() == IStatus.ERROR) {
                handleErrorStatus(status);
            }

        } catch (InvocationTargetException e) {
            handleError(org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.Messages.TracePackage_ErrorOperation, e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return exporter.getStatus().getSeverity() == IStatus.OK;
    }

    private boolean checkForOverwrite() {
        File file = new File(getFilePathValue());
        if (file.exists()) {
            return MessageDialog.openQuestion(getContainer().getShell(), null, Messages.ExportTracePackageWizardPage_AlreadyExitst);
        }
        return true;
    }
}
