unit SpotPrice;

interface

uses
  Classes, SysUtils, Generics.Collections, Generics.Defaults, XML,
  fphttpclient, opensslsockets, DOM, Common, DateUtils, CsvDocument;

type
  { TSpotPrice }

  TSpotPrice = record
    Time: TDateTime;
    Value: Currency;
    class function Create(Time: TDateTime; Value: Currency): TSpotPrice; static;
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
  end;

  { TSpotPrices }

  TSpotPrices = class(TList<TSpotPrice>)
  private
    function FileNameFilter(FileName: string): Boolean;
    function Comparer(constref Left, Right: TSpotPrice): Integer;
    procedure LoadSpotReport(FileName: string);
  public
    procedure ClearInterval(IntervalFrom, IntervalTo: TDateTime);
    function GetBlock(var Text: string; StartText, EndText: string): string;
    procedure LoadFromWebDate(Date: TDate);
    procedure LoadFromWeb;
    procedure Import(Directory: string);
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
    function SearchByTime(Time: TDateTime): Integer;
  end;

const
  SpotPriceName = 'SpotPrice';
  SpotPricesName = 'SpotPrices';
  DPH = 1.21;


implementation

{ TSpotPrice }

class function TSpotPrice.Create(Time: TDateTime; Value: Currency): TSpotPrice;
begin
  Result.Time := Time;
  Result.Value := Value;
end;

procedure TSpotPrice.LoadFromXmlNode(Node: TDOMNode);
begin
  Time := ReadDateTime(Node, 'Time', Time);
  Value := ReadDouble(Node, 'Value', Value);
end;

procedure TSpotPrice.SaveToXmlNode(Node: TDOMNode);
begin
  WriteDateTime(Node, 'Time', Time);
  WriteDouble(Node, 'Value', Value);
end;

procedure TSpotPrices.LoadFromWebDate(Date: TDate);
var
  URL: string;
  S: string;
  RowText: string;
  TableText: string;
  TimeText: string;
  ValueText: string;
  Time: TDateTime;
  Value: Double;
begin
  ClearInterval(Date, Date + 1);

  URL := 'https://spotovaelektrina.cz/denni-ceny/' + DateToStr(Date, XmlFormatSettings);
  with TFPHttpClient.Create(nil) do
    try
      S := Get(URL);
      TableText := GetBlock(S, '<table class="table" id="prices">', '</table>');
      repeat
        RowText := GetBlock(TableText, '<tr>', '</tr>');
        if RowText <> '' then begin

          TimeText := GetBlock(RowText, '<td>', '</td>');
          if TimeText <> '' then Time := Date + StrToTime(TimeText);

          ValueText := GetBlock(RowText, '<td>', '</td>').Trim;
          if ValueText <> '' then begin
            ValueText := StringReplace(ValueText, 'Kč', '', [rfReplaceAll]).Trim;
            ValueText := StringReplace(ValueText, Chr($c2) + Chr($a0), '', [rfReplaceAll]);
            Value := StrToInt(ValueText) / 1000 * DPH;
          end;

          if ValueText <> '' then
            Add(TSpotPrice.Create(Time, Value));
        end else Break;
      until False;
    finally
      Free;
    end;
end;

procedure TSpotPrices.LoadFromWeb;
var
  I: Integer;
begin
  for I := 1 to 15 do
    LoadFromWebDate(StrToDate(IntToStr(I) + '.4.2026'));

  Sort(TComparer<TSpotPrice>.Construct(Comparer));
end;

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

function TSpotPrices.Comparer(constref Left, Right: TSpotPrice): Integer;
begin
  Result := CompareDateTime(Left.Time, Right.Time);
end;

procedure TSpotPrices.LoadSpotReport(FileName: string);
var
  CSVDoc: TCSVDocument;
  R: Integer;
  Date, Time: TDateTime;
  Value: Currency;
begin
  CSVDoc := TCSVDocument.Create;
  try
    CSVDoc.LoadFromFile(FileName);

    for R := 1 to CSVDoc.RowCount - 1 do begin
      Date := StrToDate(CSVDoc.Cells[0, R]);
      Time := Date + StrToTime(Copy(CSVDoc.Cells[1, R], 1, Pos(' ', CSVDoc.Cells[1, R]) - 1));
      Value := StrToCurr(CSVDoc.Cells[3, R]) / 1000;
      Add(TSpotPrice.Create(Time, Value));
    end;
  finally
    CSVDoc.Free;
  end;
end;

procedure TSpotPrices.ClearInterval(IntervalFrom, IntervalTo: TDateTime);
var
  I: Integer;
begin
  for I := Count - 1 downto 0 do
    if (Items[I].Time >= IntervalFrom) and (Items[I].Time < IntervalTo) then
      Delete(I);
end;

procedure TSpotPrices.Import(Directory: string);
var
  Reports: TStringList;
  I: Integer;
begin
  Clear;

  Reports := TStringList.Create;
  try
    SearchFiles(Reports, Directory, FileNameFilter);
    for I := 0 to Reports.Count - 1 do
      LoadSpotReport(Reports[I]);
  finally
    Reports.Free;
  end;

  Sort(TComparer<TSpotPrice>.Construct(Comparer));
end;

procedure TSpotPrices.LoadFromXmlNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  SpotPrice: TSpotPrice;
begin
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = SpotPriceName) do begin
    SpotPrice.LoadFromXmlNode(Node2);
    Add(SpotPrice);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TSpotPrices.SaveToXmlNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  I: Integer;
begin
  for I := 0 to Count - 1 do begin
    Node2 := Node.OwnerDocument.CreateElement(SpotPriceName);
    Items[I].SaveToXmlNode(Node2);
    Node.AppendChild(Node2);
  end;
end;

function TSpotPrices.SearchByTime(Time: TDateTime): Integer;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (Items[I].Time <> Time) do Inc(I);
  if I < Count then Result := I
    else Result := -1;
end;

function TSpotPrices.GetBlock(var Text: string; StartText, EndText: string
  ): string;
var
  Index: Integer;
begin
  Result := '';
  Index := Pos(StartText, Text);
  if Index > 0 then begin
    Text := Copy(Text, Index + Length(StartText), MaxInt);
    Index := Pos(EndText, Text);
    if Index > 0 then begin
      Result := Copy(Text, 1, Index - 1);
      Text := Copy(Text, Index + Length(EndText), MaxInt);
    end;
  end;
end;

end.

