unit FormMain;

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls,
  EditBtn, TAGraph, TASeries, Generics.Collections, Generics.Defaults,
  DateUtils, Ean, Translator, ApplicationInfo, RegistryEx, PersistentForm,
  Theme, Common;

type

  TPeriod = (prDay, prWeek, prMonth, prYear);
  TValueUnit = (vukWh, vuWh, vukW, vuW);

  { TFormMain }

  TFormMain = class(TForm)
    ApplicationInfo1: TApplicationInfo;
    ButtonIntervalRight: TButton;
    ButtonIntervalLeft: TButton;
    ButtonLoad: TButton;
    Chart1: TChart;
    Chart1LineSeries1: TLineSeries;
    Chart1LineSeries2: TLineSeries;
    Chart1LineSeries3: TLineSeries;
    Chart1LineSeries4: TLineSeries;
    Chart1LineSeries5: TLineSeries;
    Chart1LineSeries6: TLineSeries;
    CheckBoxShowResult: TCheckBox;
    ComboBoxEan: TComboBox;
    ComboBoxEan3: TComboBox;
    ComboBoxEanData: TComboBox;
    ComboBoxEan2: TComboBox;
    ComboBoxPeriod: TComboBox;
    ComboBoxUnit: TComboBox;
    DateEdit1: TDateEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    ListView1: TListView;
    ListView2: TListView;
    Memo1: TMemo;
    PageControl1: TPageControl;
    PersistentForm1: TPersistentForm;
    TabSheetOverview: TTabSheet;
    TabSheetData: TTabSheet;
    TabSheetChart: TTabSheet;
    TabSheetLog: TTabSheet;
    ThemeManager1: TThemeManager;
    TimeEdit1: TTimeEdit;
    Translator1: TTranslator;
    procedure ButtonIntervalLeftClick(Sender: TObject);
    procedure ButtonIntervalRightClick(Sender: TObject);
    procedure ButtonLoadClick(Sender: TObject);
    procedure CheckBoxShowResultChange(Sender: TObject);
    procedure ComboBoxEanSelect(Sender: TObject);
    procedure ComboBoxPeriodSelect(Sender: TObject);
    procedure ComboBoxUnitSelect(Sender: TObject);
    procedure DateEdit1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure TabSheetOverviewShow(Sender: TObject);
    procedure TabSheetChartShow(Sender: TObject);
    procedure TabSheetDataShow(Sender: TObject);
    procedure TabSheetLogShow(Sender: TObject);
    procedure TimeEdit1Change(Sender: TObject);
    procedure Translator1Translate(Sender: TObject);
  private
    Eans: TEans;
    IntervalFrom: TDateTime;
    IntervalTo: TDateTime;
    ValueUnit: TValueUnit;
    Period: TPeriod;
    ReportsDir: string;
    OwnersFileName: string;
    ShowResult: Boolean;
    function FileNameFilter(FileName: string): Boolean;
    function MeasureToFloat(Value: Double; Interval: Double): Double;
    function MeasureToStr(Value: Double): string;
    procedure CreateEan(Number, Owner: string; Kind: TEanKind);
    procedure LoadConfig;
    procedure Report;
    procedure ReloadListViewOverview;
    procedure ReloadListViewData;
    procedure LoadOwners(FileName: string);
    procedure LoadEdcReport(FileName: string);
    procedure ReloadChart;
    function GetIntervalLength: TDateTime;
    function GetIntervalStep: TDateTime;
    procedure SaveConfig;
    procedure UpdateInterface;
    procedure UpdateInterval;
    procedure ShowActiveTab;
  public
  end;


var
  FormMain: TFormMain;


implementation

{$R *.lfm}

uses
  csvdocument;

resourcestring
  STotalSupply = 'Total supply';
  STotalConsumtion = 'Total consumption';
  SEAN = 'EAN';
  SOwner = 'Owner';
  SShared = 'Shared';
  SMeasured = 'Measured';
  SResult = 'Result';
  SDay = 'Day';
  SWeek = 'Week';
  SMonth = 'Month';
  SYear = 'Year';

{ TFormMain }

function TFormMain.FileNameFilter(FileName: string): Boolean;
begin
  Result := ExtractFileExt(FileName) = '.csv';
end;

procedure TFormMain.ButtonLoadClick(Sender: TObject);
var
  Reports: TStringList;
  I: Integer;
