unit convertoldlogfile;

{$mode objfpc}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, Grids,
  dateutils, //Required to convert logged UTC string to TDateTime
  upascaltz, //For tiomezone conversions
  strutils,
  appsettings;

type

  { TConvertOldLogForm }

  TConvertOldLogForm = class(TForm)
    ImportHeaderButton: TButton;
    ConvertButton: TButton;
    SerialLabel: TLabel;
    OpenFileDialog: TOpenDialog;
    OutputfilenameLabel: TLabeledEdit;
    LogfileSelectButton: TButton;
    FromPreviousComboBox: TComboBox;
    ImportHeaderNameEdit: TEdit;
    LogfilenameLabel: TEdit;
    HeaderDefinitionGroupBox: TGroupBox;
    MethodGroupBox: TRadioGroup;
    StringGrid1: TStringGrid;
    procedure ConvertButtonClick(Sender: TObject);
    procedure ImportHeaderButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FromPreviousComboBoxChange(Sender: TObject);
    procedure LogfileSelectButtonClick(Sender: TObject);
    procedure MethodGroupBoxClick(Sender: TObject);
    procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: integer;
      Rect: TRect; State: TGridDrawState);
  private
    { private declarations }
  public
    { public declarations }
    procedure RadioSelect();
    procedure UpdateFromPreviousConfig();
  end;

var
  ConvertOldLogForm: TConvertOldLogForm;
  ZoneValid: boolean = False;
  ptz :TPascalTZ;
  AZones: TStringList;


implementation

uses
  vinfo;

{ TConvertOldLogForm }

procedure TConvertOldLogForm.MethodGroupBoxClick(Sender: TObject);
begin
  RadioSelect();
end;

procedure TConvertOldLogForm.StringGrid1DrawCell(Sender: TObject;
  ACol, ARow: integer; Rect: TRect; State: TGridDrawState);
//Justify text in the columns and and colourize cells appropriately
const
  border = 3;
var
  RectWidth: integer;
begin
  RectWidth := Rect.Right - Rect.Left;
  with Sender as TStringGrid do
  begin
    Canvas.FillRect(Rect);
    if aCol = 0 then
      Canvas.TextRect(Rect, Rect.Left + RectWidth -
        Canvas.TextWidth(Cells[ACol, ARow]) - border,
        Rect.Top, Cells[ACol, ARow]);
    if aCol = 2 then
      Canvas.TextRect(Rect, Rect.Left + border, Rect.Top, Cells[ACol, ARow]);

    if (aCol = 2) and (aRow = 6) and (length(Stringgrid1.Cells[2, 6]) = 0) then
      begin
        stringgrid1.canvas.Brush.Color := clRed;
        Canvas.FillRect(Rect);
        ConvertButton.Enabled := False;
      end
      else
      begin
        stringgrid1.canvas.Brush.Color := clDefault;
        ConvertButton.Enabled := True;
      end;
  end;
end;

procedure TConvertOldLogForm.LogfileSelectButtonClick(Sender: TObject);
// get the filename to be converted
begin
  OpenFileDialog.Filter := 'csv files|*.csv|All files|*.*;*';
  OpenFileDialog.InitialDir := appsettings.LogsDirectory;
  if OpenFileDialog.Execute then
  begin
    // Show selected input filename
    LogfilenameLabel.Caption := OpenFileDialog.FileName;

    // Show determined output filename
    OutputfilenameLabel.Text := ChangeFileExt(LogfilenameLabel.Text, '.dat');
  end;
end;

procedure TConvertOldLogForm.FromPreviousComboBoxChange(Sender: TObject);
begin
  UpdateFromPreviousConfig();

end;

procedure TConvertOldLogForm.FormShow(Sender: TObject);
var
  INISections: TStringList;
  i: integer;
  pieces: TStringList;
begin
  pieces := TStringList.Create;
  pieces.Delimiter := ':';

  INISections := TStringList.Create;
  //Update radio button selection and enable appropriate fields
  RadioSelect();

  //Update combobox fields with serial numbers of previously storeds header definitions
  vConfigurations.ReadSectionNames(INISections);

  INISections.Sort;

  //delete all non "Serial" number section names
  FromPreviousComboBox.Items.Clear;
  for i := 0 to INISections.Count - 1 do
  begin
    if AnsiStartsStr('Serial', INISections.Strings[i]) then
    begin
      pieces.DelimitedText := INISections.Strings[i];
      FromPreviousComboBox.Items.Add(pieces.Strings[1]);
    end;
  end;

  //select first item if none is selected and at least one exists
  if ((FromPreviousComboBox.Items.Count > 0) and
    (FromPreviousComboBox.ItemIndex < 0)) then
    FromPreviousComboBox.ItemIndex := 0;

  INISections.Free;

  UpdateFromPreviousConfig();

