// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.handler;

import com.yahoo.vespa.defaults.Defaults;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;

/**
 * @author olaaun
 * @author freva
 * @author jonmv
 */
class LogReader {

    private final Path logDirectory;
    private final Pattern logFilePattern;

    LogReader(String logDirectory, String logFilePattern) {
        this(Paths.get(Defaults.getDefaults().underVespaHome(logDirectory)), Pattern.compile(logFilePattern));
    }

    LogReader(Path logDirectory, Pattern logFilePattern) {
        this.logDirectory = logDirectory;
        this.logFilePattern = logFilePattern;
    }

    void writeLogs(OutputStream outputStream, Instant from, Instant to) {
        try {
            List<Path> logs = getMatchingFiles(from, to);
            for (int i = 0; i < logs.size(); i++) {
                Path log = logs.get(i);
                boolean zipped = log.toString().endsWith(".gz");
                try (InputStream in = Files.newInputStream(log)) {
                    InputStream inProxy;

                    // If the log needs filtering, possibly unzip (and rezip) it, and filter its lines on timestamp.
                    if (i == 0 || i == logs.size() - 1) {
                        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(zipped ? new GZIPInputStream(in) : in, UTF_8));
                             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zipped ? new GZIPOutputStream(buffer) : buffer, UTF_8))) {
                            for (String line; (line = reader.readLine()) != null; ) {
                                String[] parts = line.split("\t");
                                if (parts.length != 7)
                                    continue;

                                Instant at = Instant.EPOCH.plus((long) (Double.parseDouble(parts[0]) * 1_000_000), ChronoUnit.MICROS);
                                if (at.isAfter(from) && ! at.isAfter(to)) {
                                    writer.write(line);
                                    writer.newLine();
                                }
                            }
                        }
                        inProxy = new ByteArrayInputStream(buffer.toByteArray());
                    }
                    else
                        inProxy = in;

                    // At the point when logs switch to un-zipped, replace the output stream with a zipping proxy.
                    if ( ! zipped && ! (outputStream instanceof GZIPOutputStream))
                        outputStream = new GZIPOutputStream(outputStream);

                    inProxy.transferTo(outputStream);
                }
            }
        }
        catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        finally {
            try {
                outputStream.close();
            }
            catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
    }

    /** Returns log files which may have relevant entries, sorted by modification time — the first and last must be filtered. */
    private List<Path> getMatchingFiles(Instant from, Instant to) {
        Map<Path, Instant> paths = new HashMap<>();
        try {
            Files.walkFileTree(logDirectory, new SimpleFileVisitor<>() {

                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                    if (logFilePattern.matcher(file.getFileName().toString()).matches())
                        paths.put(file, attrs.lastModifiedTime().toInstant());

                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        List<Path> sorted = new ArrayList<>();
        for (var entries = paths.entrySet().stream().sorted(comparing(Map.Entry::getValue)).iterator(); entries.hasNext(); ) {
            var entry = entries.next();
            if (entry.getValue().isAfter(from))
                sorted.add(entry.getKey());
            if (entry.getValue().isAfter(to))
                break;
        }
        return sorted;
    }

}

@@ -3425,171 +3425,93 @@
     
-// At the point when logs switch to un-zipped, replace the output stream with a zipping proxy.%0A                    if ( ! zipped && ! (outputStream instanceof GZIP
+if ( ! zipped) %7B%0A                        ByteArrayOutputStream buffer = new ByteArray
 Outp
@@ -3518,18 +3518,19 @@
 utStream
-))
+();
 %0A       
@@ -3538,33 +3538,38 @@
                 
-o
+try (O
 utputStream = ne
@@ -3564,16 +3564,25 @@
 tStream 
+outProxy 
 = new GZ
@@ -3600,24 +3600,27 @@
 eam(
-outputStream);%0A%0A
+buffer)) %7B%0A        
     
@@ -3657,25 +3657,21 @@
 erTo(out
-putStream
+Proxy
 );%0A     
@@ -3669,38 +3669,32 @@
 y);%0A            
-    %7D%0A
             %7D%0A  
@@ -3695,34 +3695,32 @@
   %7D%0A        
-%7D%0A
         
 catch (IOExc
@@ -3711,76 +3711,71 @@
     
-catch (IOException e) %7B%0A            throw new UncheckedIOException(e
+        inProxy = new ByteArrayInputStream(buffer.toByteArray()
 );%0A 
@@ -3781,26 +3781,24 @@
         
-%7D%0A
         
 finally 
@@ -3793,21 +3793,14 @@
     
-finally %7B%0A
+ 
    
+%7D%0A
     
@@ -3796,38 +3796,32 @@
      %7D%0A         
-try %7B%0A
                 
@@ -3815,21 +3815,35 @@
         
-     
+inProxy.transferTo(
 outputSt
@@ -3850,18 +3850,15 @@
 ream
-.close();%0A
+);%0A    
     
@@ -3863,32 +3863,52 @@
           %7D%0A    
+        %7D%0A        %7D%0A
         catch (I
@@ -3915,36 +3915,32 @@
 OException e) %7B%0A
-    
             thro
@@ -3970,30 +3970,16 @@
 ion(e);%0A
-            %7D%0A
         

Make sure GZIPOutputStream is properly closed, but without closing channel