begin
  LoadOwners(OwnersFileName);

  Reports := TStringList.Create;
  SearchFiles(Reports, ReportsDir, FileNameFilter);
  for I := 0 to Reports.Count - 1 do
    LoadEdcReport(Reports[I]);
  Reports.Free;

  for I := 0 to Eans.Count - 1 do
    Eans[I].Values.Sort(TComparer<TEanValue>.Construct(Eans[I].Values.Comparer));

  CreateEan('', STotalSupply, ekSupply);
  CreateEan('', STotalConsumtion, ekConsumption);

  IntervalTo := Eans[0].Values.Last.Time + 15 * OneMinute;
  UpdateInterval;
  UpdateInterface;
end;

procedure TFormMain.CheckBoxShowResultChange(Sender: TObject);
begin
  if CheckBoxShowResult.Checked <> ShowResult then begin
    ShowResult := CheckBoxShowResult.Checked;
    ReloadChart;
  end;
end;

procedure TFormMain.ButtonIntervalRightClick(Sender: TObject);
begin
  IntervalTo := IntervalTo + GetIntervalLength;
  UpdateInterval;
end;

procedure TFormMain.ButtonIntervalLeftClick(Sender: TObject);
begin
  IntervalTo := IntervalTo - GetIntervalLength;
  UpdateInterval;
end;

procedure TFormMain.ComboBoxEanSelect(Sender: TObject);
begin
  ReloadChart;
end;

procedure TFormMain.ComboBoxPeriodSelect(Sender: TObject);
begin
  Period := TPeriod(ComboBoxPeriod.ItemIndex);
  UpdateInterval;
end;

procedure TFormMain.ComboBoxUnitSelect(Sender: TObject);
begin
  ValueUnit := TValueUnit(ComboBoxUnit.ItemIndex);
  ShowActiveTab;
end;

procedure TFormMain.DateEdit1Change(Sender: TObject);
var
  NewIntervalTo: TDateTime;
begin
  NewIntervalTo := DateEdit1.Date + TimeOf(IntervalTo);
  if NewIntervalTo <> IntervalTo then begin
    IntervalTo := NewIntervalTo;
    UpdateInterval;
  end;
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  PageControl1.TabIndex := 0;
  Eans := TEans.Create;
  LoadConfig;
end;

procedure TFormMain.FormDestroy(Sender: TObject);
begin
  SaveConfig;
  FreeAndNil(Eans);
end;

procedure TFormMain.FormShow(Sender: TObject);
begin
  ButtonLoadClick(nil);
end;

procedure TFormMain.TabSheetOverviewShow(Sender: TObject);
begin
  ReloadListViewOverview;
end;

procedure TFormMain.TabSheetChartShow(Sender: TObject);
begin
  Eans.LoadToStrings(ComboBoxEan.Items, True);
  if (ComboBoxEan.Items.Count > 1) and (ComboBoxEan.ItemIndex = -1) then ComboBoxEan.ItemIndex := 1;
  Eans.LoadToStrings(ComboBoxEan2.Items, True);
  Eans.LoadToStrings(ComboBoxEan3.Items, True);
  ReloadChart;
end;

procedure TFormMain.TabSheetDataShow(Sender: TObject);
begin
  Eans.LoadToStrings(ComboBoxEanData.Items);
  if (ComboBoxEanData.Items.Count > 0) and (ComboBoxEanData.ItemIndex = -1) then
    ComboBoxEanData.ItemIndex := 0;
  ReloadListViewData;
end;

procedure TFormMain.TabSheetLogShow(Sender: TObject);
begin
  Report;
end;

procedure TFormMain.TimeEdit1Change(Sender: TObject);
var
  NewIntervalTo: TDateTime;
begin
  NEwIntervalTo := DateOf(IntervalTo) + TimeEdit1.Time;
  if NewIntervalTo <> IntervalTo then begin
    IntervalTo := NewIntervalTo;
    UpdateInterval;
  end;
end;

procedure TFormMain.Translator1Translate(Sender: TObject);
begin
  ComboBoxPeriod.Items[Integer(prDay)] := SDay;
  ComboBoxPeriod.Items[Integer(prWeek)] := SWeek;
  ComboBoxPeriod.Items[Integer(prMonth)] := SMonth;
  ComboBoxPeriod.Items[Integer(prYear)] := SYear;
  Ean.Translate;
end;

function TFormMain.MeasureToFloat(Value: Double; Interval: Double): Double;
begin
  case ValueUnit of
    vukWh: Result := Value;
    vuWh: Result := Value * 1000;
    vukW: Result := Value / Interval;
    vuW: Result := Value / Interval * 1000;
    else Result := 0;
  end;
end;

function TFormMain.MeasureToStr(Value: Double): string;
begin
  Result := IntToStr(Trunc(MeasureToFloat(Value, GetIntervalLength * 24)));
end;

