unit correct49to56;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, Buttons, ComCtrls, Grids;

type

  { Record defining results of checking file for valid conversion possibility. }
  TFileCheck = record
    Filename:String;
    Filepathname:String;
    FirmwareVersion:Integer;
    ModelNumber:Integer;
    AllowableVersion:Boolean; //Allow output file to be written based on firmware version.
    AllowableModel:Boolean; //Allow output file to be written based on meter model.
    AllowableUncorrected:Boolean; //Allow output file to be written based on not corrected yet.
  end;

  { Record defining results of checking file for valid conversion possibility. }
  TFileCorrect = record
    InFilename:String;
    OutFilename:String;
    ConversionSuccess:Boolean; //Flag to indicate success in conversion.
    UserQuit:Boolean; //File to indicate user quit conversion.
    OverwriteSelection: String;
  end;

  { TCorrectForm }
  TCorrectForm = class(TForm)
    CheckDirectoryButton: TButton;
    CorrectDirectoryButton: TButton;
    CorrectButton: TBitBtn;
    FileSelectButton1: TButton;
    FirmwareVersionLabeledEdit: TLabeledEdit;
    InGroupBox: TGroupBox;
    InputFile: TLabeledEdit;
    ConvertDirectoryEdit: TLabeledEdit;
    ModelLabeledEdit: TLabeledEdit;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    OutGroupBox: TGroupBox;
    OutputFile: TLabeledEdit;
    PageControl1: TPageControl;
    StatusBar1: TStatusBar;
    DirectoryStringGrid: TStringGrid;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    procedure CheckDirectoryButton1Click(Sender: TObject);
    procedure CheckDirectoryButtonClick(Sender: TObject);
    procedure CorrectButtonClick(Sender: TObject);
    procedure CorrectDirectoryButtonClick(Sender: TObject);
    procedure FileSelectButton1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ConvertDirectoryEditChange(Sender: TObject);
    procedure ConvertDirectoryEditEditingDone(Sender: TObject);
  private
    procedure AddFilesToList(FilePathName: String);
    procedure FileCheck(var FileCheckRecord: TFileCheck);
    procedure CorrectFile(var WorkingFile:TFileCorrect);
    function CreateOutFilename(InFile:String): String;
  public

  end;


var
  CorrectForm: TCorrectForm;
  ConvertFileDirectory:String;
  InFileName: String;
  OutFileName: String;
  SingleFileCheckRecord, MultiFileCheckRecord: TFileCheck;
  SingleFileCorrect, MultiFileCorrect: TFileCorrect;
  Correctionfactor: Real;
  FileCorrectionMode: String; //Multiple or Single file correction mode
  OverwriteMode:String = 'No'; // YesToAll, Yes, No
  InFileList:TStringList;//List of files to convert

  { Fix for multi-language issues }
  FPointSeparator, FCommaSeparator: TFormatSettings;

implementation

uses
  appsettings //Required to read application settings (like locations).
  , dateutils //Required to convert logged UTC string to TDateTime
  , strutils //Required for checking lines in conversion file.
  , LazFileUtils //required for ExtractFileNameOnly
  , math //for log calculations
  ;

{ TCorrectForm }

function TCorrectForm.CreateOutFilename(InFile:String): String;
begin
    CreateOutFilename := ExtractFilePath(InFile) +
                         LazFileUtils.ExtractFileNameOnly (InFile) +
                         '_MPSASCorr' +
                         ExtractFileExt(InFile);

end;

procedure TCorrectForm.FileSelectButton1Click(Sender: TObject);
var
  StatusTextComposeString:String;
begin
    { Clear input filename in preparation for new selected filename}
    InputFile.Text:='';

    { Clear status bar }
    StatusBar1.Panels.Items[0].Text:='';

    OpenDialog1.Filter:='data log files|*.dat|All files|*.*';
    OpenDialog1.InitialDir := appsettings.LogsDirectory;

      { Get Input filename from user }
  if (OpenDialog1.Execute) then  begin

    InFileName:=OpenDialog1.FileName;
    InputFile.Text:=InFileName;

    SingleFileCheckRecord.Filepathname:=InFileName;

    FileCheck(SingleFileCheckRecord);

    FirmwareVersionLabeledEdit.Text:=Format('%d',[SingleFileCheckRecord.FirmwareVersion]);
    ModelLabeledEdit.Text:=Format('%d',[SingleFileCheckRecord.ModelNumber]);

    if SingleFileCheckRecord.AllowableModel and SingleFileCheckRecord.AllowableVersion and SingleFileCheckRecord.AllowableUncorrected then begin

        { Create output file name }
        OutFileName := CreateOutFilename(InFileName);
        OutputFile.Text:=OutFileName;
        StatusBar1.Panels.Items[0].Text:='Waiting to start correction, press "Correct" button';
        CorrectButton.Enabled:=True;
     end
     else begin
       StatusTextComposeString:=''; //prepare status bar for error message.

       if not SingleFileCheckRecord.AllowableModel then
         StatusTextComposeString:=StatusTextComposeString+format('Invalid model %d, must be 6 to 11. ',[SingleFileCheckRecord.ModelNumber]);

       if not SingleFileCheckRecord.AllowableVersion then
         StatusTextComposeString:=StatusTextComposeString+format('Invalid firmware version %d, must be 49 to 56. ',[SingleFileCheckRecord.FirmwareVersion]);

       if not SingleFileCheckRecord.AllowableUncorrected then
         StatusTextComposeString:=StatusTextComposeString+format('This file [%s] has already been corrected, please select another.',[InFileName]);

       StatusBar1.Panels.Items[0].Text:=StatusTextComposeString;

       CorrectButton.Enabled:=False;
     end;

  end;