end;

procedure TConvertOldLogForm.FormCreate(Sender: TObject);
begin
  //Fill up stringgrid field labels
  Stringgrid1.Columns[0].Alignment := taRightJustify;
  Stringgrid1.Cells[0, 1] := 'Device type';//.dat file record name
  Stringgrid1.Cells[1, 1] := '';//ini file record name

  Stringgrid1.Cells[0, 2] := 'Instrument ID';
  Stringgrid1.Cells[1, 2] := 'Instrument ID';

  Stringgrid1.Cells[0, 3] := 'Data supplier';
  Stringgrid1.Cells[1, 3] := 'Data Supplier';

  Stringgrid1.Cells[0, 4] := 'Location name';
  Stringgrid1.Cells[1, 4] := 'Location Name';

  Stringgrid1.Cells[0, 5] := 'Position (lat, lon, elev(m))';
  Stringgrid1.Cells[1, 5] := 'Position';

  Stringgrid1.Cells[0, 6] := 'Local timezone';
  Stringgrid1.Cells[1, 6] := 'Local time zone';

  Stringgrid1.Cells[0, 7] := 'Time Synchronization';
  Stringgrid1.Cells[1, 7] := 'Time Synchronization';

  Stringgrid1.Cells[0, 8] := 'Moving / Stationary position';
  Stringgrid1.Cells[1, 8] := 'Moving Stationary Position';

  Stringgrid1.Cells[0, 9] := 'Moving / Fixed look direction';
  Stringgrid1.Cells[1, 9] := 'Moving Stationary Direction';

  Stringgrid1.Cells[0, 10] := 'Number of channels';
  Stringgrid1.Cells[1, 10] := 'Number Of Channels';

  Stringgrid1.Cells[0, 11] := 'Filters per channel';
  Stringgrid1.Cells[1, 11] := 'Filters Per Channel';

  Stringgrid1.Cells[0, 12] := 'Measurement direction per channel';
  Stringgrid1.Cells[1, 12] := 'Measurement Direction Per Channel';

  Stringgrid1.Cells[0, 13] := 'Field of view (degrees)';
  Stringgrid1.Cells[1, 13] := 'Field Of View';

  Stringgrid1.Cells[0, 15] := 'SQM serial number';
  Stringgrid1.Cells[1, 15] := 'SQM serial number';

  Stringgrid1.Cells[0, 16] := 'SQM firmware version';
  Stringgrid1.Cells[1, 16] := 'SQM firmware version';

  Stringgrid1.Cells[0, 17] := 'SQM cover offset value';
  Stringgrid1.Cells[1, 17] := 'CoverOffset';

  Stringgrid1.AutoAdjustColumns;
  Stringgrid1.ColWidths[1] := 0;

end;

procedure TConvertOldLogForm.ImportHeaderButtonClick(Sender: TObject);
var
  File1: TextFile;
  s, v, Str: string;
  i: integer;

begin
  OpenFileDialog.Filter := 'data log files|*.dat|All files|*.*';
  OpenFileDialog.InitialDir := appsettings.LogsDirectory;
  if OpenFileDialog.Execute then
  begin
    ImportHeaderNameEdit.Text := OpenFileDialog.FileName;

    //Initially clear out all values from any previous read-in.
    Stringgrid1.Cols[2].Clear;

    //Start reading file.
    AssignFile(File1, OpenFileDialog.Filename);

    {$I+}
    try
      Reset(File1);

      repeat
        Readln(File1, Str); // Read one line at a time from the file.
        if (AnsiStartsStr('# ', Str) and AnsiContainsStr(Str, ':')) then
          //only parse comment lines containing values
        begin
          Str := AnsiRightStr(Str, length(Str) - 2);//Remove comment characters
          s := AnsiLeftStr(Str, NPos(':', Str, 1) - 1);//get the field name
          v := Trim(AnsiRightStr(Str, length(Str) - RPos(':', Str)));
          //get the field value

          //matchup to fill grid
          for i := 0 to Stringgrid1.RowCount - 1 do
          begin
            if s = Stringgrid1.Cells[0, i] then
            begin
              Stringgrid1.Cells[2, i] := v;
            end;
          end;
        end;

      until (EOF(File1));
      CloseFile(File1);
    except
      on E: EInOutError do
      begin
        MessageDlg('Error', 'File handling error occurred. Details: ' +
          E.ClassName + '/' + E.Message, mtError, [mbOK], 0);
      end;
    end;
    Stringgrid1.AutoAdjustColumns;
    Stringgrid1.ColWidths[1] := 0;

  end;

