/*******************************************************************************
 * Copyright (c) 2013, 2014 École Polytechnique de Montréal
 *
 * 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:
 *   Geneviève Bastien - Initial implementation and API
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.core.synchronization;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.eclipse.tracecompass.internal.tmf.core.Activator;

/**
 * Class to fetch and save synchronization information between traces
 *
 * @author Geneviève Bastien
 */
public class SynchronizationBackend {

    private static final int SYNC_FILE_MAGIC_NUMBER = 0x0DECAF00;

    private static final int FILE_VERSION = 1;

    private static final int HEADER_SIZE = 20;

    private final File fSyncFile;

    /**
     * Constructor
     *
     * @param syncFile
     *            The file containing synchronization information
     * @throws IOException
     *             If the file couldn't be opened for some reason
     */
    public SynchronizationBackend(File syncFile) throws IOException {
        this(syncFile, true);
    }

    /**
     * Constructor with possibility to tell whether to throw errors on exception
     * or not
     *
     * @param syncFile
     *            The file containing synchronization information
     * @param throwErrors
     *            Whether to throw exceptions or not
     * @throws IOException
     *             If the file couldn't be opened for some reason
     */
    public SynchronizationBackend(File syncFile, boolean throwErrors) throws IOException {
        /*
         * Open the file ourselves, get the header information we need, then
         * pass on the descriptor.
         */
        int res;

        fSyncFile = syncFile;

        if (syncFile == null) {
            return;
        }

        if (!syncFile.exists()) {
            if (throwErrors) {
                throw new IOException("Selected synchronization file does not exist"); //$NON-NLS-1$
            }
            return;
        }
        if (syncFile.length() <= 0) {
            if (throwErrors) {
                throw new IOException("Invalid synchronization file selected, " + //$NON-NLS-1$
                        "target file is empty"); //$NON-NLS-1$
            }
            return;
        }

        try (FileInputStream fis = new FileInputStream(syncFile);
                FileChannel fc = fis.getChannel();) {
            ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
            buffer.clear();
            fc.read(buffer);
            buffer.flip();

            /*
             * Check the magic number,to make sure we're opening the right type
             * of file
             */
            res = buffer.getInt();
            if (res != SYNC_FILE_MAGIC_NUMBER) {
                throw new IOException("Selected file does not" + //$NON-NLS-1$
                        "look like a synchronization file"); //$NON-NLS-1$
            }

            res = buffer.getInt(); /* Major version number */
            if (res != FILE_VERSION) {
                throw new IOException("Select synchronization file is of an older " //$NON-NLS-1$
                        + "format. Synchronization will have to be computed again."); //$NON-NLS-1$
            }

            res = buffer.getInt(); /* Minor version number */
        }
    }

    /**
     * Opens an existing synchronization file
     *
     * @return The synchronization algorithm contained in the file
     * @throws IOException
     *             Exception returned file functions
     */
    public SynchronizationAlgorithm openExistingSync() throws IOException {

        if (fSyncFile == null) {
            return null;
        }

        try (/* Set the position after the header */
                FileInputStream fis = new FileInputStream(fSyncFile);
                FileChannel fc = fis.getChannel().position(HEADER_SIZE);
                /* Read the input stream */
                ObjectInputStream ois = new ObjectInputStream(fis);) {

            return (SynchronizationAlgorithm) ois.readObject();
        } catch (ClassNotFoundException e) {
            return null;
        }


    }

    /**
     * Saves the synchronization algorithm object to file
     *
     * @param syncAlgo
     *            The algorithm to save
     * @throws FileNotFoundException
     *             propagate callee's exceptions
     */
    public void saveSync(SynchronizationAlgorithm syncAlgo) throws FileNotFoundException {

        if (fSyncFile == null) {
            return;
        }

        /* Save the header of the file */
        try (FileOutputStream fos = new FileOutputStream(fSyncFile, false);
                FileChannel fc = fos.getChannel();) {

            ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
            buffer.clear();

            fc.position(0);

            buffer.putInt(SYNC_FILE_MAGIC_NUMBER);

            buffer.putInt(FILE_VERSION);

            buffer.flip();
            int res = fc.write(buffer);
            if (res > HEADER_SIZE) {
                throw new IOException("Wrote more data than the header size"); //$NON-NLS-1$
            }
            /* done writing the file header */

            fc.position(HEADER_SIZE);

            try (ObjectOutputStream oos = new ObjectOutputStream(fos);) {
                oos.writeObject(syncAlgo);
            }

        } catch (FileNotFoundException e) {
            /* Send this upwards */
            throw e;
        } catch (IOException e) {
            /* Handle other cases of IOException's */
            Activator.logError("Error saving trace synchronization data", e); //$NON-NLS-1$
        }
        return;

    }

}
