/*
 * Copyright 2015-2017 the original author or authors.
 *
 * Licensed 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.glowroot.ui;

import java.io.File;
import java.nio.charset.Charset;
import java.util.List;

import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.io.Files;
import com.google.common.net.MediaType;
import com.google.common.primitives.Longs;
import io.netty.handler.codec.http.HttpHeaderNames;

import org.glowroot.ui.CommonHandler.CommonRequest;
import org.glowroot.ui.CommonHandler.CommonResponse;
import org.glowroot.ui.HttpSessionManager.Authentication;

import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

class GlowrootLogHttpService implements HttpService {

    private static final int DEFAULT_MAX_LINES = 1000;

    private static final Ordering<File> byLastModified = new Ordering<File>() {
        @Override
        public int compare(File left, File right) {
            return Longs.compare(left.lastModified(), right.lastModified());
        }
    };

    private final File logDir;

    GlowrootLogHttpService(File logDir) {
        this.logDir = logDir;
    }

    @Override
    public String getPermission() {
        return "admin:view:log";
    }

    @Override
    public CommonResponse handleRequest(CommonRequest request, Authentication authentication)
            throws Exception {
        List<String> maxLinesParams = request.getParameters("max-lines");
        if (maxLinesParams.isEmpty()) {
            CommonResponse response = new CommonResponse(FOUND);
            response.setHeader(HttpHeaderNames.LOCATION, "log?max-lines=" + DEFAULT_MAX_LINES);
            return response;
        }
        int maxLines = Integer.parseInt(maxLinesParams.get(0));

        File[] list = logDir.listFiles();
        if (list == null) {
            throw new IllegalStateException(
                    "Could not list directory: " + logDir.getAbsolutePath());
        }
        List<File> files = Lists.newArrayList();
        for (File file : list) {
            if (file.isFile() && file.getName().matches("glowroot.*\\.log")) {
                files.add(file);
            }
        }
        files = byLastModified.reverse().sortedCopy(files);
        List<String> lines = Lists.newArrayList();
        for (File file : files) {
            // logback writes logs in default charset
            List<String> olderLines = Files.readLines(file, Charset.defaultCharset());
            olderLines.addAll(lines);
            lines = olderLines;
            if (lines.size() >= maxLines) {
                break;
            }
        }
        List<ChunkSource> chunkSources = Lists.newArrayList();
        if (lines.size() > maxLines) {
            // return last 'maxLines' lines from aggregated log files
            lines = lines.subList(lines.size() - maxLines, lines.size());
            chunkSources.add(ChunkSource.wrap("[earlier log entries truncated]\n\n"));
        }
        for (String line : lines) {
            chunkSources.add(ChunkSource.wrap(line + '\n'));
        }
        return new CommonResponse(OK, MediaType.PLAIN_TEXT_UTF_8, ChunkSource.concat(chunkSources));
    }
}

@@ -710,14 +710,91 @@
 til.
-List;%0A
+Collection;%0Aimport java.util.List;%0A%0Aimport com.google.common.collect.EvictingQueue;
 %0Aimp
@@ -908,16 +908,59 @@
 .Files;%0A
+import com.google.common.io.LineProcessor;%0A
 import c
@@ -3045,63 +3045,455 @@
  // 
-logback writes logs in default charset%0A            List
+don't read entire file into memory at once, even though rollover may be 10mb, a flood%0A            // of logging can create a much much much larger file before rollover occurs%0A            // (see ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy call to isTooSoon())%0A%0A            // logback writes logs in default charset%0A            // %22+ 1%22 is to read extra line to know whether to add %22%5Bearlier log entries truncated%5D%22%0A            Collection
 %3CStr
@@ -3556,19 +3556,23 @@
 harset()
-);%0A
+,%0A     
         
@@ -3579,31 +3579,58 @@
     
-olderLines.addAll(lines
+   new ReadLastNLines(maxLines + 1 - lines.size())
 );%0A 
@@ -3645,18 +3645,26 @@
    lines
- =
+.addAll(0,
  olderLi
@@ -3666,16 +3666,17 @@
 derLines
+)
 ;%0A      
@@ -3699,17 +3699,16 @@
 size() %3E
-=
  maxLine
@@ -4315,11 +4315,496 @@
 );%0A    %7D
+%0A%0A    private static class ReadLastNLines implements LineProcessor%3CCollection%3CString%3E%3E %7B%0A%0A        private final Collection%3CString%3E queue;%0A%0A        private ReadLastNLines(int nLines) %7B%0A            queue = EvictingQueue.create(nLines);%0A        %7D%0A%0A        @Override%0A        public boolean processLine(String line) %7B%0A            queue.add(line);%0A            return true;%0A        %7D%0A%0A        @Override%0A        public Collection%3CString%3E getResult() %7B%0A            return queue;%0A        %7D%0A    %7D
 %0A%7D%0A

Fix OOM caused by reading really large log file