end;

{ modifies FileResult record}
procedure TCorrectForm.FileCheck(var FileCheckRecord: TFileCheck);
var
   pieces: TStringList;
   InFile: TextFile;
   Str: String;
   Stopreading:Boolean = False;
begin
     pieces := TStringList.Create;

     { Default responses }
     FileCheckRecord.AllowableModel:=False;
     FileCheckRecord.AllowableUncorrected:=False;
     FileCheckRecord.AllowableVersion:=False;
     FileCheckRecord.FirmwareVersion:=0;
     FileCheckRecord.ModelNumber:=0;

     { Check that firmware version 49-56 is valid. }
     { read formware version}

     //Start reading file.
     AssignFile(InFile, FileCheckRecord.Filepathname);

     {$I+}
     try
       Reset(InFile);

       StatusBar1.Panels.Items[0].Text:='Checking Input file for proper firmware version';

       repeat
         // Read one line at a time from the file.
         Readln(InFile, Str);
         StatusBar1.Panels.Items[0].Text:='Processing : '+Str;

         pieces.Delimiter := '-';
         pieces.StrictDelimiter := True; //Do not parse spaces also
         pieces.DelimitedText := Str;

         { Read comment lines firmware identifier line. }
         if AnsiStartsStr('# SQM firmware version:',Str) then begin
//writeln('Count= '+IntToStr(pieces.Count));
           if (pieces.Count=3) then begin
             {Check Model DL (6) or DL-V (11)}
              FileCheckRecord.ModelNumber:=StrToIntDef(pieces.Strings[1],0);
              if ((FileCheckRecord.ModelNumber=6) or (FileCheckRecord.ModelNumber=11)) then
                FileCheckRecord.AllowableModel:=True;

              { Check firmware version }
              FileCheckRecord.FirmwareVersion:=StrToIntDef(pieces.Strings[2],0);
              if ((FileCheckRecord.FirmwareVersion>=49) and (FileCheckRecord.FirmwareVersion<=56)) then
                FileCheckRecord.Allowableversion:=True;

              FileCheckRecord.AllowableUncorrected:=True; //Assume file has not yet been correced
              if pieces.Count>3 then //Check for appended text in version information
                 if pieces.Strings[3]='CorrectedMPSAS' then
                   FileCheckRecord.AllowableUncorrected:=False; //Do not allow correction since it has already been done.

           end;
           Stopreading:=True; // Can stop reading file now.
         end;

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

       CloseFile(InFile);
       StatusBar1.Panels.Items[0].Text:='Finished checking';

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

end;

procedure TCorrectForm.FormCreate(Sender: TObject);
begin
  // Format seetings to convert a string to a float
  FPointSeparator := DefaultFormatSettings;
  FPointSeparator.DecimalSeparator := '.';
  FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
  FCommaSeparator := DefaultFormatSettings;
  FCommaSeparator.DecimalSeparator := ',';
  FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator


  { Difference between measurements taken at 8MHz from 14.7456MHz }
  Correctionfactor:=2.5*(log10(14.7456e6)-log10(8e6));


  InFileList:=TStringList.Create;
end;

procedure TCorrectForm.FormDestroy(Sender: TObject);
begin
  InFileList.Free;
end;

procedure TCorrectForm.FormShow(Sender: TObject);
begin
    ConvertFileDirectory:=appsettings.LogsDirectory;
    ConvertDirectoryEdit.Text:=ConvertFileDirectory;
    AddFilesToList(ConvertFileDirectory);
end;

procedure TCorrectForm.ConvertDirectoryEditChange(Sender: TObject);
begin
     {Load up variable}
     ConvertFileDirectory:=ConvertDirectoryEdit.Text;

     { Clear directory list since it may not be correct anymore. }
     DirectoryStringGrid.Clear;
     DirectoryStringGrid.RowCount:=1;
end;

