/*******************************************************************************
 * Copyright (c) 2015, 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
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ui.swtbot.tests.viewers.events;

import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.apache.log4j.Logger;
import org.apache.log4j.varia.NullAppender;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotUtils;
import org.eclipse.tracecompass.tmf.ui.tests.shared.WaitUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test refreshing a text trace after new content was added.
 */
@RunWith(SWTBotJunit4ClassRunner.class)
public class TestRefreshTextTrace {

    private static final String PROJECT_NAME = "Test";
    private static final String TRACE_TYPE_SYSLOG = "org.eclipse.linuxtools.tmf.tests.stubs.trace.text.testsyslog";
    private static final String TIMESTAMP_FORMAT = "MMM dd HH:mm:ss";

    private static final long INITIAL_NB_EVENTS = 100;
    private static final int NEW_EVENTS_PER_REFRESH = 2000;
    private static final int NB_REFRESH = 3;
    private static final long SECOND_TO_MILLISECOND = 1000;
    private static final long MICROSECOND_TO_NANOSECOND = 1000000;
    private static final int INDEXING_TIMEOUT = 300000;

    private static final Calendar CURRENT = Calendar.getInstance();
    private static final String TRACE_LOCATION = TmfTraceManager.getTemporaryDirPath() + File.separator + "test.txt";
    private static SWTWorkbenchBot fBot;

    /** The Log4j logger instance. */
    private static final Logger fLogger = Logger.getRootLogger();

    private long fNbWrittenEvents = 0;

    /** Test Class setup */
    @BeforeClass
    public static void init() {
        SWTBotPreferences.KEYBOARD_LAYOUT = "EN_US";
        SWTBotUtils.initialize();
        SWTBotPreferences.TIMEOUT = 20000; /* 20 second timeout */
        fLogger.removeAllAppenders();
        fLogger.addAppender(new NullAppender());
        fBot = new SWTWorkbenchBot();

        /* finish waiting for eclipse to load */
        WaitUtils.waitForJobs();
    }

    /**
     * Clean up
     */
    @AfterClass
    public static void afterClass() {
        fLogger.removeAllAppenders();
    }

    /**
     * Test setup
     *
     * @throws Exception on error
     */
    @Before
    public void before() throws Exception {
        SWTBotUtils.createProject(PROJECT_NAME);
        SWTBotUtils.openTrace(PROJECT_NAME, createTrace(INITIAL_NB_EVENTS), getTraceType());
        WaitUtils.waitForJobs();
    }

    /**
     * Test tear down
     */
    @After
    public void after() {
        SWTBotUtils.deleteProject(PROJECT_NAME, fBot);
    }

    /**
     * Test refreshing a trace after new content was added.
     *
     * @throws IOException
     *             on error
     */
    @Test
    public void testRefresh() throws IOException {
        ITmfTrace activeTrace = TmfTraceManager.getInstance().getActiveTrace();
        assertNotNull(activeTrace);

        fBot.waitUntil(new NumberOfEventsCondition(activeTrace, INITIAL_NB_EVENTS));

        for (int i = 0; i < NB_REFRESH; i++) {
            appendToTrace(NEW_EVENTS_PER_REFRESH);

            // Force resource refresh and ignore trace change
            fBot.viewByTitle("Project Explorer").show();
            fBot.menu().menu("File", "Refresh").click();
            fBot.shell("Trace Changed").activate().bot().button("No").click();

            // Refresh
            SWTBotEditor eventsEditor = SWTBotUtils.activeEventsEditor(fBot);
            eventsEditor.show();
            fBot.menu().menu("File", "Refresh").click();

            // Make sure the refresh is completed
            fBot.waitUntil(new NumberOfEventsCondition(activeTrace, getNbWrittenEvents()), INDEXING_TIMEOUT);
        }

        // Make sure the end of the table matches what we expect
        goToTableEnd();
        fBot.waitUntil(ConditionHelpers.selectionInEventsTable(fBot, getExpectedEndTimeStamp()));
    }

    private static class NumberOfEventsCondition extends DefaultCondition {

        private ITmfTrace fTrace;
        private long fNbEvents;

        private NumberOfEventsCondition(ITmfTrace trace, long nbEvents) {
            fTrace = trace;
            fNbEvents = nbEvents;
        }

        @Override
        public boolean test() throws Exception {
            return fTrace.getNbEvents() == fNbEvents;
        }

        @Override
        public String getFailureMessage() {
            return fTrace.getName() + " did not contain the expected number of " + fNbEvents + " events. Current: " + fTrace.getNbEvents();
        }
    }

    /**
     * Create a trace with a number of events.
     *
     * @param nbEvents
     *            the number of events to generate
     * @return the path to the created trace
     * @throws Exception
     *             on error
     */
    protected String createTrace(long nbEvents) throws Exception {
        writeToTrace(nbEvents, false);
        return TRACE_LOCATION;
    }

    /**
     * Append a number of events to the trace.
     *
     * @param nbEvents
     *            the number of events to append
     * @throws IOException
     *             on error
     */
    protected void appendToTrace(long nbEvents) throws IOException {
        writeToTrace(nbEvents, true);
    }

    private void writeToTrace(long nbEvents, boolean append) throws IOException {
        final File file = new File(TRACE_LOCATION);
        try (FileWriter writer = new FileWriter(file, append)) {
            for (int i = 0; i < nbEvents; ++i) {
                writeEvent(writer);
            }
        }
    }

    private void writeEvent(FileWriter fw) throws IOException {
        SimpleDateFormat f = new SimpleDateFormat(TIMESTAMP_FORMAT);
        String timeStampStr = f.format(new Date(fNbWrittenEvents * SECOND_TO_MILLISECOND));
        fw.write(timeStampStr + " HostF LoggerF: SourceFileF:9 Message F\n");
        fNbWrittenEvents++;
    }

    /**
     * Get the trace type for the test.
     *
     * @return the trace type
     */
    protected String getTraceType() {
        return TRACE_TYPE_SYSLOG;
    }

    /**
     * Get the expected time in nanosecs at the end of the trace, after
     * refreshing.
     *
     * @return the expected time in nanosecs at the end of the trace
     */
    protected long getExpectedEndTimeStamp() {
        Date date = new Date((fNbWrittenEvents - 1) * SECOND_TO_MILLISECOND);
        // Syslog fills in the year when parsing so we have to do it for the
        // expected time stamp as well
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.set(Calendar.YEAR, CURRENT.get(Calendar.YEAR));
        if (calendar.after(CURRENT)) {
            calendar.set(Calendar.YEAR, CURRENT.get(Calendar.YEAR) - 1);
        }
        return calendar.getTimeInMillis() * MICROSECOND_TO_NANOSECOND;
    }

    private static void goToTableEnd() {
        SWTBotEditor eventsEditor = SWTBotUtils.activeEventsEditor(fBot);
        eventsEditor.setFocus();
        eventsEditor.bot().table().pressShortcut(Keystrokes.END);
    }

    /**
     * Get the number of events written so far.
     *
     * @return the number of events written so far
     */
    protected long getNbWrittenEvents() {
        return fNbWrittenEvents;
    }
}
