unit Ean;

interface

uses
  Classes, SysUtils, Generics.Collections, Generics.Defaults, DateUtils, DOM,
  XML, SpotPrice;

type

  { TEanValue }

  TEanValue = record
    Time: TDateTime;
    ValueIn: Currency;
    ValueOut: Currency;
    class function Create(Time: TDateTime; ValueIn, ValueOut: Currency): TEanValue; static;
    function GetShared: Double;
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
  end;

  { TEanValues }

  TEanValues = class(TList<TEanValue>)
    function Comparer(constref Left, Right: TEanValue): Integer;
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
  end;

  TEanKind = (ekSupply, ekConsumption);

  { TEan }

  TEan = class
    Number: string;
    Owner: string;
    Address: string;
    DistributionTariff: string;
    PriceLow: Double;
    PriceHigh: Double;
    PriceSpot: Double;
    Values: TEanValues;
    Kind: TEanKind;
    function GetTotalIn(TimeFrom, TimeTo: TDateTime): Double;
    function GetTotalOut(TimeFrom, TimeTo: TDateTime): Double;
    function GetShared(TimeFrom, TimeTo: TDateTime): Double;
    function GetSharedPercent(TimeFrom, TimeTo: TDateTime): Double;
    function GetSavings(TimeFrom, TimeTo: TDateTime; SpotPrices: TSpotPrices): Currency;
    function GetPrice(TimeFrom, TimeTo: TDateTime; SpotPrices: TSpotPrices; MinPrice: Currency): Currency;
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
    constructor Create;
    destructor Destroy; override;
  end;

  { TEans }

  TEans = class(TObjectList<TEan>)
    function GetMaxTime: TDateTime;
    function GetMinTime: TDateTime;
    function SearchByNumber(Number: string): TEan;
    function SearchByOwnerKind(Owner: string; Kind: TEanKind): TEan;
    procedure LoadToStrings(Strings: TStrings; AllowNone: Boolean = False);
    procedure LoadFromXmlNode(Node: TDOMNode);
    procedure SaveToXmlNode(Node: TDOMNode);
  end;

var
  EanKindText: array[TEanKind] of string;

procedure Translate;

const
  EansName = 'Eans';
  EanName = 'Ean';
  EanValueName = 'EanValue';
  EanValuesName = 'EanValues';
  FifteenMinute = 15 * OneMinute;


implementation

resourcestring
  SSupply = 'Supply';
  SConsumption = 'Consumption';

procedure Translate;
begin
  EanKindText[ekSupply] := SSupply;
  EanKindText[ekConsumption] := SConsumption;
end;

{ TEanValue }

class function TEanValue.Create(Time: TDateTime; ValueIn, ValueOut: Currency
  ): TEanValue;
begin
  Result.Time := Time;
  Result.ValueIn := ValueIn;
  Result.ValueOut := ValueOut;
end;

function TEanValue.GetShared: Double;
begin
  if ValueIn <> 0 then Result := (1 - ValueOut / ValueIn) * 100
    else Result := 0;
end;

procedure TEanValue.LoadFromXmlNode(Node: TDOMNode);
begin
  Time := ReadDateTime(Node, 'Time', Time);
  ValueIn := ReadCurrency(Node, 'ValueIn', 0);
  ValueOut := ReadCurrency(Node, 'ValueOut', 0);
end;

procedure TEanValue.SaveToXmlNode(Node: TDOMNode);
begin
  if Time <> 0 then WriteDateTime(Node, 'Time', Time);
  if ValueIn <> 0 then WriteCurrency(Node, 'ValueIn', ValueIn);
  if ValueOut <> 0 then WriteCurrency(Node, 'ValueOut', ValueOut);
end;

{ TEanValues }

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

procedure TEanValues.LoadFromXmlNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  EanValue: TEanValue;
begin
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = EanValueName) do begin
    EanValue.LoadFromXmlNode(Node2);
    Add(EanValue);
    Node2 := Node2.NextSibling;
  end;
