{ Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina }

unit MathExpressions;

interface

uses
  System.Rtti, System.SysUtils, System.Bindings.EvalProtocol,
  System.Bindings.Evaluator, System.Bindings.EvalSys, System.Bindings.Methods;

function CalculateExpression(const Expr: string): string;

var
  InDegrees: boolean;

implementation

uses
  System.Math, Windows, Translation;

var
  LScope: IScope;

function Sine: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      ANum: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['Sin()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            ANum := IAValue.GetValue.AsExtended; // AV: check the parameter type
            if InDegrees then  // AV: translate the parameter into radians
              ANum := DegToRad(ANum);
            Exit(TValueWrapper.Create(RoundTo(sin(ANum), -6)));
         except
            MessageBox(0, PChar('Sin(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function CoSine: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      ANum: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['Cos()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            ANum := IAValue.GetValue.AsExtended;
            if InDegrees then   // AV: translate the parameter into radians
              ANum := DegToRad(ANum);
            Exit(TValueWrapper.Create(RoundTo(cos(ANum), -6)));
         except
            MessageBox(0, PChar('Cos(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function ArcSine: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      AValue: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['ArcSin()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            AValue := IAValue.GetValue.AsExtended;
            if InRange(AValue, -1, 1) then
            begin
              AValue := arcsin(AValue);
              if InDegrees then
                AValue := RadToDeg(AValue);
              Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
            end
            else
              MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-outofrange')),
                'Apophysis AV', 48);
         except
            MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function ArcCoSine: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      AValue: Double;
     begin
        //AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['ArcCos()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            AValue := IAValue.GetValue.AsExtended;
            if InRange(AValue, -1, 1) then
            begin
              AValue := arccos(AValue);
              if InDegrees then
                AValue := RadToDeg(AValue);
              Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
            end
            else
              MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-outofrange')),
                'Apophysis AV', 48);
         except
            MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function ArcTangentYX: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IYValue, IXValue: IValue;
      AValue: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 2 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['ArcTan2()', 2, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IYValue := Args[0];
         IXValue := Args[1];

         try
            AValue := arctan2(IYValue.GetValue.AsExtended,
                      IXValue.GetValue.AsExtended);
            if InDegrees then
                AValue := RadToDeg(AValue);
            Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
         except
            MessageBox(0, PChar('ArcTan2(): ' + TextByKey('common-invalidformat')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function SquareRoot: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      ANum: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['Sqrt()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            ANum := IAValue.GetValue.AsExtended;
            if ANum >= 0 then
              Exit(TValueWrapper.Create(RoundTo(sqrt(ANum), -6)))
            else
              MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-unsigned')),
                'Apophysis AV', 48);
         except
            MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function NatLog: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IAValue: IValue;
      ANum: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 1 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['Ln()', 1, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IAValue := Args[0];

         try
            ANum := IAValue.GetValue.AsExtended;
            if ANum > 0 then
              Exit(TValueWrapper.Create(RoundTo(ln(ANum), -6)))
            else
              MessageBox(0, PChar('Ln(): ' + TextByKey('formula-unsigned')),
                'Apophysis AV', 48);
         except
            MessageBox(0, PChar('Ln(): ' + TextByKey('formula-wrongdatatype')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

function PowerXY: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
    var
      IYValue, IXValue: IValue;
      ANum: Double;
     begin
        // AV: check the number of passed parameters
        if Length(Args) <> 2 then begin
          MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
            ['Power()', 2, Length(Args)])), 'Apophysis AV', 48);
          exit;
        end;

         IXValue := Args[0];
         IYValue := Args[1];

         try
            ANum := IXValue.GetValue.AsExtended;
            if ANum >= 0 then
              Result := TValueWrapper.Create(RoundTo(power(ANum,
                IYValue.GetValue.AsExtended), -6))
            else
              MessageBox(0, PChar('Power(): ' + TextByKey('formula-unsigned')),
                'Apophysis AV', 48);
         except
            MessageBox(0, PChar('Power(): ' + TextByKey('common-invalidformat')),
              'Apophysis AV', 16);
         end;
     end
     );
end;

procedure RegisterMathFunctions;
begin
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          Sine, 'sin', 'sin', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          CoSine, 'cos', 'cos', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          ArcSine, 'arcsin', 'arcsin', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          ArcCoSine, 'arccos', 'arccos', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          ArcTangentYX, 'arctan2', 'arctan2', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          SquareRoot, 'sqrt', 'sqrt', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          PowerXY, 'power', 'power', '', True, '', nil));
  TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
          NatLog, 'ln', 'ln', '', True, '', nil));

  LScope := BasicOperators;
  TDictionaryScope(LScope).Map.Add('pi', TValueWrapper.Create(pi));
  TDictionaryScope(LScope).Map.Add('exp', TValueWrapper.Create(exp(1)));
  // AV: add the registered methods
  LScope := TNestedScope.Create(LScope, TBindingMethodsFactory.GetMethodScope);
end;

function CalculateExpression(const Expr: string): string;
var
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
begin
  Result := '';

  try
    LCompiledExpr := Compile(Expr, LScope);
    LResult := LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
    if not LResult.IsEmpty then
      Result := LResult.ToString;
  except
    Result := '';
	  MessageBox(0, PChar(TextByKey('formula-cannotevaluate')),
        'Apophysis AV', 16);
  end;
end;

///////////////////////////////////////////////////////////////////////////////
initialization
  RegisterMathFunctions;

end.
