/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.clerezza.integrationtest.web.framework;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author hasan, reto
 *
 * @since version 0.1
 */
public class ResultsLogger {

    AbnormalTeminationNotificator notificator;
    
    private List<ExceptionDescription> exeptionDescriptionList = new ArrayList<ExceptionDescription>(); 

    private PrintWriter printWriter;
    /**
     * the prefix of report files' name
     */
    private static final String REPORT_FILEPRFX = "testresults-";
    /**
     * the suffix of report files' name
     */
    private static final String REPORT_FILESUFX = ".txt";
    /**
     * maps a TestThread to its statistics data
     */
    private Map<TestThread, TestStatistics> testThreadStatistics = 
            new HashMap<TestThread, TestStatistics>();

    final Logger logger = LoggerFactory.getLogger(ResultsLogger.class);

    ResultsLogger(String resultsDirectory, AbnormalTeminationNotificator notificator) {
        this.notificator = notificator;
        printWriter = null;

        try {
            String index = getNextIndex(resultsDirectory);
            String fname = resultsDirectory + "/" + REPORT_FILEPRFX + index + ".txt";
            logger.info("ResultsLogger: test results stored into {}", fname);
            File f = new File(fname);
            if (!f.exists()) {
                f.createNewFile();
            }
            printWriter = new PrintWriter(new FileWriter(f, true));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * gets the next index to be used as part of the filename for storing 
     * test results
     * 
     * @param resultsDirectory the directory for storing test results
     * @return index for the new file to store test results
     */
    private String getNextIndex(String resultsDirectory) {
        File dir = new File(resultsDirectory);

        FilenameFilter filter = new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.startsWith(REPORT_FILEPRFX) &&
                        name.endsWith(REPORT_FILESUFX);
            }
        };

        String[] children = dir.list(filter);

        if (children == null) { // Either dir does not exist or is not a directory or is not readable
            if (dir.isFile()) {
                throw new RuntimeException(resultsDirectory +
                        " already exists as a file");
            }
            if (dir.exists()) {
                if (!dir.canRead()) {
                    throw new RuntimeException("Cannot read directory " +
                            resultsDirectory);
                }
            } else {
                if (!dir.mkdir()) {
                    throw new RuntimeException("Cannot create directory " +
                            resultsDirectory);
                }
            }
            return "1";
        } else {
            int max = 0;
            for (int i = 0; i < children.length; i++) {
                // Get filename of file
                String filename = children[i];
                String strIndex = filename.substring(REPORT_FILEPRFX.length(),
                        filename.length() - REPORT_FILESUFX.length());
                int idx = Integer.parseInt(strIndex);
                if (max < idx) {
                    max = idx;
                }
            }
            return String.valueOf(++max);
        }
    }

    /**
     * logs results of a TestThread
     * updates TestStatistics of this TestThread
     * 
     * @param timeInNanos elapsed time measured by the TestThread
     * @param test the TestThread
     */
    synchronized void logResult(long timeInNanos, TestThread test) {
        TestStatistics testStatistics = getTestStatistics(test);
        testStatistics.newTestValue(timeInNanos);

        printWriter.print(test.getName());
        printWriter.print("\t");
        printWriter.print(timeInNanos);
        printWriter.println("\tns");
        printWriter.flush();
    }
    
    synchronized void logException(long timeInNanos, TestThread testThread, RuntimeException exception) {
        exeptionDescriptionList.add(new ExceptionDescription(timeInNanos, testThread, exception));
    }

    /**
     * returns an instance of a TestStatistics for the given TestThread
     * creates a new instance if none has been assigned to this TestThread
     * 
     * @param test TestThread whose information is to be logged
     * @return the TestStatistics assigned to this TestThread
     */
    private TestStatistics getTestStatistics(TestThread test) {
        TestStatistics testStatistics = testThreadStatistics.get(test);
        if (testStatistics == null) {
            testStatistics = new TestStatistics();
            testThreadStatistics.put(test, testStatistics);
        }
        return testStatistics;
    }

    /**
     * this method prints all TestStatistics and then close the writer
     */
    void close() {
        Set<TestThread> testThreads = testThreadStatistics.keySet();
        TestStatistics testStatistics;
        printWriter.println("# Test Statistics:");
        for (TestThread tt : testThreads) {
            testStatistics = testThreadStatistics.get(tt);
            printWriter.print(tt.getName());
            printWriter.print("\t");
            printWriter.print(testStatistics.getMax());
            printWriter.print("\tns\t");
            printWriter.print(testStatistics.getMin());
            printWriter.print("\tns\t");
            printWriter.print(testStatistics.getSum() / testStatistics.getSize());
            printWriter.println("\tns");
        }
        if (exeptionDescriptionList.size() == 0) {
            printWriter.print("# Test regularly terminated at: ");
        } else {
            printWriter.print("# Test terminated after exception at: ");
            notifyAbnormalTemination();
        }
        printWriter.println(new Date());
        printExceptions();
        printWriter.close();
    }

    private void notifyAbnormalTemination() {
        if (notificator != null) {
            printWriter.println("Sending notification...");
            try {
                notificator.notifyAbnormalTermination(exeptionDescriptionList);
            } catch (RuntimeException ex) {
                printWriter.println();
                printWriter.println("###ERROR### Couldn't send notification:");
                ex.printStackTrace(printWriter);
                printWriter.println("###");
            }
        }
        
    }

    private void printExceptions() {
        for (ExceptionDescription exceptionDescription : exeptionDescriptionList) {
            printExceptionDescription(exceptionDescription);
        }
        
    }

    private void printExceptionDescription(
            ExceptionDescription exceptionDescription) {
        printWriter.print("The test-thread ");
        printWriter.print(exceptionDescription.getTestThread().getName());
        printWriter.print(" got a ");
        printWriter.print(exceptionDescription.getException().getClass().getName());
        printWriter.print(" after ");
        printWriter.print(exceptionDescription.getTimeInNanos());
        printWriter.println(" ns");
        exceptionDescription.getException().printStackTrace(printWriter);
        
    }
}