procedure TCorrectForm.ConvertDirectoryEditEditingDone(Sender: TObject);
begin
  {check directory for correctable files. }
    CheckDirectoryButtonClick(nil);
end;

{ Correct file }
procedure TCorrectForm.CorrectFile(var WorkingFile:TFileCorrect);
var
    InFile,OutFile: TextFile;
    Str: String;
    pieces: TStringList;

    index: Integer;

    ComposeString: String;
    //OutFileString: String;

    i: Integer; //general purpose counter

    MSASField: Integer = -1; //Field that contains the MSAS variable, -1 = not defined  yet.
    RecordTypeField: Integer = -1; //Field that contains the Record Type (Initial/subsequent) variable, -1 = not defined  yet.
    MSAS, MSASorig: Double;
begin
    pieces := TStringList.Create;

    { Clear status bar }
    StatusBar1.Panels.Items[0].Text:='';


   { Start reading file. }
   AssignFile(InFile, WorkingFile.InFilename);
   AssignFile(OutFile, WorkingFile.OutFileName);

   { Check file mode multi/single }
   if (FileExists(WorkingFile.OutFileName) and not (OverwriteMode='YesToAll')) then begin
      case FileCorrectionMode of
       'Single': begin
                    if (MessageDlg(
                                   'Overwrite existing file?'
                                   ,'Do you want to overwrite the existing file?'
                                   ,mtConfirmation
                                   ,[mbOK,mbCancel],0
                                   ) = mrOK) then
                        OverwriteMode:='Yes'
                    else
                      OverwriteMode:='No';
                 end;
       'Multiple':begin
                    case MessageDlg(
                                   'Overwrite existing file(s)?'
                                   ,'Do you want to overwrite the existing file(s)?' +sLineBreak
                                   + ExtractFileNameOnly(WorkingFile.OutFilename)+ExtractFileExt(WorkingFile.OutFilename)
                                   ,mtConfirmation
                                   ,[mbYesToAll,mbYes,mbNo, mbAbort],0
                                   ) of
                    mrYesToAll: OverwriteMode:='YesToAll';
                    mrYes: OverwriteMode:='Yes';
                    mrNo: OverwriteMode:='No';
                    mrAbort: OverwriteMode:='Abort';
                 end;
      end;

      end;
     end
   else
     if (not (OverwriteMode='YesToAll')) then
       OverwriteMode:='NA'; //Not applicable since output file does not exist, create new one.

   if ((OverwriteMode='YesToAll') or (OverwriteMode='Yes') or (OverwriteMode='NA')) then begin

   {$I+}
   try
     Reset(InFile);

     Rewrite(OutFile); //Open file for writing

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

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

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

       //Ignore comment lines which have # as first character.
       if (AnsiStartsStr('#',Str)) then begin
         { Find out which field contains MPSAS }
         { Parse field definition line. }
         if (AnsiContainsStr(str,'UTC') and AnsiContainsStr(str,'Local') and AnsiContainsStr(str,'MSAS')) then begin
           pieces.Delimiter := ',';
           pieces.StrictDelimiter := True; //Do not parse spaces also
           pieces.DelimitedText := Str;

           { Get the field locations. }
           for i:=0 to pieces.Count-1 do begin
             if AnsiContainsStr(pieces.Strings[i],'MSAS') then MSASField:=i;
             if AnsiContainsStr(pieces.Strings[i],'Record type') then RecordTypeField:=i;
           end;
         end;


         { Touch up firmware identifier line }
         if AnsiStartsStr('# SQM firmware version:',Str) then
           WriteLn(OutFile,Str+'-CorrectedMPSAS') //Indicate that correction has been made.
         else
          { Write untouched header line }
          WriteLn(OutFile,Str);

       end else begin //Records processing.
        {Only start if MSAS and RecordType fields exist}
        if ((MSASField>0) and (RecordTypeField>0)) then begin
           //Separate the fields of the record.
           pieces.Delimiter := ';';
           pieces.DelimitedText := Str;

           { Only convert subsequent records }
           ComposeString:=Str;//Default to initial reading value (no changes)
           if pieces.Strings[RecordTypeField]='1' then begin

             //parse the fields, and convert as necessary.
             //Convert MSAS value
             //bad values were brighter by 0.66MSAS, therefore corrected values should be darker (larger number).

             MSASorig:=StrToFloatDef(pieces.Strings[MSASField],0,FPointSeparator);

             { Only write valid readings (above 0.0 saturation point) }
             if MSASorig=0.0 then
               MSAS:=MSASorig
             else
               MSAS:=MSASorig + CorrectionFactor;


             { Compose resultant string }
             ComposeString:=pieces.Strings[0];//Start with first field
             for index:=1 to pieces.count-1 do begin
                 if index=MSASField then
                   ComposeString:=ComposeString+';'+format('%0.2f',[MSAS],FPointSeparator)
                 else
                   ComposeString:=ComposeString+';'+pieces.Strings[index];
             end;

           end;

           WriteLn(OutFile,ComposeString);
        end //End of checking for MSAS and RecordTyoe fields
        else begin
          MessageDlg(
                     'Error'
                     , 'File did not contain proper MSAS or Record Type field(s).' + sLineBreak
                        + 'Filename: ' + WorkingFile.InFilename + sLineBreak
                        + 'MSAS field number = ' + IntToStr(MSASField) + sLineBreak
                        + 'Record type field number = ' + IntToStr(RecordTypeField) + sLineBreak
                        + 'File processing aborted.' + sLineBreak
                     , mtError
                     , [mbOK],0);
          OverwriteMode:='Abort';
        end;

       end;
     until(EOF(InFile) or (OverwriteMode='Abort')); // EOF(End Of File) The the program will keep reading new lines until there is none.
     CloseFile(InFile);
     StatusBar1.Panels.Items[0].Text:=format('Finished writing %s',[WorkingFile.OutFileName]);

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

   { Check if overwrite mode was once only. }
   if OverwriteMode='Yes' then
     OverwriteMode:='No';


   end;//End of OverwriteMode check.

   { Pass overwrite selection back to calling routine. }
   WorkingFile.OverwriteSelection:=OverwriteMode;

