{
     Это одна из возможных реализаций контура логгирования в программах.
     При этом интерфейс любой реализации должен быть неизменным.

     Требования к логгированию:
     1. Простота использования и написание единого кода логгирования
        в любой программе.

         Log('text')  -- просто залоггировать произвольный текст
         Log(Group('G'), 'text') -- залоггировать в определённой группе
         Log(Timestamp(T), 'text') -- залоггировать в определённое время

         Log(LogParams) -- общий формат вызова
         Log(LogParams, Ex1) -- вызов с доп. параметром
         Log(LogParams, Ex1, Ex2) -- вызов с двумя доп. параметрами

     2. Поддержка подключения любого бэкенда, т.е. Writeln, запись в файл,
        отправка по сокету, logcat в андроиде и т.д.

     3. Ограничения на формат, по которым можно что-то парсить.

         TIMESTAMP G TEXT
         ^         ^ ^
         |         | |_ произвольный текст, формат зависит от группы G
         |         |
         |         |___ группа записи
         |              для записей без групп используется символ *
         |              возможно, что будет пересмотрено в пользу другого
         |              символа или пустоты
         |
         |_____________ время в миллисекундах возникновения события
                        в десятичной системе исчисления
}
{$MODE OBJFPC}
{$MODESWITCH RESULT}
{$MODESWITCH AUTODEREF}
unit dlog;

interface

uses
  dtypes,
  dstrnum;

type
PLogParam = ^TLogParam;
TLogParam = record
  Value: AnsiString;
end;

PLogEntry = ^TLogEntry;
TLogEntry = object
  Timestamp: LongInt;
  Group: AnsiString;
  Line: AnsiString;
  Ex: array of TLogParam;
  function BuildString: AnsiString;
end;

// TODO maybe it is better to make Line as an other argument
TLogFunc = procedure (Entry: PLogEntry);

var
  DefaultEntry: TLogEntry;

procedure LogNothing(Entry: PLogEntry);
//procedure SetLogFunc(Func: TLogFunc);
procedure AddLogFunc(Func: TLogFunc);
procedure Log(const E: TLogEntry); overload; inline;
procedure Log(const S: AnsiString); overload; inline;
procedure Log(const E: TLogEntry; Ex1: TLogParam); overload;

operator := (const S: AnsiString): TLogEntry; inline;
operator := (const S: AnsiString): TLogParam; inline;

implementation

var
  LogFuncs: array of TLogFunc;

procedure CallLogFunc(Entry: PLogEntry);
var
  I: LongInt;
begin
  for I := 0 to High(LogFuncs) do
    LogFuncs[I](Entry);
end;

function TLogEntry.BuildString: AnsiString;
var
  I: LongInt;
begin
  Result := IntToStr(Timestamp) + #9 +
            Group + #9 +
            Line;
  for I := 0 to High(Ex) do
    Result := Result + Ex[I].Value;
end;

procedure LogNothing(Entry: PLogEntry);
begin
end;

procedure SetLogFunc(Func: TLogFunc);
begin
  SetLength(LogFuncs, 1);
  LogFuncs[0] := Func;
end;

procedure AddLogFunc(Func: TLogFunc);
begin
  SetLength(LogFuncs, Length(LogFuncs) + 1);
  LogFuncs[High(LogFuncs)] := Func;
end;

procedure Log(const E: TLogEntry);
begin
  CallLogFunc(@E);
end;

procedure Log(const S: AnsiString);
var
  E: TLogEntry;
begin
  E := DefaultEntry;
  E.Line := S;
  CallLogFunc(@E);
end;

procedure Log(const E: TLogEntry; Ex1: TLogParam);
var
  L: TLogEntry;
begin
  L := E;
  SetLength(L.Ex, Length(L.Ex) + 1);
  L.Ex[High(L.Ex)] := Ex1;
  CallLogFunc(@L);
end;

operator := (const S: AnsiString): TLogEntry;
begin
  Result := DefaultEntry;
  Result.Line := S;
end;

operator := (const S: AnsiString): TLogParam; inline;
begin
  Result.Value := S;
end;

initialization
  DefaultEntry.Timestamp := 0;
  DefaultEntry.Group := '*';
  DefaultEntry.Line := '';
  DefaultEntry.Ex := nil;
end.
