unit avgtool;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, Spin, ComCtrls, ExtCtrls, Buttons
  , strutils
  , Contnrs
  , LazFileUtils //required for ExtractFileNameOnly
  ;

type

  { TForm8 }

  TForm8 = class(TForm)
    BinsLabel: TLabel;
    BinsSpinEdit: TSpinEdit;
    RollingSettingsGroup: TGroupBox;
    Label1: TLabel;
    Label2: TLabel;
    ProcessStatusMemo: TMemo;
    Methodradio: TRadioGroup;
    InputFileListMemo: TMemo;
    SourceDirectoryButton: TBitBtn;
    SourceDirectoryEdit: TEdit;
    Memo1: TMemo;
    ProgressBar1: TProgressBar;
    SelectDirectoryDialog1: TSelectDirectoryDialog;
    StartButton: TButton;
    StatusBar1: TStatusBar;
    procedure BinsSpinEditChange(Sender: TObject);
    procedure MethodRadioClick(Sender: TObject);
    procedure StartButtonClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SourceDirectoryButtonClick(Sender: TObject);
  private

  public

  end;

var
  Form8: TForm8;

implementation
uses
  appsettings, Unit1
;

type
  TFileDetails = class
    Name: String;
    Size, Time: int64;
  end;

const
  Section='AvgTool';

var
  SourceDirectory:String;
  BinsTotal:Integer;
  Bins: Array of Double;
  BinPointer:Integer; //Current bin pointer for incoming values
  ComputeMethod:Integer; //0=Rolling, 1=All records

  { TForm8 }

procedure UpdateBinSize();
var
  v:Double;
  i:Integer;
begin
  //Set array size
  SetLength(Bins, BinsTotal);

  //Clear bin values
  i:=0;
  for v in Bins do begin
    Bins[i]:=0;
    Inc(i);
  end;

end;

procedure FileListAppendFileNames(const AFileList: TObjectList; const APath: TFileName);
var
  LDetails: TFileDetails;
  LSearchRec: TSearchRec;
begin
  if FindFirst(APath + '*.txt', 0, LSearchRec) = 0 then begin
    try
      repeat
        LDetails := TFileDetails.Create;
        LDetails.Name := LSearchRec.Name;
        LDetails.Size := LSearchRec.Size;
        LDetails.Time := LSearchRec.Time;
        AFileList.Add(LDetails);
      until FindNext(LSearchRec) <> 0;
    finally
      FindClose(LSearchRec);
    end;
  end;
end;

function CompareName(A, B: Pointer): Integer;
begin
  Result := AnsiCompareFilename(TFileDetails(A).Name, TFileDetails(B).Name);
end;

function CompareSize(A, B: Pointer): Integer;
begin
  Result := TFileDetails(A).Size - TFileDetails(B).Size;
end;

function CompareTime(A, B: Pointer): Integer;
begin
  Result := TFileDetails(A).Time - TFileDetails(B).Time;
end;


procedure FillList(Directory:String);
var
  LIndex: Integer;
  LFileList: TObjectList;
begin
  LFileList := TObjectList.Create;
  Directory:=Directory;
  try
    Form8.InputFileListMemo.Clear;
    Form8.ProcessStatusMemo.Clear;
    FileListAppendFileNames(LFileList, Directory);
    LFileList.Sort(@CompareName);
    for LIndex := 0 to LFileList.Count - 1 do begin
      Form8.InputFileListMemo.Append(TFileDetails(LFileList[LIndex]).Name);
    end;
  finally
    { Update the progress bar maximum. }
    Form8.ProgressBar1.Max:=LFileList.Count;
    Form8.ProgressBar1.Position:=0;

    FreeAndNil(LFileList);
  end;
end;

procedure TForm8.SourceDirectoryButtonClick(Sender: TObject);
begin
  SelectDirectoryDialog1.FileName:=SourceDirectory;
  if SelectDirectoryDialog1.Execute then begin
    SourceDirectory:=RemoveMultiSlash(SelectDirectoryDialog1.FileName+DirectorySeparator);
    SourceDirectoryEdit.Text:=SourceDirectory;
  end;

  //Save directory name in registry
  vConfigurations.WriteString(Section,'SourceDirectory',SourceDirectory);

  FillList(SourceDirectory);
end;

{ Processing of averages takes place in the Bins array.
  Each record of the input files is entered in a Bin (rolling pointer).
  The average is computer from all bins and written to the outputfile.}
procedure TForm8.StartButtonClick(Sender: TObject);
Var
    Count : Longint;
    pieces: TStringList;
    Str: String;
    InFile,OutFile: TextFile;
    ComposeString: String;
    InputFileName:String;
    OutputPathFileName, InputPathFileName:String;
    WorkingPath, OutputPath:String;
    index: Integer;
    WriteAllowable: Boolean = True; //Allow output file to be written or not.

    i:Integer;//Counter
    v:Double;//Value in bin
    Average:Double;//Final average

    LIndex: Integer;
    LFileList: TObjectList;

    MPSASIndex:Integer;