procedure TFormMain.CreateEan(Number, Owner: string; Kind: TEanKind);
var
  Ean: TEan;
  Values: TDictionary<TDateTime, TEanValue>;
  ValuesArray: TArray<TPair<TDateTime, TEanValue>>;
  I: Integer;
  E: Integer;
  Value: TEanValue;
begin
  Ean := TEan.Create;
  Ean.Number := Number;
  Ean.Owner := Owner;
  Ean.Kind := Kind;

  Values := TDictionary<TDateTime, TEanValue>.Create;
  for E := 0 to Eans.Count - 1 do
  if Eans[E].Kind = Kind then begin
    for I := 0 to Eans[E].Values.Count - 1 do begin
      if Values.TryGetValue(Eans[E].Values[I].Time, Value) then begin
        Value.ValueIn := Value.ValueIn + Eans[E].Values[I].ValueIn;
        Value.ValueOut := Value.ValueOut + Eans[E].Values[I].ValueOut;
        Values[Eans[E].Values[I].Time] := Value;
      end else begin
        Values.Add(Eans[E].Values[I].Time, TEanValue.Create(0, Eans[E].Values[I].ValueIn, Eans[E].Values[I].ValueOut));
      end;
    end;
  end;

  ValuesArray := Values.ToArray;
  for I := 0 to Values.Count - 1 do begin
    Ean.Values.Add(TEanValue.Create(ValuesArray[I].Key, ValuesArray[I].Value.ValueIn, ValuesArray[I].Value.ValueOut));
  end;
  Ean.Values.Sort(TComparer<TEanValue>.Construct(Ean.Values.Comparer));

  Values.Free;

  Eans.Add(Ean);
end;

procedure TFormMain.Report;
var
  I: Integer;
  TotalIn, TotalOut: Double;
begin
  Memo1.Lines.BeginUpdate;
  try
    Memo1.Lines.Clear;
    for I := 0 to Eans.Count - 1 do begin
      Memo1.Lines.Add(SEAN + ': ' + Eans[I].Number);
      Memo1.Lines.Add(SOwner + ': ' + Eans[I].Owner);
      TotalIn := Eans[I].GetTotalIn(IntervalFrom, IntervalTo);
      TotalOut := Eans[I].GetTotalOut(IntervalFrom, IntervalTo);
      Memo1.Lines.Add(SMeasured + ': ' + IntToStr(Trunc(TotalIn)));
      Memo1.Lines.Add(SResult + ': ' + IntToStr(Trunc(TotalOut)));
      if TotalIn <> 0 then
      Memo1.Lines.Add(SShared + ': ' + IntToStr(Trunc(Eans[I].GetShared(IntervalFrom, IntervalTo))) + ' %');
      Memo1.Lines.Add('');
    end;
  finally
    Memo1.Lines.EndUpdate;
  end;
end;

procedure TFormMain.ReloadListViewOverview;
var
  ListItem: TListItem;
  I: Integer;
begin
  ListView1.BeginUpdate;
  try
    ListView1.Items.Clear;
    for I := 0 to Eans.Count - 1 do begin
      ListItem := ListView1.Items.Add;
      ListItem.Caption := Eans[I].Number;
      ListItem.SubItems.Add(Eans[I].Owner);
      ListItem.SubItems.Add(MeasureToStr(Eans[I].GetTotalIn(IntervalFrom, IntervalTo)));
      ListItem.SubItems.Add(MeasureToStr(Eans[I].GetTotalOut(IntervalFrom, IntervalTo)));
      ListItem.SubItems.Add(IntToStr(Trunc(Eans[I].GetShared(IntervalFrom, IntervalTo))));
      ListItem.SubItems.Add(EanKindText[Eans[I].Kind]);
    end;
  finally
    ListView1.EndUpdate;
  end;
end;

procedure TFormMain.ReloadListViewData;
var
  ListItem: TListItem;
  I: Integer;
  Ean: TEan;
begin
  ListView2.Items.Clear;
  if ComboBoxEanData.ItemIndex = -1 then Exit;
  Ean := TEan(ComboBoxEanData.Items.Objects[ComboBoxEanData.ItemIndex]);

  if Assigned(Ean) then begin
    ListView2.BeginUpdate;
    try
      for I := 0 to Ean.Values.Count - 1 do begin
        ListItem := ListView2.Items.Add;
        ListItem.Caption := DateTimeToStr(Ean.Values[I].Time);
        ListItem.SubItems.Add(FloatToStr(Ean.Values[I].ValueIn));
        ListItem.SubItems.Add(IntToStr(Trunc(Ean.Values[I].GetShared)));
      end;
    finally
      ListView2.EndUpdate;
    end;
  end;
