package com.taobao.arthas.core.shell.command.internal;

import java.util.List;
import java.util.regex.Pattern;

import com.taobao.arthas.core.command.basic1000.GrepCommand;
import com.taobao.arthas.core.shell.cli.CliToken;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.middleware.cli.CLI;
import com.taobao.middleware.cli.CommandLine;
import com.taobao.middleware.cli.annotations.CLIConfigurator;

/**
 * @author beiwei30 on 12/12/2016.
 */
public class GrepHandler extends StdoutHandler {
    public static final String NAME = "grep";

    private String keyword;
    private boolean ignoreCase;
    /**
     * select non-matching lines
     */
    private final boolean invertMatch;

    private final Pattern pattern;

    /**
     * print line number with output lines
     */
    private final boolean showLineNumber;

    private boolean trimEnd;

    /**
     * print NUM lines of leading context
     */
    private final Integer beforeLines;
    /**
     * print NUM lines of trailing context
     */
    private final Integer afterLines;

    /**
     * stop after NUM selected lines
     */
    private final Integer maxCount;

    private static CLI cli = null;

    public static StdoutHandler inject(List<CliToken> tokens) {
        List<String> args = StdoutHandler.parseArgs(tokens, NAME);

        GrepCommand grepCommand = new GrepCommand();
        if (cli == null) {
            cli = CLIConfigurator.define(GrepCommand.class);
        }
        CommandLine commandLine = cli.parse(args, true);

        try {
            CLIConfigurator.inject(commandLine, grepCommand);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        int context = grepCommand.getContext();
        int beforeLines = grepCommand.getBeforeLines();
        int afterLines = grepCommand.getAfterLines();
        if (context > 0) {
            if (beforeLines < 1) {
                beforeLines = context;
            }
            if (afterLines < 1) {
                afterLines = context;
            }
        }
        return new GrepHandler(grepCommand.getPattern(), grepCommand.isIgnoreCase(), grepCommand.isInvertMatch(),
                        grepCommand.isRegEx(), grepCommand.isShowLineNumber(), grepCommand.isTrimEnd(), beforeLines,
                        afterLines, grepCommand.getMaxCount());
    }

    GrepHandler(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode,
                    boolean showLineNumber, boolean trimEnd, int beforeLines, int afterLines, int maxCount) {
        this.ignoreCase = ignoreCase;
        this.invertMatch = invertMatch;
        this.showLineNumber = showLineNumber;
        this.trimEnd = trimEnd;
        this.beforeLines = beforeLines > 0 ? beforeLines : 0;
        this.afterLines = afterLines > 0 ? afterLines : 0;
        this.maxCount = maxCount > 0 ? maxCount : 0;
        if (regexpMode) {
            final int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0;
            this.pattern = Pattern.compile(keyword, flags);
        } else {
            this.pattern = null;
        }
        this.keyword = ignoreCase ? keyword.toLowerCase() : keyword;
    }

    @Override
    public String apply(String input) {
        StringBuilder output = new StringBuilder();
        String[] lines = input.split("\n");
        int continueCount = 0;
        int lastStartPos = 0;
        int lastContinueLineNum = -1;
        int matchCount = 0;
        for (int lineNum = 0; lineNum < lines.length;) {
            String line = null;
            if (this.trimEnd) {
                line = StringUtils.stripEnd(lines[lineNum], null);
            } else {
                line = lines[lineNum];
            }
            lineNum++;

            final boolean match;
            if (pattern == null) {
                match = (ignoreCase ? line.toLowerCase() : line).contains(keyword);
            } else {
                match = pattern.matcher(line).find();
            }
            if (invertMatch ? !match : match) {
                matchCount++;
                if (beforeLines > continueCount) {
                    int n = lastContinueLineNum == -1 ? (beforeLines >= lineNum ? 1 : lineNum - beforeLines)
                                    : lineNum - beforeLines - continueCount;
                    if (n >= lastContinueLineNum || lastContinueLineNum == -1) {
                        StringBuilder beforeSb = new StringBuilder();
                        for (int i = n; i < lineNum; i++) {
                            appendLine(beforeSb, i, lines[i - 1]);
                        }
                        output.insert(lastStartPos, beforeSb);
                    }
                } // end handle before lines

                lastStartPos = output.length();
                appendLine(output, lineNum, line);

                if (afterLines > continueCount) {
                    int last = lineNum + afterLines - continueCount;
                    if (last > lines.length) {
                        last = lines.length;
                    }
                    for (int i = lineNum; i < last; i++) {
                        appendLine(output, i + 1, lines[i]);
                        lineNum++;
                        continueCount++;
                        lastStartPos = output.length();
                    }
                } // end handle afterLines

                continueCount++;
                if (maxCount > 0 && matchCount >= maxCount) {
                    break;
                }
            } else { // not match
                if (continueCount > 0) {
                    lastContinueLineNum = lineNum - 1;
                    continueCount = 0;
                }
            }
        }
        final String str = output.toString();
        return str;
    }

    protected void appendLine(StringBuilder output, int lineNum, String line) {
        if (showLineNumber) {
            output.append(lineNum).append(':');
        }
        output.append(line).append('\n');
    }
}