end;

{ Correct single file button click}
procedure TCorrectForm.CorrectButtonClick(Sender: TObject);

begin
     FileCorrectionMode:='Single';

    { Default overwrite mode = No }
    OverwriteMode:= 'No';

     //Assume that InFilename and OutFileName have already been set.
     SingleFileCorrect.InFilename:=InFileName;
     SingleFileCorrect.OutFilename:=OutFileName;

     CorrectFile(SingleFileCorrect);

end;


{ Check for valid files }
procedure TCorrectForm.AddFilesToList(FilePathName: String);
var
  FileSize : integer = 0; {Filesize, undetermined = 0}
  Row : integer = 1; {Start at row 1}
  FileName, FilePath : string;
  sr : TSearchRec;
begin

    InFileList.Clear;

    { Scan through directory }
    FilePath := ExtractFilePath(FilePathName + DirectorySeparator);
    DirectoryStringGrid.RowCount:=1; {Reset file list}

    {Check if any files match criteria}
    if FindFirstUTF8(FilePath+'*.dat',faAnyFile,sr)=0 then begin
      repeat

        {Get formatted file properties}
        FileName := ExtractFileName(sr.Name);
        FileSize := sr.Size;

        MultiFileCheckRecord.Filename:=FileName;
        MultiFileCheckRecord.Filepathname:=FilePath+FileName;

        { Read through the current file}
        FileCheck(MultiFileCheckRecord);

        { Only list if correctable. }
        if (MultiFileCheckRecord.AllowableModel and MultiFileCheckRecord.AllowableVersion and MultiFileCheckRecord.AllowableUncorrected) then begin

          InFileList.Add(MultiFileCheckRecord.Filepathname);

          {Display found filename and timestamp}
          DirectoryStringGrid.InsertRowWithValues(Row,[
                                                       FileName
                                                       , IntToStr(FileSize)
                                                       , IntToStr(MultiFileCheckRecord.ModelNumber)
                                                       , IntToStr(MultiFileCheckRecord.FirmwareVersion)
                                                       ]);

          {Prepare for next file display}
          Row := Row + 1;
        end;
      until FindNextUTF8(sr)<>0;
    end;
    FindCloseUTF8(sr);

    {Initial sorting}
    if DirectoryStringGrid.RowCount>1 then
      DirectoryStringGrid.SortColRow(true, 1,1,DirectoryStringGrid.RowCount-1);

end;

procedure TCorrectForm.CheckDirectoryButton1Click(Sender: TObject);
begin

end;

procedure TCorrectForm.CheckDirectoryButtonClick(Sender: TObject);
begin
    AddFilesToList(ConvertFileDirectory);
end;

procedure TCorrectForm.CorrectDirectoryButtonClick(Sender: TObject);
var
  FileName:String;
begin
    { Default overwrite mode = No }
    OverwriteMode:= 'No';

    FileCorrectionMode:='Multiple';

    { Iterate through valid filenames. }
    for FileName in InFileList do begin
      { Set Input and Output FileNames. }
      MultiFileCorrect.InFilename:=FileName;
      MultiFileCorrect.OutFilename:=CreateOutFilename(FileName);

      { Correct and write each file. }
      CorrectFile(MultiFileCorrect);

      { Check if user aborted conversions}
      if (MultiFileCorrect.OverwriteSelection='Abort') then
        break;

    end;
    StatusBar1.Panels.Items[0].Text:='Finished writing corrected files.';

end;

initialization
  {$I correct49to56.lrs}

end.