end;

procedure TConvertOldLogForm.ConvertButtonClick(Sender: TObject);
//Convert old file to .dat format using variables already defined.
var
  InFile, OutFile: TextFile;
  Str: string;
  pieces: TStringList;

  ComposeString: string;

  WriteAllowable: boolean = True; //Allow output file to be written or not.
  Info: TVersionInfo;
  LocalTime,UTCTime : TDateTime;
  TZLocation:string;
begin
  pieces := TStringList.Create;
  AZones:=TStringList.Create;

  //Pull in all timezone regions because region was not defined
  ptz := TPascalTZ.Create();
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'africa');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'antarctica');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'asia');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'australasia');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'europe');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'northamerica');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'southamerica');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'etcetera');
  ptz.ParseDatabaseFromFile(appsettings.TZDirectory+'pacificnew');
  Azones.Clear;
  ptz.GetTimeZoneNames(AZones,true); //only geo name = true
  ptz.DetectInvalidLocalTimes:=False;// maybe a bug: 2012.12.30 00:00:08 does not exist in Australia/Perth
  TZLocation:=Stringgrid1.Cells[2, 6];

  //Start reading file.
  AssignFile(InFile, LogfilenameLabel.Text);
  AssignFile(OutFile, OutputfilenameLabel.Text);
  if FileExists(OutputfilenameLabel.Text) then
  begin
    if (MessageDlg('Overwrite existing file?',
      'Do you want to overwrite the existing file?', mtConfirmation,
      [mbOK, mbCancel], 0) = mrCancel) then
      WriteAllowable := False;
  end;

  if WriteAllowable then
  begin
      {$I+}
    try
      Reset(InFile);
      Rewrite(OutFile); //Open file for writing

      { Write header }
      SetTextLineEnding(OutFile,#13#10);
      Writeln(OutFile,'# Light Pollution Monitoring Data Format 1.0');
      Writeln(OutFile,'# URL: http://www.darksky.org/measurements');
      Writeln(OutFile,'# Number of header lines: 35');
      Writeln(OutFile,'# This data is released under the following license: ODbL 1.0 http://opendatacommons.org/licenses/odbl/summary/');
      Writeln(OutFile,'# Device type: '+Stringgrid1.Cells[2, 1]);
      Writeln(OutFile,'# Instrument ID: '+Stringgrid1.Cells[2, 2]);
      Writeln(OutFile,'# Data supplier: '+Stringgrid1.Cells[2, 3]);
      Writeln(OutFile,'# Location name: '+Stringgrid1.Cells[2, 4]);
      Writeln(OutFile,'# Position (lat, lon, elev(m)): '+Stringgrid1.Cells[2, 5]);
      Writeln(OutFile,'# Local timezone: '+Stringgrid1.Cells[2, 6]);
      Writeln(OutFile,'# Time Synchronization: '+Stringgrid1.Cells[2, 7]);
      Writeln(OutFile,'# Moving / Stationary position: '+Stringgrid1.Cells[2, 8]);
      Writeln(OutFile,'# Moving / Fixed look direction: '+Stringgrid1.Cells[2, 9]);
      Writeln(OutFile,'# Number of channels: '+Stringgrid1.Cells[2, 10]);
      Writeln(OutFile,'# Filters per channel: '+Stringgrid1.Cells[2, 11]);
      Writeln(OutFile,'# Measurement direction per channel: '+Stringgrid1.Cells[2, 12]);
      Writeln(OutFile,'# Field of view (degrees): '+Stringgrid1.Cells[2, 13]);
      Writeln(OutFile,'# Number of fields per line: 5');
      Writeln(OutFile,'# SQM serial number: '+Stringgrid1.Cells[2, 15]);
      Writeln(OutFile,'# SQM firmware version: '+Stringgrid1.Cells[2, 16]);
      Writeln(OutFile,'# SQM cover offset value: '+Stringgrid1.Cells[2, 17]);
      Writeln(OutFile,'# SQM readout test ix: ');
      Writeln(OutFile,'# SQM readout test rx: ');
      Writeln(OutFile,'# SQM readout test cx: ');
      Writeln(OutFile,'# Comment: ');
      Writeln(OutFile,'# Comment: ');
      Writeln(OutFile,'# Comment: ');
      Writeln(OutFile,'# Comment: ');
      Writeln(OutFile,'# Comment: ');

      // Log the UDM version.
      Info := TVersionInfo.Create;
      Info.Load(HINSTANCE);
      Writeln(OutFile,Format('# UDM version: %s',
                                  [IntToStr(Info.FixedInfo.FileVersion[0])
                                  +'.'+IntToStr(Info.FixedInfo.FileVersion[1])
                                  +'.'+IntToStr(Info.FixedInfo.FileVersion[2])
                                  +'.'+IntToStr(Info.FixedInfo.FileVersion[3])]));
      Info.Free;

      Writeln(OutFile,'# UDM setting: Converted from old style csv file.');
      Writeln(OutFile,'# blank line 32');
      Writeln(OutFile,'# UTC Date & Time, Local Date & Time, Temperature, Voltage, MSAS');
      Writeln(OutFile,'# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;Volts;mag/arcsec^2');
      Writeln(OutFile,'# END OF HEADER');
      Flush(OutFile);

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

        //Separate the fields of the record.
        pieces.Delimiter := ',';
        pieces.StrictDelimiter := False; //Parse spaces also (to get rid of DOW).
        pieces.DelimitedText := Str;

        //Make sure the number of fields is correct
        if ((pieces.Count <> 7)) then begin
          MessageDlg('Error', 'Got '+IntToStr(pieces.Count)+' fields, need 7 fields in record.',
            mtError, [mbOK], 0);
          break;
        end
        else begin

          //parse the fields, and convert as necessary.
          //Convert UTC string 'YYYY-MM-DDTHH:mm:ss.fff' into TDateTime
          //UTCRecord := ScanDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz', pieces.Strings[1]);
          //writeln(FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz',UTCRecord));

          //Prepare string for output:
          // Input = 1,12-11-26 Mon 19:00:58,MPSAS,temperature,voltage
          //Output = YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;Volts;mag/arcsec^2
          //writeln(Str);
          //writeln(pieces.Strings[1]);
          //writeln(pieces.Strings[2]);
          //writeln(pieces.Strings[3]);
          LocalTime:=ScanDateTime('yy-mm-ddhh:nn:ss',pieces.Strings[1]+pieces.Strings[3]);
          UTCTime:=ptz.LocalTimeToGMT(LocalTime, TZLocation);
          ComposeString :=
              FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',UTCTime)   //UTC calculated from local datetime
            + FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LocalTime) //local time fixed to standard method
            + pieces.Strings[5]+';' //temperature
            + pieces.Strings[6]+';' //voltage
            + pieces.Strings[4]; //mpsas

          WriteLn(OutFile, ComposeString);
        end;//End of checking number of fields in record.

      until (EOF(InFile));
      // EOF(End Of File) The the program will keep reading new lines until there is none.
      CloseFile(InFile);
    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);
  end;//End of WriteAllowable check.
  ptz.Destroy;