end;

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

{ TEan }

function TEan.GetTotalIn(TimeFrom, TimeTo: TDateTime): Double;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Values.Count - 1 do
    if (Values[I].Time >= TimeFrom) and (Values[I].Time < TimeTo) then
      Result := Result + Values[I].ValueIn;
end;

function TEan.GetTotalOut(TimeFrom, TimeTo: TDateTime): Double;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Values.Count - 1 do
    if (Values[I].Time >= TimeFrom) and (Values[I].Time < TimeTo) then
      Result := Result + Values[I].ValueOut;
end;

function TEan.GetShared(TimeFrom, TimeTo: TDateTime): Double;
var
  TotalIn: Double;
  TotalOut: Double;
begin
  TotalIn := GetTotalIn(TimeFrom, TimeTo);
  TotalOut := GetTotalOut(TimeFrom, TimeTo);
  Result := TotalIn - TotalOut;
end;

function TEan.GetSharedPercent(TimeFrom, TimeTo: TDateTime): Double;
var
  TotalIn: Double;
  TotalOut: Double;
begin
  TotalIn := GetTotalIn(TimeFrom, TimeTo);
  TotalOut := GetTotalOut(TimeFrom, TimeTo);
  Result := (1 - TotalOut / TotalIn) * 100;
end;

function TEan.GetSavings(TimeFrom, TimeTo: TDateTime; SpotPrices: TSpotPrices): Currency;
var
  TotalIn: Double;
  TotalOut: Double;
  I: Integer;
  SpotPrice: TSpotPrice;
  SpotPriceIndex: Integer;
begin
    if PriceLow = 0 then begin
    Result := 0;
    for I := 0 to Values.Count - 1 do
      if (Values[I].Time >= TimeFrom) and (Values[I].Time < TimeTo) then begin
        SpotPriceIndex := SpotPrices.SearchByTime(Values[I].Time);
        if SpotPriceIndex <> -1 then
          Result := Result + (Values[I].ValueIn - Values[I].ValueOut) * (SpotPrices[SpotPriceIndex].Value - PriceSpot);
      end;
  end else begin
    TotalIn := GetTotalIn(TimeFrom, TimeTo);
    TotalOut := GetTotalOut(TimeFrom, TimeTo);
    Result := (TotalIn - TotalOut) * PriceLow;
  end;
end;

function TEan.GetPrice(TimeFrom, TimeTo: TDateTime; SpotPrices: TSpotPrices; MinPrice: Currency): Currency;
var
  I: Integer;
  SpotPriceIndex: Integer;
  SpotPrice: Currency;
begin
  if PriceLow = 0 then begin
    Result := 0;
    for I := 0 to Values.Count - 1 do
      if (Values[I].Time >= TimeFrom) and (Values[I].Time < TimeTo) then begin
        SpotPriceIndex := SpotPrices.SearchByTime(Values[I].Time);
        if (SpotPriceIndex <> -1) then begin
          SpotPrice := SpotPrices[SpotPriceIndex].Value - PriceSpot;
          if SpotPrice > MinPrice then
            Result := Result + Values[I].ValueIn * SpotPrice;
        end;
      end;
  end else Result := GetTotalIn(TimeFrom, TimeTo) * PriceLow;
end;

procedure TEan.LoadFromXmlNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  Number := ReadString(Node, 'Number', Number);
  Owner := ReadString(Node, 'Owner', Owner);
  Address := ReadString(Node, 'Address', Address);
  DistributionTariff := ReadString(Node, 'DistributionTariff', DistributionTariff);
  PriceLow := ReadDouble(Node, 'PriceLow', PriceLow);
  PriceHigh := ReadDouble(Node, 'PriceHigh', PriceHigh);
  PriceSpot := ReadDouble(Node, 'PriceSpot', PriceSpot);
  Kind := TEanKind(ReadInteger(Node, 'Kind', Integer(Kind)));

  NewNode := Node.FindNode(EanValuesName);
  if Assigned(NewNode) then
    Values.LoadFromXmlNode(NewNode);