end;

procedure TFormMain.LoadOwners(FileName: string);
var
  Owners: TStringList;
  Ean: TEan;
  I: Integer;
begin
  Owners := TStringList.Create;
  Owners.LoadFromFile(FileName);
  Eans.Clear;
  for I := 0 to Owners.Count - 1 do begin
    Ean := TEan.Create;
    Ean.Number := Owners.Names[I];
    Ean.Owner := Owners.ValueFromIndex[I];
    Eans.Add(Ean);
  end;
  Owners.Free;
end;

procedure TFormMain.LoadEdcReport(FileName: string);
var
  R: Integer;
  C: Integer;
  CSVDoc: TCSVDocument;
  TimeFrom: TDateTime;
  TimeTo: TDateTime;
  CellValueIn: Double;
  CellValueOut: Double;
  Ean: TEan;
  EanKind: TEanKind;
  Number: string;
begin
  CSVDoc := TCSVDocument.Create;
  try
    CSVDoc.Delimiter := ';';
    CSVDoc.LoadFromFile(FileName);

    for C := 0 to ((CSVDoc.ColCount[0] - 3) div 2) - 1 do begin
      Number := CSVDoc.Cells[3 + C * 2, 0];
      if Copy(Number, 1, 3) = 'IN-' then Number := Copy(Number, 4, MaxInt);
      if Copy(Number, 1, 4) = 'OUT-' then Number := Copy(Number, 5, MaxInt);
      if Copy(Number, Length(Number) - 1, 2) = '-D' then begin
        EanKind := ekSupply;
        Number := Copy(Number, 1, Length(Number) - 2);
      end;
      if Copy(Number, Length(Number) - 1, 2) = '-O' then begin
        EanKind := ekConsumption;
        Number := Copy(Number, 1, Length(Number) - 2);
      end;
      Ean := Eans.SearchByNumber(Number);
      if not Assigned(Ean) then begin
        Ean := TEan.Create;
        Ean.Number := Number;
        Eans.Add(Ean);
      end;
      Ean.Kind := EanKind;

      for R := 1 to CSVDoc.RowCount - 1 do begin
        TimeFrom := StrToDate(CSVDoc.Cells[0, R]) + StrToTime(CSVDoc.Cells[1, R]);
        TimeTo := StrToDate(CSVDoc.Cells[0, R]) + StrToTime(CSVDoc.Cells[2, R]);

        CellValueIn := 0;
        if TryStrToFloat(CSVDoc.Cells[3 + C * 2, R], CellValueIn) then begin
          if EanKind = ekConsumption then CellValueIn := -CellValueIn;
        end;

        CellValueOut := 0;
        if TryStrToFloat(CSVDoc.Cells[3 + C * 2 + 1, R], CellValueOut) then begin
          if EanKind = ekConsumption then CellValueOut := -CellValueOut;
        end;

        Ean.Values.Add(TEanValue.Create(TimeFrom, CellValueIn, CellValueOut));
      end;
    end;
  finally
    CSVDoc.Free;
  end;
end;

procedure TFormMain.ReloadChart;
var
  I: Integer;
  Ean: TEan;
begin
  Chart1LineSeries1.BeginUpdate;
  Chart1LineSeries4.BeginUpdate;
  Chart1LineSeries1.Clear;
  Chart1LineSeries4.Clear;
  if ComboBoxEan.ItemIndex <> -1 then begin
    Ean := TEan(ComboBoxEan.Items.Objects[ComboBoxEan.ItemIndex]);
    if Assigned(Ean) then
    with Ean do begin
      for I := 0 to Values.Count - 1 do
        if (Values[I].Time >= IntervalFrom) and (Values[I].Time < IntervalTo) then begin
          Chart1LineSeries1.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueIn, 1 / 4));
          if ShowResult then Chart1LineSeries4.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueOut, 1 / 4));
        end;
    end;
  end;
  Chart1LineSeries1.EndUpdate;
  Chart1LineSeries4.EndUpdate;

  Chart1LineSeries2.BeginUpdate;
  Chart1LineSeries5.BeginUpdate;
  Chart1LineSeries2.Clear;
  Chart1LineSeries5.Clear;
  if ComboBoxEan2.ItemIndex <> -1 then begin
    Ean := TEan(ComboBoxEan2.Items.Objects[ComboBoxEan2.ItemIndex]);
    if Assigned(Ean) then
    with Ean do begin
      for I := 0 to Values.Count - 1 do
        if (Values[I].Time >= IntervalFrom) and (Values[I].Time < IntervalTo) then begin
          Chart1LineSeries2.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueIn, 1 / 4));
          if ShowResult then Chart1LineSeries5.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueOut, 1 / 4));
        end;
    end;
  end;
  Chart1LineSeries2.EndUpdate;
  Chart1LineSeries5.EndUpdate;

  Chart1LineSeries3.BeginUpdate;
  Chart1LineSeries6.BeginUpdate;
  Chart1LineSeries3.Clear;
  Chart1LineSeries6.Clear;
  if ComboBoxEan3.ItemIndex <> -1 then begin
    Ean := TEan(ComboBoxEan3.Items.Objects[ComboBoxEan3.ItemIndex]);
    if Assigned(Ean) then
    with Ean do begin
      for I := 0 to Values.Count - 1 do
        if (Values[I].Time >= IntervalFrom) and (Values[I].Time < IntervalTo) then begin
          Chart1LineSeries3.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueIn, 1 / 4));
          if ShowResult then Chart1LineSeries6.AddXY(Values[I].Time, MeasureToFloat(Values[I].ValueOut, 1 / 4));
        end;
    end;
  end;
  Chart1LineSeries3.EndUpdate;
  Chart1LineSeries6.EndUpdate;
