package uk.co.automatictester.lightning.core.data;

import com.univocity.parsers.common.processor.ConcurrentRowProcessor;
import com.univocity.parsers.common.processor.RowListProcessor;
import com.univocity.parsers.csv.CsvParserSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.automatictester.lightning.core.exceptions.CSVFileNonexistentLabelException;
import uk.co.automatictester.lightning.core.s3.S3Client;

import java.io.File;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

import static java.lang.Math.min;
import static uk.co.automatictester.lightning.core.enums.JMeterColumns.*;

public class JMeterTransactions extends CsvEntries {

    private static final int MAX_NUMBER_OF_LONGEST_TRANSACTIONS = 5;

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    protected JMeterTransactions() {
    }

    private JMeterTransactions(File csvFile) {
        long start = System.currentTimeMillis();
        log.debug("Reading CSV file - start");

        loadFromFile(csvFile);
        throwExceptionIfEmpty();

        long finish = System.currentTimeMillis();
        long millisecondsBetween = finish - start;
        log.debug("Reading CSV file - finish, read {} rows, took {}ms", entries.size(), millisecondsBetween);
    }

    private JMeterTransactions(String region, String bucket, String csvObject) {
        s3Client = S3Client.getInstance(region, bucket);
        long start = System.currentTimeMillis();
        log.debug("Reading CSV file - start");

        loadFromS3Object(csvObject);
        throwExceptionIfEmpty();

        long finish = System.currentTimeMillis();
        long millisecondsBetween = finish - start;
        log.debug("Reading CSV file - finish, read {} rows, took {}ms", entries.size(), millisecondsBetween);
    }

    private JMeterTransactions(List<String[]> entries) {
        super(entries);
    }

    public static JMeterTransactions fromFile(File csvFile) {
        return new JMeterTransactions(csvFile);
    }

    public static JMeterTransactions fromS3Object(String region, String bucket, String csvObject) {
        return new JMeterTransactions(region, bucket, csvObject);
    }

    public static JMeterTransactions fromList(List<String[]> entries) {
        return new JMeterTransactions(entries);
    }

    public JMeterTransactions getTransactionsWith(String expectedTransactionName) {
        BiPredicate<String, String> equals = String::equals;
        return getTransactions(expectedTransactionName, equals);
    }

    public JMeterTransactions getTransactionsMatching(String expectedTransactionName) {
        BiPredicate<String, String> matches = String::matches;
        return getTransactions(expectedTransactionName, matches);
    }

    public List<Integer> getLongestTransactions() {
        List<Integer> transactionDurations = getTransactionDurationsDesc();
        return getLongestTransactionDurations(transactionDurations);
    }

    public int getFailCount() {
        return (int) entries.stream()
                .filter(t -> "false".equals(t[TRANSACTION_RESULT_INDEX.getValue()]))
                .count();
    }

    public long getFirstTransactionTimestamp() {
        BiPredicate<Long, Long> isBefore = (edgeTimestamp, currentTimestamp) -> edgeTimestamp == 0 || currentTimestamp < edgeTimestamp;
        return getEdgeTransactionTimestamp(isBefore);
    }

    public long getLastTransactionTimestamp() {
        BiPredicate<Long, Long> isAfter = (edgeTimestamp, currentTimestamp) -> edgeTimestamp == 0 || currentTimestamp > edgeTimestamp;
        return getEdgeTransactionTimestamp(isAfter);
    }

    private JMeterTransactions getTransactions(String expectedTransactionName, BiPredicate<String, String> trait) {
        JMeterTransactions transactions = new JMeterTransactions();
        entries.forEach(transaction -> {
            String transactionName = transaction[TRANSACTION_LABEL_INDEX.getValue()];
            boolean isInScope = trait.test(transactionName, expectedTransactionName);
            if (isInScope) {
                transactions.add(transaction);
            }
        });
        if (transactions.size() == 0) {
            throw new CSVFileNonexistentLabelException(expectedTransactionName);
        }
        return transactions;
    }

    private long getEdgeTransactionTimestamp(BiPredicate<Long, Long> trait) {
        long edgeTimestamp = 0;
        for (String[] transaction : entries) {
            long currentTransactionTimestamp = Long.parseLong(transaction[TRANSACTION_TIMESTAMP.getValue()]);
            boolean isEdgeTimestamp = trait.test(edgeTimestamp, currentTransactionTimestamp);
            if (isEdgeTimestamp) {
                edgeTimestamp = currentTransactionTimestamp;
            }
        }
        return edgeTimestamp;
    }

    private List<Integer> getTransactionDurationsDesc() {
        return entries.stream()
                .map(e -> Integer.parseInt(e[TRANSACTION_DURATION_INDEX.getValue()]))
                .sorted((i1, i2) -> i2 - i1)
                .collect(Collectors.toList());
    }

    private List<Integer> getLongestTransactionDurations(List<Integer> transactionDurations) {
        int transactionDurationsCount = min(transactionDurations.size(), MAX_NUMBER_OF_LONGEST_TRANSACTIONS);
        return transactionDurations.stream()
                .limit(transactionDurationsCount)
                .collect(Collectors.toList());
    }

    protected CsvParserSettings getCsvParserSettings() {
        CsvParserSettings parserSettings = new CsvParserSettings();
        parserSettings.setLineSeparatorDetectionEnabled(true);
        parserSettings.setHeaderExtractionEnabled(true);
        parserSettings.selectFields("label", "elapsed", "success", "timeStamp");
        RowListProcessor rowProcessor = new RowListProcessor();
        ConcurrentRowProcessor concurrentRowProcessor = new ConcurrentRowProcessor(rowProcessor);
        parserSettings.setProcessor(concurrentRowProcessor);
        return parserSettings;
    }
}

@@ -456,16 +456,69 @@
 o.File;%0A
+import java.time.Duration;%0Aimport java.time.Instant;%0A
 import j
@@ -532,16 +532,16 @@
 l.List;%0A
-
 import j
@@ -1006,36 +1006,39 @@
 File) %7B%0A        
-long
+Instant
  start = System.
@@ -1034,32 +1034,19 @@
 t = 
-System.currentTimeMillis
+Instant.now
 ();%0A
@@ -1158,36 +1158,39 @@
 pty();%0A%0A        
-long
+Instant
  finish = System
@@ -1187,85 +1187,82 @@
 h = 
-System.currentTimeMillis();%0A        long millisecondsBetween = finish - start
+Instant.now();%0A        Duration duration = Duration.between(start, finish)
 ;%0A  
@@ -1347,35 +1347,35 @@
 size(), 
-millisecondsBetween
+duration.toMillis()
 );%0A    %7D
@@ -1522,20 +1522,23 @@
         
-long
+Instant
  start =
@@ -1542,32 +1542,19 @@
 t = 
-System.currentTimeMillis
+Instant.now
 ();%0A
@@ -1680,20 +1680,23 @@
         
-long
+Instant
  finish 
@@ -1701,85 +1701,82 @@
 h = 
-System.currentTimeMillis();%0A        long millisecondsBetween = finish - start
+Instant.now();%0A        Duration duration = Duration.between(start, finish)
 ;%0A  
@@ -1865,27 +1865,27 @@
 (), 
-millisecondsBetween
+duration.toMillis()
 );%0A 

Introduce Instant