unit MainUnit;

interface

uses
    System.SysUtils,
    System.Types,
    System.UITypes,
    System.Classes,
    System.Variants,
    FMX.Types,
    FMX.Controls,
    FMX.Forms,
    FMX.Graphics,
    FMX.Dialogs,
    FMX.Controls.Presentation,
    FMX.StdCtrls,
    FMX.WebBrowser,
    Winapi.WebView2,
    FMX.Memo.Types,
    FMX.ScrollBox,
    FMX.Memo,
    FMX.Edit,
    FMX.StdActns,
    // FMX.Edit für TEdit, FMX.StdActns oft für Standardaktionen
    // Korrekte und vollständige Units
    System.IOUtils,
    System.Threading,
    System.Diagnostics,
    Winapi.Windows,
    LLVM.Runner;

const
    // Pfad zur HTML-Datei (BITTE HIER ANPASSEN!)
    PATH_BLOCKLY = 'T:\Myc\Blockly\index.html'; // <--- PRÜFEN UND ANPASSEN!

    // Pfade zu den LLVM-Werkzeugen (BITTE HIER ANPASSEN!)
    CLANG_PATH = 'clang.exe'; // <--- PRÜFEN UND ANPASSEN!
    LLD_PATH = 'lld-link.exe'; // <--- PRÜFEN UND ANPASSEN!

type
    TMainForm = class(TForm)
        WebBrowser1: TWebBrowser;
        SaveWorkspaceButton: TSpeedButton;
        GenerateCodeButton: TSpeedButton;
        Memo: TMemo;
        InputEdit: TEdit; // <--- Hinzugefügt (Optional für Beschriftung des InputEdit)
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure RefreshButtonClick(Sender: TObject);
        procedure SaveWorkspaceButtonClick(Sender: TObject);
        procedure GenerateCodeButtonClick(Sender: TObject);
        procedure WebBrowser1DidFinishLoad(ASender: TObject);
    private
        { Private declarations }
        FWebView: ICoreWebView2;
        FWebMessageReceiver: ICoreWebView2WebMessageReceivedEventHandler;
        FWebMessageReceivedToken: EventRegistrationToken;
        procedure HandleWebMessage(const AMessage: string);
        function ExecuteProcess(const ACommand: string; const AParameters: string): string;
    public
    { Public declarations }
    end;

var
    MainForm: TMainForm;

implementation

{$R *.fmx}

type
    TWebMessageReceiver = class(TInterfacedObject, ICoreWebView2WebMessageReceivedEventHandler)
    private
        FOwner: TMainForm;
    public
        constructor Create(AOwner: TMainForm);
        function Invoke(const Sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs): HResult; stdcall;
    end;

constructor TWebMessageReceiver.Create(AOwner: TMainForm);
begin
    inherited Create;
    FOwner := AOwner;
end;

function TWebMessageReceiver.Invoke(const Sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs): HResult;
var
    message: PWideChar;
begin
    Result := args.TryGetWebMessageAsString(message);
    if Succeeded(Result) then
    begin
        TThread.Queue(nil, procedure begin FOwner.HandleWebMessage(string(message)); end);
    end;
    Result := S_OK;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
    TLLVMRunner.Log := Memo.Lines;
    // Optional: Standardwert für InputEdit
    InputEdit.Text := '123';
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
    TLLVMRunner.Log := nil;
    if (FWebView <> nil) and (FWebMessageReceivedToken.value <> 0) then
    begin
        FWebView.remove_WebMessageReceived(FWebMessageReceivedToken);
    end;
end;

function TMainForm.ExecuteProcess(const ACommand: string; const AParameters: string): string;
var
    sa: TSecurityAttributes;
    si: TStartupInfo;
    pi: TProcessInformation;
    stdOutRead, stdOutWrite: THandle;
    success: Boolean;
    cmdLine: string;
    buffer: TBytes;
    bytesRead: Cardinal;
    output, errors: string;
begin
    // Pipe für StdOut und StdErr erstellen
    sa.nLength := SizeOf(TSecurityAttributes);
    sa.lpSecurityDescriptor := nil;
    sa.bInheritHandle := True;

    // Für stdout und stderr wird dieselbe Pipe verwendet, da clang Fehler auf stdout ausgibt
    if not CreatePipe(stdOutRead, stdOutWrite, @sa, 0) then
        Exit('Error creating pipe.');

    try
        // StartupInfo vorbereiten
        FillChar(si, SizeOf(TStartupInfo), 0);
        si.cb := SizeOf(TStartupInfo);
        si.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
        si.wShowWindow := SW_HIDE; // Fenster explizit verstecken
        si.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
        si.hStdOutput := stdOutWrite;
        si.hStdError := stdOutWrite; // Leite stderr auf dieselbe Pipe wie stdout

        cmdLine := Format('"%s" %s', [ACommand, AParameters]);

        success := CreateProcess(nil, PChar(cmdLine), nil, nil, True, CREATE_NO_WINDOW, nil, nil, si, pi);

        // Schreib-Handle der Pipe sofort schließen
        CloseHandle(stdOutWrite);

        if not success then
            Exit(Format('CreateProcess failed. Code: %d', [GetLastError]));

        try
            output := '';
            // Warten, bis der Prozess fertig ist und alle Daten in die Pipe geschrieben hat
            if WaitForSingleObject(pi.hProcess, 5000) = WAIT_TIMEOUT then // 5s Timeout
            begin
                TerminateProcess(pi.hProcess, 1);
                Exit('Process timed out.');
            end;

            // Bytes aus der Pipe lesen
            repeat
                SetLength(buffer, 1024);
                bytesRead := 0;
                if ReadFile(stdOutRead, buffer[0], Length(buffer), bytesRead, nil) and (bytesRead > 0) then
                begin
                    SetLength(buffer, bytesRead);
                    // Gelesene Bytes mit dem Default-System-Encoding in einen String umwandeln
                    output := output + TEncoding.Default.GetString(buffer);
                end
                else
                begin
                    break; // Pipe ist leer oder Fehler
                end;
            until False;

        finally
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        end;

        Result := output;

    finally
        CloseHandle(stdOutRead);
    end;