end;

procedure TEan.SaveToXmlNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  if Number <> '' then WriteString(Node, 'Number', Number);
  if Owner <> '' then WriteString(Node, 'Owner', Owner);
  if Address <> '' then WriteString(Node, 'Address', Address);
  if DistributionTariff <> '' then WriteString(Node, 'DistributionTariff', DistributionTariff);
  if PriceLow <> 0 then WriteDouble(Node, 'PriceLow', PriceLow);
  if PriceHigh <> 0 then WriteDouble(Node, 'PriceHigh', PriceHigh);
  if PriceSpot <> 0 then WriteDouble(Node, 'PriceSpot', PriceSpot);
  WriteInteger(Node, 'Kind', Integer(Kind));

  if Values.Count > 0 then begin
    NewNode := Node.OwnerDocument.CreateElement(EanValuesName);
    Node.AppendChild(NewNode);
    Values.SaveToXmlNode(NewNode);
  end;
end;

constructor TEan.Create;
begin
  Values := TEanValues.Create;
end;

destructor TEan.Destroy;
begin
  FreeAndNil(Values);
  inherited;
end;

{ TEans }

function TEans.GetMaxTime: TDateTime;
var
  I: Integer;
  Found: Boolean;
begin
  Found := False;
  for I := 0 to Count - 1 do
  with Items[I] do
    if Values.Count > 0 then begin
      if not Found then begin
        Result := Values[Values.Count - 1].Time;
        Found := True;
      end
      else if Values[Values.Count - 1].Time > Result then begin
        Result := Values[Values.Count - 1].Time;
      end;
    end;
  if not Found then Result := Now;
end;

function TEans.GetMinTime: TDateTime;
var
  I: Integer;
  Found: Boolean;
begin
  Found := False;
  for I := 0 to Count - 1 do
  with Items[I] do
    if Values.Count > 0 then begin
      if not Found then begin
        Result := Values[0].Time;
        Found := True;
      end
      else if Values[0].Time < Result then begin
        Result := Values[0].Time;
      end;
    end;
  if not Found then Result := Now;
end;

function TEans.SearchByNumber(Number: string): TEan;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (Items[I].Number <> Number) do Inc(I);
  if I < Count then Result := Items[I]
    else Result := nil;
end;

function TEans.SearchByOwnerKind(Owner: string; Kind: TEanKind): TEan;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and ((Items[I].Owner <> Owner) or (Items[I].Kind <> Kind)) do Inc(I);
  if I < Count then Result := Items[I]
    else Result := nil;
end;

procedure TEans.LoadToStrings(Strings: TStrings; AllowNone: Boolean = False);
var
  I: Integer;
  Shift: Integer;
  Text: string;
begin
  if AllowNone then Shift := 1 else Shift := 0;
  Strings.BeginUpdate;
  try
    while Strings.Count > Count + Shift do Strings.Delete(Strings.Count - 1);
    while Strings.Count < Count + Shift do Strings.Add('');
    for I := 0 to Count - 1 do begin
      Text := EanKindText[Items[I].Kind];
      if Items[I].Address <> '' then Text := Items[I].Address + ', ' + Text;
      if Items[I].Owner <> '' then Text := Items[I].Owner + ', ' + Text;
      Text := Items[I].Number + ' (' + Text + ')';
      Strings[I + Shift] := Text;
      Strings.Objects[I + Shift] := Items[I];
    end;
  finally
    Strings.EndUpdate;
  end;
end;

procedure TEans.LoadFromXmlNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  Ean: TEan;
begin
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = EanName) do begin
    Ean := TEan.Create;
    Ean.LoadFromXmlNode(Node2);
    Add(Ean);
    Node2 := Node2.NextSibling;
  end;
end;

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

end.

