/*******************************************************************************
 * Copyright (c) 2013, 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:
 *   Marc-Andre Laperle - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.core.tests.parsers.custom;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;

import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.trace.ITmfEventParser;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpointIndex;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Common test code for custom trace indexes
 *
 * @author Marc-Andre Laperle
 */
public abstract class AbstractCustomTraceIndexTest {

    /**
     * Time format use for event creation
     */
    protected static final String TIMESTAMP_FORMAT = "dd/MM/yyyy HH:mm:ss:SSS";
    /**
     * Block size used for the indexer
     */
    protected static final int BLOCK_SIZE = 100;
    /**
     * The total number of events in the generated trace
     */
    protected static final int NB_EVENTS = 10000;
    private static final long MILLISECOND_TO_NANOSECOND = 1000000;
    private TestTrace fTrace = null;

    /**
     * A common test indexer for custom trace index tests
     */
    protected static class TestIndexer extends TmfBTreeTraceIndexer {

        /**
         * Constructs a new test indexer
         *
         * @param trace the trace
         * @param interval the checkpoint interval
         */
        public TestIndexer(ITmfTrace trace, int interval) {
            super(trace, interval);
        }

        /**
         * Get the index
         *
         * @return the index
         */
        public ITmfCheckpointIndex getCheckpoints() {
            return getTraceIndex();
        }
    }

    interface TestTrace extends ITmfTrace {
        TestIndexer getIndexer();
    }

    /**
     * Setup the test
     *
     * @throws Exception when error occurs
     */
    @Before
    public void setUp() throws Exception {
        setupTrace();
    }

    private synchronized void setupTrace() throws Exception {
        File traceDirectory = new File(getTraceDirectory());
        if (traceDirectory.exists()) {
            traceDirectory.delete();
        }
        traceDirectory.mkdir();
        if (fTrace == null) {
            fTrace = createTrace();
            fTrace.indexTrace(true);
        }
    }

    /**
     * Create a test trace, varies between tests
     *
     * @return the test trace
     * @throws Exception when error occurs
     */
    abstract protected TestTrace createTrace() throws Exception;
    /**
     * Return the trace directory for the generated trace
     *
     * @return the trace directory for the generated trace
     */
    abstract protected String getTraceDirectory();

    /**
     * Tear down the test
     */
    @After
    public void tearDown() {
        String directory = TmfTraceManager.getSupplementaryFileDir(fTrace);
        try {
            fTrace.dispose();
            fTrace = null;
        } finally {
            File dir = new File(directory);
            if (dir.exists()) {
                File[] files = dir.listFiles();
                for (File file : files) {
                    file.delete();
                }
                dir.delete();
            }

            File trace = new File(getTraceDirectory());
            if (trace.exists()) {
                trace.delete();
            }
        }

    }

    /**
     * Test the content of the index after building the full index
     */
    @Test
    public void testTmfTraceIndexing() {
        verifyIndexContent();
    }

    private void verifyIndexContent() {
        long endTime = (NB_EVENTS - 1) * MILLISECOND_TO_NANOSECOND;
        assertEquals("getCacheSize", BLOCK_SIZE, fTrace.getCacheSize());
        assertEquals("getTraceSize", NB_EVENTS, fTrace.getNbEvents());
        assertEquals("getRange-start", 0, fTrace.getTimeRange().getStartTime().getValue());
        assertEquals("getRange-end", endTime, fTrace.getTimeRange().getEndTime().getValue());
        assertEquals("getStartTime", 0, fTrace.getStartTime().getValue());
        assertEquals("getEndTime", endTime, fTrace.getEndTime().getValue());

        ITmfCheckpointIndex checkpoints = fTrace.getIndexer().getCheckpoints();
        int pageSize = fTrace.getCacheSize();
        assertTrue("Checkpoints exist", checkpoints != null);
        assertEquals("Checkpoints size", NB_EVENTS / BLOCK_SIZE, checkpoints.size());

        // Validate that each checkpoint points to the right event
        for (int i = 0; i < checkpoints.size(); i++) {
            ITmfCheckpoint checkpoint = checkpoints.get(i);
            TmfContext context = new TmfContext(checkpoint.getLocation(), i * pageSize);
            ITmfEvent event = ((ITmfEventParser)fTrace).parseEvent(context);
            assertEquals("Rank for checkpoint " + i, i * pageSize, context.getRank());
            assertEquals("Timestamp for checkpoint " + i, checkpoint.getTimestamp(), event.getTimestamp());
        }
    }

    /**
     * Test that a fully built index has the same content when reloaded from disk
     *
     * @throws Exception when error occurs
     */
    @Test
    public void testReopenIndex() throws Exception {
        fTrace.dispose();
        fTrace = createTrace();
        assertFalse(fTrace.getIndexer().getCheckpoints().isCreatedFromScratch());
        fTrace.indexTrace(true);

        verifyIndexContent();
    }
}