end;

procedure TMainForm.RefreshButtonClick(Sender: TObject);
begin
    WebBrowser1.Navigate(PATH_BLOCKLY);
end;

procedure TMainForm.SaveWorkspaceButtonClick(Sender: TObject);
begin
    WebBrowser1.EvaluateJavaScript('saveWorkspace()');
end;

procedure TMainForm.GenerateCodeButtonClick(Sender: TObject);
begin
    WebBrowser1.EvaluateJavaScript('generateAndPostCode()');
end;

procedure TMainForm.HandleWebMessage(const AMessage: string);
var
    llFile, objFile, dllFile: string;
    compilerOutput: string;
    success: Boolean;
    InputInteger: Integer; // <--- Hinzugefügt
begin
    Memo.Lines.Clear;
    Memo.Lines.Add('LLVM-Code empfangen. Starte Kompilierung...');
    Memo.Lines.Add(AMessage);
    Memo.Lines.Add('--------------------');

    // Wert aus dem InputEdit-Feld holen
    try
        InputInteger := StrToIntDef(InputEdit.Text, 0); // Standardwert 0, falls ungültig
    except
        InputInteger := 0;
        Memo.Lines.Add('WARNUNG: Ungültige Eingabe im InputEdit-Feld. Verwende 0.');
    end;

    TTask.Run(
        procedure
        var
            llFileLocal, objFileLocal, dllFileLocal: string; // Lokale Variablen für den Task
            compilerOutputLocal: string;
            successLocal: Boolean;
        begin
            llFileLocal := TPath.Combine(TPath.GetTempPath, 'poc.ll');
            objFileLocal := TPath.ChangeExtension(llFileLocal, '.obj');
            dllFileLocal := TPath.ChangeExtension(llFileLocal, '.dll');
            successLocal := False;

            try
                TFile.WriteAllText(llFileLocal, AMessage);

                TThread.Queue(nil, procedure begin Memo.Lines.Add('Kompiliere zu Objektdatei...'); end);
                compilerOutputLocal := ExecuteProcess(CLANG_PATH, Format('-c "%s" -o "%s"', [llFileLocal, objFileLocal]));
                TThread.Queue(nil, procedure begin Memo.Lines.Add(compilerOutputLocal); end);

                if not TFile.Exists(objFileLocal) then
                begin
                    TThread
                        .Queue(nil, procedure begin Memo.Lines.Add('FEHLER: Kompilierung fehlgeschlagen. .obj-Datei nicht erstellt.'); end);
                    Exit;
                end;

                TThread.Queue(nil, procedure begin Memo.Lines.Add('Linke zu DLL...'); end);
                compilerOutputLocal := ExecuteProcess(LLD_PATH, Format('/dll /noentry "%s" /out:"%s"', [objFileLocal, dllFileLocal]));
                TThread.Queue(nil, procedure begin Memo.Lines.Add(compilerOutputLocal); end);

                if TFile.Exists(dllFileLocal) then
                begin
                    successLocal := True;
                    TThread.Queue(nil, procedure begin Memo.Lines.Add(Format('ERFOLG: DLL wurde erstellt: %s', [dllFileLocal])); end);
                end
                else
                begin
                    TThread.Queue(nil, procedure begin Memo.Lines.Add('FEHLER: Linken fehlgeschlagen. .dll-Datei nicht erstellt.'); end);
                end;

            finally
                if TFile.Exists(llFileLocal) then
                    TFile.Delete(llFileLocal);
                if TFile.Exists(objFileLocal) then
                    TFile.Delete(objFileLocal);
                if not successLocal and TFile.Exists(dllFileLocal) then
                    TFile.Delete(dllFileLocal);
            end;

            TThread.Queue(
                nil,
                procedure
                begin
                    Memo.Lines.Add('---------------------------------------------------------');
                    Memo.Lines.Add('DLL wird aufgerufen:');

                    if TFile.Exists(dllFileLocal) then
                    begin
                        var Runner := TLLVMRunner.Create(dllFileLocal);
                        try
                            Runner.Execute(InputInteger); // <--- HIER DEN INPUT-PARAMETER ÜBERGEBEN
                        finally
                            Runner.Free;
                        end;
                    end;

                    Memo.Lines.Add('DLL beendet.');
                    Memo.CaretPosition := TCaretPosition.Create(Memo.Lines.Count - 1, 1);
                end
            );
        end
    );
end;

procedure TMainForm.WebBrowser1DidFinishLoad(ASender: TObject);
begin
    if Assigned(FWebMessageReceiver) then
        Exit;

    if Supports(WebBrowser1, ICoreWebView2, FWebView) then
    begin
        FWebMessageReceiver := TWebMessageReceiver.Create(Self);
        FWebView.add_WebMessageReceived(FWebMessageReceiver, FWebMessageReceivedToken);
    end;
end;

end.
