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

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.importtrace.TarFile;

/**
 * An abstract operation containing common code useful for other trace package
 * operations
 *
 * @author Marc-Andre Laperle
 */
public abstract class AbstractTracePackageOperation {
    private IStatus fStatus;
    // Result of this operation, if any
    private TracePackageElement[] fResultElements;

    private final String fFileName;

    /**
     * Constructs a new trace package operation
     *
     * @param fileName
     *            the output file name
     */
    public AbstractTracePackageOperation(String fileName) {
        fFileName = fileName;
    }

    /**
     * Run the operation. The status (result) of the operation can be obtained
     * with {@link #getStatus}
     *
     * @param progressMonitor
     *            the progress monitor to use to display progress and receive
     *            requests for cancellation
     */
    public abstract void run(IProgressMonitor progressMonitor);

    /**
     * Returns the status of the operation (result)
     *
     * @return the status of the operation
     */
    public IStatus getStatus() {
        return fStatus;
    }

    /**
     * Get the resulting elements for this operation, if any
     *
     * @return the resulting elements or null if no result is produced by this
     *         operation
     */
    public TracePackageElement[] getResultElements() {
        return fResultElements;
    }

    /**
     * Set the resulting elements for this operation, if any
     *
     * @param elements
     *            the resulting elements produced by this operation, can be set
     *            to null
     */
    public void setResultElements(TracePackageElement[] elements) {
        fResultElements = elements;
    }

    /**
     * Set the status for this operation
     *
     * @param status
     *            the status
     */
    protected void setStatus(IStatus status) {
        fStatus = status;
    }

    /**
     * Get the file name of the package
     *
     * @return the file name
     */
    protected String getFileName() {
        return fFileName;
    }

    /**
     * Answer a handle to the archive file currently specified as being the
     * source. Return null if this file does not exist or is not of valid
     * format.
     *
     * @return the archive file
     */
    public ArchiveFile getSpecifiedArchiveFile() {
        if (fFileName.length() == 0) {
            return null;
        }

        File file = new File(fFileName);
        if (file.isDirectory()) {
            return null;
        }

        try {
            return new ZipArchiveFile(new ZipFile(file));
        } catch (IOException e) {
            // ignore
        }

        try {
            return new TarArchiveFile(new TarFile(file));
        } catch (IOException e) {
            // ignore
        }

        return null;
    }

    /**
     * Get the number of checked elements in the array and the children
     *
     * @param elements
     *            the elements to check for checked
     * @return the number of checked elements
     */
    protected int getNbCheckedElements(TracePackageElement[] elements) {
        int totalWork = 0;
        for (TracePackageElement tracePackageElement : elements) {
            TracePackageElement[] children = tracePackageElement.getChildren();
            if (children != null && children.length > 0) {
                totalWork += getNbCheckedElements(children);
            } else if (tracePackageElement.isChecked()) {
                ++totalWork;
            }
        }

        return totalWork;
    }

    /**
     * Returns whether or not the Files element is checked under the given trace
     * package element
     *
     * @param tracePackageElement
     *            the trace package element
     * @return whether or not the Files element is checked under the given trace
     *         package element
     */
    public static boolean isFilesChecked(TracePackageElement tracePackageElement) {
        for (TracePackageElement element : tracePackageElement.getChildren()) {
            if (element instanceof TracePackageFilesElement) {
                return element.isChecked();
            }
        }

        return false;
    }

    /**
     * Common interface between ZipArchiveEntry and TarArchiveEntry
     */
    protected interface ArchiveEntry {
        /**
         * The name of the entry
         *
         * @return The name of the entry
         */
        String getName();
    }

    /**
     * Common interface between ZipFile and TarFile
     */
    protected interface ArchiveFile {
        /**
         * Returns an enumeration cataloging the archive.
         *
         * @return enumeration of all files in the archive
         */
        Enumeration<@NonNull ? extends ArchiveEntry> entries();

        /**
         * Close the file input stream.
         *
         * @throws IOException
         */
        void close() throws IOException;