Begin
  pieces := TStringList.Create;
  BinPointer:=0;

  {Update the file list in case it recently changed, and set the progress bar maximum.}
  FillList(SourceDirectory);

  WorkingPath:=RemoveMultiSlash(SourceDirectory + DirectorySeparator);
  OutputPath:=RemoveMultiSlash(WorkingPath+'average' + DirectorySeparator);
  { Make directory to store files. }
  if (not(DirectoryExists(OutputPath))) then
    mkdir(OutputPath);

  { So far there ar no conditions to prevent writing files.
    The output directory either already exists, or has been created.
    The output files will overwrite previous output files.}
  WriteAllowable:=True;

  if WriteAllowable then begin

  { Process the files }

  Count:=0;
  LFileList := TObjectList.Create;

  try
    FileListAppendFileNames(LFileList, SourceDirectory);
    LFileList.Sort(@CompareName);
    for LIndex := 0 to LFileList.Count - 1 do begin
      InputFileName:=TFileDetails(LFileList[LIndex]).Name;
      Inc(Count);

      {Start reading file.}

      { Define Input file. }
      InputPathFileName:=WorkingPath+InputFileName;
      AssignFile(InFile, InputPathFileName);
      ProcessStatusMemo.Append('Processing: '+InputFileName);
      Application.ProcessMessages;
      //ProcessStatusMemo.Update;

      { Define Output file. }
      OutputPathFileName:=OutputPath+LazFileUtils.ExtractFileNameWithoutExt(InputFileName)+'_avg.txt';
      AssignFile(OutFile, OutputPathFileName);
      Rewrite(OutFile); //Open file for writing

      {$I+}
      try
         Reset(InFile);

         StatusBar1.Panels.Items[0].Text:='Reading Input file';

         { Duplicate first one or two non-record lines. }
         Readln(InFile, Str);   WriteLn(OutFile,Str);
         if AnsiStartsStr('Produced',Str) then begin
           Readln(InFile, Str);   WriteLn(OutFile,Str);
           MPSASIndex:=2;
         end
         else
           MPSASIndex:=1;

         repeat
           // Read one line at a time from the file.
           Readln(InFile, Str);

           StatusBar1.Panels.Items[0].Text:='Processing : '+Str;

            begin
               { Separate the fields of the record. }
               pieces.Delimiter := ',';
               pieces.StrictDelimiter := True; //Do not parse spaces
               pieces.DelimitedText := Str;
               ComposeString:='';

               { Compose beginning of string }
               if MPSASIndex=1 then
                 ComposeString:=pieces.Strings[0]
               else
                 ComposeString:=pieces.Strings[0]+','+pieces.Strings[1];

               { parse the fields, and convert as necessary. }
               //Insert the reading into a bin for averaging.
               Bins[BinPointer]:=StrToFloatDef(pieces.Strings[MPSASIndex],0);

               //perform the averaging function on the bins.
               Average:=0;
               i:=0;
               for v in Bins do begin
                 Average:=Average+Bins[i];
                 Inc(i);
               end;
               Average:=Average/i;
               ComposeString:=ComposeString+','+format('%.2f',[Average]);

               { Compose remainder of string }
               for index:=MPSASIndex+1 to pieces.count-1 do begin
                 ComposeString:=ComposeString+','+pieces.Strings[index];
               end;

               WriteLn(OutFile,ComposeString);
               BinPointer:= (BinPointer+1) mod BinsTotal;

             end;
         until(EOF(InFile)); // EOF(End Of File) The the program will keep reading new lines until there is none.
         CloseFile(InFile);

         Flush(OutFile);
         CloseFile(OutFile);

         StatusBar1.Panels.Items[0].Text:='Finished file'+InputPathFileName;

       except
         on E: EInOutError do
         begin
          MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
         end;
       end;

      ProgressBar1.Position:=Count;
      ProgressBar1.Update;

    end;//End of files processing
  finally
    FreeAndNil(LFileList);
  end;

  StatusBar1.Panels.Items[0].Text:='Finished all files. Results stored in :'+OutputPath;
  ProcessStatusMemo.Append('Finished processing files.');
  ProcessStatusMemo.Append('Results stored in: ');
  ProcessStatusMemo.Append(' '+OutputPath);

end;//End of WriteAllowable check.

end;

procedure TForm8.BinsSpinEditChange(Sender: TObject);
begin
  BinsTotal:=BinsSpinEdit.Value;
  //Save value in registry
  vConfigurations.WriteString(Section,'Bins',IntToStr(BinsTotal));

  UpdateBinSize();

end;

procedure TForm8.MethodRadioClick(Sender: TObject);
begin
  //Save value in registry
  ComputeMethod:=MethodRadio.ItemIndex;
  vConfigurations.WriteString(Section,'ComputeMethod',IntToStr(ComputeMethod));
  case ComputeMethod of
   0: RollingSettingsGroup.Enabled:=True;
   1: RollingSettingsGroup.Enabled:=False;
  end;

end;

procedure TForm8.FormShow(Sender: TObject);
begin
  SourceDirectory:=RemoveMultiSlash(vConfigurations.ReadString(Section, 'SourceDirectory', '')+DirectorySeparator);
  SourceDirectoryEdit.Text:=SourceDirectory;

  BinsTotal:=StrToIntDef(vConfigurations.ReadString(Section,'Bins'),0);
  BinsSpinEdit.Value:=BinsTotal;
  UpdateBinSize();

  ComputeMethod:=StrToIntDef(vConfigurations.ReadString(Section,'ComputeMethod'),0);
  MethodRadio.ItemIndex:=ComputeMethod;
  case ComputeMethod of
   0: RollingSettingsGroup.Enabled:=True;
   1: RollingSettingsGroup.Enabled:=False;
  end;

  FillList(SourceDirectory);
end;

initialization
  {$I avgtool.lrs}

end.