end;

function TFormMain.GetIntervalLength: TDateTime;
begin
  case Period of
    prDay: Result := 1;
    prWeek: Result := 7;
    prMonth: Result := 31;
    prYear: Result := 365;
  end;
end;

function TFormMain.GetIntervalStep: TDateTime;
begin
  case Period of
    prDay: Result := 15 * OneMinute;
    prWeek: Result := 1;
    prMonth: Result := 1;
    prYear: Result := 30;
  end;
end;

procedure TFormMain.UpdateInterface;
begin
  ComboBoxUnit.ItemIndex := Integer(ValueUnit);
  ComboBoxPeriod.ItemIndex := Integer(Period);
  CheckBoxShowResult.Checked := ShowResult;
end;

procedure TFormMain.UpdateInterval;
begin
  IntervalFrom := IntervalTo - GetIntervalLength;
  DateEdit1.Date := IntervalTo;
  TimeEdit1.Time := IntervalTo;
  ShowActiveTab;
end;

procedure TFormMain.ShowActiveTab;
begin
  if Assigned(PageControl1.ActivePage.OnShow) then
    PageControl1.ActivePage.OnShow(nil);
end;

procedure TFormMain.LoadConfig;
begin
  PersistentForm1.RegistryContext := ApplicationInfo1.GetRegistryContext;

  with TRegistryEx.Create do
  try
    CurrentContext := ApplicationInfo1.GetRegistryContext;
    if ValueExists('LanguageCode') then
      Translator1.Language := Translator1.Languages.SearchByCode(ReadStringWithDefault('LanguageCode', ''))
      else Translator1.Language := Translator1.Languages.SearchByCode('');
    if ValueExists('Theme') then
      ThemeManager1.Theme := ThemeManager1.Themes.FindByName(ReadStringWithDefault('Theme', 'System'))
      else ThemeManager1.Theme := ThemeManager1.Themes.FindByName('System');
    ValueUnit := TValueUnit(ReadIntegerWithDefault('Unit', Integer(ValueUnit)));
    Period := TPeriod(ReadIntegerWithDefault('Period', Integer(Period)));
    ReportsDir := ReadStringWithDefault('ReportsDir', ReportsDir);
    OwnersFileName := ReadStringWithDefault('OwnersFileName', OwnersFileName);
    PageControl1.TabIndex := ReadIntegerWithDefault('ActiveTab', PageControl1.TabIndex);
    ShowResult := ReadBoolWithDefault('ShowResult', ShowResult);
  finally
    Free;
  end;
end;

procedure TFormMain.SaveConfig;
begin
  with TRegistryEx.Create do
  try
    CurrentContext := ApplicationInfo1.GetRegistryContext;
    if Assigned(Translator1.Language) and (Translator1.Language.Code <> '') then
      WriteString('LanguageCode', Translator1.Language.Code)
      else DeleteValue('LanguageCode');
    if Assigned(ThemeManager1.Theme) and (ThemeManager1.Theme.Name <> '') then
      WriteString('Theme', ThemeManager1.Theme.Name)
      else DeleteValue('Theme');
    WriteInteger('Unit', Integer(ValueUnit));
    WriteInteger('Period', Integer(Period));
    WriteString('ReportsDir', ReportsDir);
    WriteString('OwnersFileName', OwnersFileName);
    WriteInteger('ActiveTab', PageControl1.TabIndex);
    WriteBool('ShowResult', ShowResult);
  finally
    Free;
  end;
end;

end.

