{
    This file is part of logfilter.
    Copyright (C) 2015 Andreas Schneider
    logfilter is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    logfilter is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with logfilter.  If not, see .
}
unit UApp;
{$mode objfpc}{$H+}
{.$define debugmatches}
interface
uses
  Classes, SysUtils, CustApp, RegExpr, Math,
  UFilter, UWriter;
type
  { TLogFilterApplication }
  TLogFilterApplication = class(TCustomApplication)
  protected
    FLineFilters: TLineFilters;
    FCurrentLineFilter: TLineFilter;
    FCommandMatcher: TRegExpr;
    FCommandFileName: String;
    FLogFileName: String;
    FWriter: TWriterList;
    procedure DoRun; override;
    procedure ProcessCommand(ACommand, AParams: String);
    procedure WriteContent(AContent: String; AFilters: TFilterList;
      AGroupRanges: TGroupRanges);
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure WriteHelp; virtual;
  end;
implementation
{ TLogFilterApplication }
procedure TLogFilterApplication.DoRun;
var
  commandFile: TextFile;
  logFile: TextFile;
  groupRanges: TGroupRanges;
  lineFilter: TLineFilter;
  line: String;
begin
  if HasOption('h', 'help') or not HasOption('c', 'commandfile') then
  begin
    WriteHelp;
    Terminate;
    Exit;
  end;
  FCommandFileName := GetOptionValue('c', 'commandfile');
  if not FileExists(FCommandFileName) then
  begin
    Writeln('Commandfile not found: ', FCommandFileName);
    ExitCode := 1;
    Terminate;
    Exit;
  end;
  AssignFile(commandFile, FCommandFileName);
  Reset(commandFile);
  // Parse command file first
  while not EOF(commandFile) do
  begin
    Readln(commandFile, line);
    if FCommandMatcher.Exec(line) then
      ProcessCommand(FCommandMatcher.Match[1], FCommandMatcher.Match[3]);
  end;
  CloseFile(commandFile);
  if HasOption('f', 'logfile') then
    FLogFileName := GetOptionValue('f', 'logfile');
  if not FileExists(FLogFileName) then
  begin
    Writeln('Logfile not found: ', FLogFileName);
    ExitCode := 1;
    Terminate;
    Exit;
  end;
  FWriter.Add(TConsoleWriter.Create);
  if HasOption('html') then
    FWriter.Add(THTMLWriter.Create(GetOptionValue('html')));
  AssignFile(logFile, FLogFileName);
  Reset(logFile);
  // Filter log
  while not EOF(logFile) do
  begin
    Readln(logFile, line);
    for lineFilter in FLineFilters do
    begin
      if lineFilter.Matches(line, groupRanges) then
      begin
        WriteContent(line, lineFilter.Filters, groupRanges);
        Break;
      end;
    end;
  end;
  CloseFile(logFile);
  // One run is enough.
  Terminate;
end;
procedure TLogFilterApplication.ProcessCommand(ACommand, AParams: String);
var
  highlightFilter: THighlightFilter;
begin
  case LowerCase(ACommand) of
    'filter':
      begin
        FCurrentLineFilter := TLineFilter.Create(AParams);
        FLineFilters.Add(FCurrentLineFilter);
      end;
    'highlight':
      begin
        highlightFilter := THighlightFilter.Create(AParams);
        FCurrentLineFilter.Filters.Add(highlightFilter);
      end;
    'file':
      begin
        FLogFileName := AParams;
      end;
  end;
end;
procedure TLogFilterApplication.WriteContent(AContent: String;
  AFilters: TFilterList; AGroupRanges: TGroupRanges);
var
  i: Integer;
  matchPos, offset: Integer;
  highlights: THighlights;
  highlight: THighlight;
  group: Byte;
  writer: TWriter;
begin
  highlights := THighlights.Create;
  offset := 0;
  for i := 0 to AFilters.Count - 1 do
  begin
    if AFilters[i].Expression.Exec(AContent) then
    begin
      repeat
        // We need these values anyway.
        matchPos := AFilters[i].Expression.MatchPos[0];
        offset := AFilters[i].Expression.MatchLen[0];
        group := AFilters[i].Group;
        if group < Length(AGroupRanges) then
        begin
          if (matchPos + offset < AGroupRanges[group].StartIdx) or
            (matchPos > AGroupRanges[group].EndIdx) then
            continue; //Pointless; nothing we can do here
          highlight.Start := Max(AGroupRanges[group].StartIdx, matchPos);
          highlight.Length := Min(AGroupRanges[group].EndIdx - highlight.Start,
            highlight.Start + offset - matchPos);
          {$ifdef debugmatches}
          writeln('  Highlight: ', highlight.Start, ', ', highlight.Length);
          writeln('    MatchPos: ', matchPos, ', StartIdx: ', AGroupRanges[group].StartIdx);
          writeln('    Offset: ', offset, ', EndIdx: ', AGroupRanges[group].EndIdx);
          {$endif}
        end else
        begin
          highlight.Start := matchPos;
          highlight.Length := offset;
        end;
        highlight.FGColor := AFilters[i].FGColor;
        highlight.BGColor := AFilters[i].BGColor;
        highlights.Add(highlight);
      until not AFilters[i].Expression.ExecNext;
    end;
  end;
  highlights.Sort(@CompareHighlights);
  // Sanitize highlights
  for i := 0 to highlights.Count - 2 do
  begin
    if (highlights[i].Start + highlights[i].Length) > highlights[i+1].Start then
    begin
      highlight := highlights[i];
      highlight.Length := highlights[i+1].Start - highlights[i].Start;
      highlights[i] := highlight;
    end;
  end;
  for writer in FWriter do
    writer.WriteContent(AContent, highlights);
  highlights.Free;
end;
constructor TLogFilterApplication.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  FLineFilters := TLineFilters.Create;
  FCommandMatcher := TRegExpr.Create('^(\w+)( (.*)|)$'); // 1 = command, 3 = OPTIONAL params
  FWriter := TWriterList.Create;
end;
destructor TLogFilterApplication.Destroy;
begin
  FLineFilters.Free;
  FCommandMatcher.Free;
  FWriter.Free;
  inherited Destroy;
end;
procedure TLogFilterApplication.WriteHelp;
begin
  Writeln('Usage: ', ExtractFileName(ExeName), ' [options]');
  Writeln;
  Writeln('Options:');
  Writeln('  -c --commandfile=');
  Writeln('    specifies the filename with filter commands');
  Writeln('  -f --logfile=');
  Writeln('    specifies the logfile to be parsed');
  Writeln('  --html=');
  Writeln('    outputs filtered results in a formatted HTML file');
  Writeln('  -h --help');
  Writeln('    show this help screen');
end;
end.