end;


procedure TConvertOldLogForm.RadioSelect();
begin
  case MethodGroupBox.ItemIndex of
    0:
    begin
      FromPreviousComboBox.Visible := True;
      SerialLabel.Visible := True;
      ImportHeaderButton.Visible := False;
      ImportHeaderNameEdit.Visible := False;
      StringGrid1.Enabled := False;
    end;
    1:
    begin
      FromPreviousComboBox.Visible := False;
      SerialLabel.Visible := False;
      ImportHeaderButton.Visible := True;
      ImportHeaderNameEdit.Visible := True;
      StringGrid1.Enabled := False;
    end;
  end;

end;

procedure TConvertOldLogForm.UpdateFromPreviousConfig();
var
  INISection: string;
  SectionValues: TStringList;
  i: integer;
  s, v: string; //general purpose strings
begin
  SectionValues := TStringList.Create;

  INISection := 'Serial:' + FromPreviousComboBox.Text;

  //Select header from stored serial numbers
  vConfigurations.ReadSection(INISection, SectionValues);

  //Initially clear out all values from any previous read-in.
  Stringgrid1.Cols[2].Clear;

  for s in SectionValues do
  begin
    v := vConfigurations.ReadString(INISection, s, '');

    //matchup to fill grid
    for i := 0 to Stringgrid1.RowCount - 1 do
    begin
      if s = Stringgrid1.Cells[1, i] then
      begin
        Stringgrid1.Cells[2, i] := v;
      end;
    end;
  end;
  Stringgrid1.AutoAdjustColumns;
  Stringgrid1.ColWidths[1] := 0;

  SectionValues.Free;

end;

initialization
  {$I convertoldlogfile.lrs}

end.