        /**
         * Returns a new InputStream for the given file in the archive.
         *
         * @param entry
         *            the given file
         * @return an input stream for the given file
         * @throws IOException
         */
        InputStream getInputStream(ArchiveEntry entry) throws IOException;
    }

    /**
     * Adapter for TarFile to ArchiveFile
     */
    protected class TarArchiveFile implements ArchiveFile {

        private TarFile fTarFile;

        /**
         * Constructs a TarAchiveFile for a TarFile
         *
         * @param tarFile
         *            the TarFile
         */
        public TarArchiveFile(TarFile tarFile) {
            this.fTarFile = tarFile;
        }

        @Override
        public Enumeration<@NonNull ? extends ArchiveEntry> entries() {
            Vector<@NonNull ArchiveEntry> v = new Vector<>();
            for (Enumeration<?> e = fTarFile.entries(); e.hasMoreElements();) {
                v.add(new TarArchiveEntryAdapter((TarArchiveEntry) e.nextElement()));
            }

            return v.elements();
        }

        @Override
        public void close() throws IOException {
            fTarFile.close();
        }

        @Override
        public InputStream getInputStream(ArchiveEntry entry) throws IOException {
            return fTarFile.getInputStream(((TarArchiveEntryAdapter) entry).getTarEntry());
        }
    }

    /**
     * Adapter for TarArchiveEntry to ArchiveEntry
     */
    protected class TarArchiveEntryAdapter implements ArchiveEntry {
        private TarArchiveEntry fTarEntry;

        /**
         * Constructs a TarArchiveEntry for a TarArchiveEntry
         *
         * @param tarEntry
         *            the TarArchiveEntry
         */
        public TarArchiveEntryAdapter(TarArchiveEntry tarEntry) {
            this.fTarEntry = tarEntry;
        }

        @Override
        public String getName() {
            return fTarEntry.getName();
        }

        /**
         * Get the corresponding TarArchiveEntry
         *
         * @return the corresponding TarArchiveEntry
         */
        public TarArchiveEntry getTarEntry() {
            return fTarEntry;
        }

        @Override
        public String toString() {
            return getName();
        }
    }

    /**
     * Adapter for ArchiveEntry to ArchiveEntry
     */
    protected class ZipAchiveEntryAdapter implements ArchiveEntry {

        private ZipArchiveEntry fZipEntry;

        /**
         * Constructs a ZipAchiveEntryAdapter for a ZipArchiveEntry
         *
         * @param zipEntry
         *            the ZipArchiveEntry
         */
        public ZipAchiveEntryAdapter(ZipArchiveEntry zipEntry) {
            this.fZipEntry = zipEntry;
        }

        @Override
        public String getName() {
            return fZipEntry.getName();
        }

        /**
         * Get the corresponding ZipArchiveEntry
         *
         * @return the corresponding ZipArchiveEntry
         */
        public ZipArchiveEntry getZipEntry() {
            return fZipEntry;
        }

        @Override
        public String toString() {
            return getName();
        }
    }

    /**
     * Adapter for ZipFile to ArchiveFile
     */
    protected class ZipArchiveFile implements ArchiveFile {

        private ZipFile fZipFile;

        /**
         * Constructs a ZipArchiveFile for a ZipFile
         *
         * @param zipFile
         *            the ZipFile
         */
        public ZipArchiveFile(ZipFile zipFile) {
            this.fZipFile = zipFile;
        }

        @Override
        public Enumeration<@NonNull ? extends ArchiveEntry> entries() {
            Vector<@NonNull ArchiveEntry> v = new Vector<>();
            for (Enumeration<ZipArchiveEntry> e = fZipFile.getEntries(); e.hasMoreElements();) {
                v.add(new ZipAchiveEntryAdapter(e.nextElement()));
            }

            return v.elements();
        }

        @Override
        public void close() throws IOException {
            fZipFile.close();
        }

        @Override
        public InputStream getInputStream(ArchiveEntry entry) throws IOException {
            return fZipFile.getInputStream(((ZipAchiveEntryAdapter) entry).getZipEntry());
        }
    }

}
