package de.duehl.basics.retry;

import static org.junit.Assert.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;

import de.duehl.basics.logging.LogEntry;
import de.duehl.basics.logging.MemoryLogger;
import de.duehl.basics.retry.retryable.StroreResultTestRetryable;
import de.duehl.basics.retry.retryable.TestRetryable;

/*
 * Copyright 2017 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

public class RetryTest {

    private static final long MILLISECONDS_TO_SLEEP_BY_ERRORS = 100;

    @Test
    public void testWithoutLogger() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
    }

    @Test
    public void testWithoutLoggerWithRandomize() {
        int maximumNumberOfTries = 20;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.randomizeSleepTime();
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
    }

    @Test
    public void testWithMemoryLogger() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        MemoryLogger logger = new MemoryLogger();
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS,
                logger);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
    }

    @Test
    public void testWithMemoryLoggerCheckLog() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        MemoryLogger logger = new MemoryLogger();
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS,
                logger);
        retry.tryAndTry();
        List<LogEntry> logEntries = logger.getLogEntries();
        //System.out.println(LogEntry.toNiceString(logEntries));
        assertEquals(20, logEntries.size());

        {
            LogEntry firstLoopEntry = logEntries.get(0);
            assertEquals("de.duehl.basics.retry.Retry", firstLoopEntry.getClassName());
            assertEquals("loop", firstLoopEntry.getMethod());
            assertEquals("154", firstLoopEntry.getLineNumber());
            assertEquals("tryCount = '1'", firstLoopEntry.getMessage());
        }
        {
            LogEntry firstTryRetryableEntry = logEntries.get(1);
            assertEquals("de.duehl.basics.retry.Retry", firstTryRetryableEntry.getClassName());
            assertEquals("tryRetryable", firstTryRetryableEntry.getMethod());
            assertEquals("178", firstTryRetryableEntry.getLineNumber());
            assertEquals("Beim Versuch, die Aufgabe zu erledigen trat eine Ausnahme auf: "
                    + "Klasse der Ausnahme: java.lang.RuntimeException, Fehlermeldung der Ausnahme: "
                    + "Misserfolg Nummer 1", firstTryRetryableEntry.getMessage());
        }
        {
            LogEntry successEntry = logEntries.get(19);
            assertEquals("de.duehl.basics.retry.Retry", successEntry.getClassName());
            assertEquals("tryRetryable", successEntry.getMethod());
            assertEquals("173", successEntry.getLineNumber());
            assertEquals("Success!", successEntry.getMessage());
        }
    }

    @Test
    public void randomizeFrom90To110Percent() {
        int maximumNumberOfTries = 0; // hier uninteressant
        int numberOfTriesFailing = 0; // hier uninteressant
        long millisecondsToSleepByError = 100000L; // hier uninteressant
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, millisecondsToSleepByError);

        long number = 100;
        long min = 90;  // number - 10%
        long max = 110; // number + 10%

        Map<Long, Boolean> reached = new HashMap<>();
        for (long ranomizedNumber = min; ranomizedNumber <= max; ++ranomizedNumber) {
            reached.put(ranomizedNumber, false);
        }

        for (int i = 0; i < 10000; ++i) {
            long ranomizedNumber = retry.randomizeFrom90To110Percent(number);
            if (reached.containsKey(ranomizedNumber)) {
                reached.put(ranomizedNumber, true);
            }
            else {
                System.err.println("ranomizedNumber " + ranomizedNumber
                        + " nicht als Key in reached!");
                fail();
            }
        }

        for (long ranomizedNumber = min; ranomizedNumber <= max; ++ranomizedNumber) {
            boolean isReached = reached.get(ranomizedNumber);
            //System.out.println(ranomizedNumber + " => " + isReached);
            if (!isReached) {
                System.err.println("ranomizedNumber " + ranomizedNumber + " wurde nicht getroffen!");
                fail();
            }
        }
    }

    @Test
    public void retryAndStoreResult1try() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = 0;
        StroreResultTestRetryable retryable = new StroreResultTestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getResult();
        int expected = 42 + numberOfTriesFailing + 1;
        assertEquals(expected, actual);
    }

    @Test
    public void retryAndStoreResult2tries() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = 1;
        StroreResultTestRetryable retryable = new StroreResultTestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getResult();
        int expected = 42 + numberOfTriesFailing + 1;
        assertEquals(expected, actual);
    }

    @Test
    public void retryAndStoreResult9tries() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        StroreResultTestRetryable retryable = new StroreResultTestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getResult();
        int expected = 42 + numberOfTriesFailing + 1;
        assertEquals(expected, actual);
    }

    @Test
    public void retryOneTimeAndCheckTryCount() {
        int maximumNumberOfTries = 1;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getTryCount();
        int expected = maximumNumberOfTries;
        assertEquals(expected, actual);
    }

    @Test
    public void retryTwoTimesAndCheckTryCount() {
        int maximumNumberOfTries = 2;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getTryCount();
        int expected = maximumNumberOfTries;
        assertEquals(expected, actual);
    }

    @Test
    public void retryTenTimesAndCheckTryCount() {
        int maximumNumberOfTries = 10;
        int numberOfTriesFailing = maximumNumberOfTries - 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertTrue(retry.isSucessfullyDone());
        assertFalse(retry.haveAllTriesFailed());
        int actual = retryable.getTryCount();
        int expected = maximumNumberOfTries;
        assertEquals(expected, actual);
    }

    @Test
    public void retryOneTimeFailingAndCheckTryCount() {
        int maximumNumberOfTries = 1;
        int numberOfTriesFailing = 1;
        TestRetryable retryable = new TestRetryable(numberOfTriesFailing);
        Retry retry = new Retry(retryable, maximumNumberOfTries, MILLISECONDS_TO_SLEEP_BY_ERRORS);
        retry.tryAndTry();
        assertFalse(retry.isSucessfullyDone());
        assertTrue(retry.haveAllTriesFailed());
        int actual = retryable.getTryCount();
        int expected = maximumNumberOfTries;
        assertEquals(expected, actual);
    }

}
