/*******************************************************************************
 * Copyright (c) 2015 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:
 *   Bernd Hufmann - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.ctf.core.trace;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.ctf.core.CTFException;
import org.eclipse.tracecompass.ctf.core.CTFStrings;
import org.eclipse.tracecompass.internal.ctf.core.trace.StreamInputPacketIndex;

/**
 * A CTF Stream output writer. Reads the packets of a given CTFStreamInput and
 * writes packets that are within a given time range to output stream file.
 *
 * @author Bernd Hufmann
 * @since 1.0
 */
public class CTFStreamOutputWriter {

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------
    // Stream input when copying stream from an input
    // It is @Nullable for future implementations that doesn't use an input
    // stream
    @Nullable
    private final CTFStreamInput fStreamInput;
    @NonNull
    private final CTFStreamPacketOutputWriter fStreamPacketOutputWriter;
    @NonNull
    private final File fOutFile;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Constructs a StreamInput.
     *
     * @param streamInput
     *                        The stream to which this StreamInput belongs to.
     * @param file
     *                        The output trace directory.
     * @throws CTFException
     *                          If a reading or writing error occurs
     */
    public CTFStreamOutputWriter(@NonNull CTFStreamInput streamInput, @NonNull File file) throws CTFException {
        fStreamInput = streamInput;
        String inFileName = streamInput.getFile().getName();
        Path outFilePath = FileSystems.getDefault().getPath(file.getAbsolutePath(), inFileName);

        try {
            fOutFile = checkNotNull(Files.createFile(outFilePath).toFile());
        } catch (IOException e) {
            throw new CTFIOException("Output file can't be created: " + outFilePath, e); //$NON-NLS-1$
        }

        fStreamPacketOutputWriter = new CTFStreamPacketOutputWriter(streamInput);
    }

    /**
     * Copies packets from the relevant input this input stream to a corresponding
     * output stream based on a given time range. The following condition has to be
     * met so that a packet is written to the output stream:
     *
     * startTime <= packet.getTimestampEnd() && packet.getTimestampStart() <=
     * endTime
     *
     * @param startTime
     *                      the start time for packets to be written
     * @param endTime
     *                      the end time for packets to be written
     * @throws CTFException
     *                          if a reading or writing error occurs
     * @since 1.0
     */
    public void copyPackets(long startTime, long endTime) throws CTFException {
        CTFStreamInput streamInput = fStreamInput;
        if (streamInput == null) {
            throw new CTFIOException("StreamInput is null. Can't copy packets"); //$NON-NLS-1$
        }

        try (FileChannel fc = checkNotNull(FileChannel.open(fOutFile.toPath(), StandardOpenOption.WRITE));
                FileChannel source = FileChannel.open(streamInput.getFile().toPath(), StandardOpenOption.READ);) {
            StreamInputPacketIndex index = streamInput.getIndex();
            int count = 0;
            long initialLost = 0;
            for (int i = 0; i < index.size(); i++) {
                ICTFPacketDescriptor entry = index.getElement(i);
                /*
                 * Entire packet is contained
                 */
                long packetStart = entry.getTimestampBegin();
                long packetEnd = entry.getTimestampEnd();
                if (count == 0) {
                    initialLost = (long) entry.getAttributes().getOrDefault(CTFStrings.EVENTS_DISCARDED, 0L);
                }
                if (startTime <= packetStart && endTime >= packetEnd) {
                    // MUCH faster
                    fStreamPacketOutputWriter.writePacket(entry, fc, initialLost);
                    count++;
                } else if (startTime <= packetEnd && endTime >= packetStart) {
                    fStreamPacketOutputWriter.writePacket(entry, startTime, endTime, initialLost, fc);
                    count++;
                } else if (entry.getTimestampBegin() > endTime) {
                    break;
                }
            }

            if (count == 0 && fOutFile.exists() || fOutFile.length() == 0) {
                boolean deleteResult = fOutFile.delete();
                if (!deleteResult) {
                    throw new CTFIOException("Could not delete " + fOutFile.getAbsolutePath()); //$NON-NLS-1$
                }
            }
        } catch (IOException e) {
            throw new CTFIOException("Error copying packets: " + e.toString(), e); //$NON-NLS-1$
        }
    }

    /**
     * Get the stream file to write.
     *
     * @return the stream file to write
     */
    public File getOutFile() {
        return fOutFile;
    }

}
