unit uMainWin;

{
  FmxPdfTools - Windows Demo
  ===========================================================================
  A desktop workstation for the report engine. On Windows the engine renders
  through its vector pipeline, so this demo showcases the Windows-only surface
  of TReportEngine:

      Preview      - on-screen vector preview (zoom, print, export)
      ExportPDF    - selectable-text vector PDF to a chosen file
      PrintReport  - send straight to a printer
      OpenDesigner - launch the visual .frp designer on the selected template

  It also demonstrates report variables (Company, DocNo, Address tokens),
  PDF /Info metadata and the render lifecycle events (OnBeforeRender,
  OnPageRendered, OnAfterRender) which are written to the activity log.

  The English sample templates live in ..\Raporlar and are produced by the
  ReportGen tool (see Demos\ReportGen). The Northwind subset db5.db supplies
  the customers / orders / items datasets.
}

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.IOUtils,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.ListBox,
  FMX.Edit, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox,
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLiteDef,
  FireDAC.Phys.SQLite, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf,
  FireDAC.DApt, FireDAC.Phys.Intf, FireDAC.UI.Intf, FireDAC.FMXUI.Wait,
  FireDAC.Comp.Client, FireDAC.Comp.DataSet, Data.DB,
  PDFDrawing, uReportEngine, uRptDataSet;

type
  TWinDemoForm = class(TForm)
    // --- Data layer (Northwind subset, master -> detail -> sub-detail) ----
    FDConn       : TFDConnection;
    FDSQLiteDrv  : TFDPhysSQLiteDriverLink;
    QryCustomers : TFDQuery;
    DSCustomers  : TDataSource;
    QryOrders    : TFDQuery;
    DSOrders     : TDataSource;
    QryItems     : TFDQuery;
    // --- Report components ------------------------------------------------
    PDFDrawing1  : TPDFDrawing;
    ReportEngine1: TReportEngine;
    RptCustomers : TRptDataSet;
    RptOrders    : TRptDataSet;
    RptItems     : TRptDataSet;
    // --- UI ---------------------------------------------------------------
    ToolBar1     : TToolBar;
    lblTitle     : TLabel;
    Root         : TLayout;
    pnlLeft      : TLayout;
    lblPick      : TLabel;
    lstSamples   : TListBox;
    pnlRight     : TLayout;
    lblDesc      : TLabel;
    grpVars      : TGroupBox;
    lblCompany   : TLabel;
    edCompany    : TEdit;
    lblDocNo     : TLabel;
    edDocNo      : TEdit;
    lblAddress   : TLabel;
    edAddress    : TEdit;
    lblPrinter   : TLabel;
    edPrinter    : TEdit;
    btnPreview   : TButton;
    btnExport    : TButton;
    btnPrint     : TButton;
    btnDesigner  : TButton;
    lblLog       : TLabel;
    mmoLog       : TMemo;
    lblStatus    : TLabel;
    SaveDlg      : TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure lstSamplesChange(Sender: TObject);
    procedure btnPreviewClick(Sender: TObject);
    procedure btnExportClick(Sender: TObject);
    procedure btnPrintClick(Sender: TObject);
    procedure btnDesignerClick(Sender: TObject);
  private
    function  ReportsDir: string;
    function  FirstPart(const ABase: string): string;     // path of first .frp
    function  AllParts(const ABase: string): TArray<string>; // all .frp parts
    procedure ApplyContext;                                // vars + dataset mode
    procedure RestoreDataset;
    procedure Log(const AMsg: string);
    procedure SetStatus(const AMsg: string);
    procedure OpenDatabase;
    // Render lifecycle events surfaced in the activity log.
    procedure EngineBeforeRender(Sender: TObject);
    procedure EnginePageRendered(Sender: TObject; PageNum, TotalPages: Integer);
    procedure EngineAfterRender(Sender: TObject);
  end;

var
  WinDemoForm: TWinDemoForm;

implementation

{$R *.fmx}

// ---------------------------------------------------------------------------
// Sample catalogue. FileBase may join several templates with ';' (a bundle).
// Unlink = temporarily detach the orders master/detail link so the template
// iterates EVERY order (used by whole-company statements and label sheets).
// ---------------------------------------------------------------------------
type
  TSample = record
    Caption  : string;
    FileBase : string;
    Desc     : string;
    Unlink   : Boolean;
    SortField: string;   // optional ordering applied while unlinked
  end;

const
  SAMPLES: array[0..8] of TSample = (
    (Caption: 'Sales Statement / Invoice';
     FileBase: 'sample_invoice_A4';
     Desc: 'Branded statement: logo image, bill-to variables, Code-128 + QR, a row per order and SUM/AVG/COUNT totals in the summary band.';
     Unlink: True; SortField: 'ShipCountry'),
    (Caption: 'Business Cards';
     FileBase: 'sample_business_card_A5L';
     Desc: 'One card per customer (A5 landscape). RoundRect / Circle / Ellipse / Line shapes, logo image and a scannable vCard QR code.';
     Unlink: False; SortField: ''),
    (Caption: 'Customer Directory (grouped)';
     FileBase: 'sample_catalog_grouped_A4';
     Desc: 'Customers grouped by country with a GroupHeader banner, [RecordNo] numbering, per-group COUNT subtotal and a grand total.';
     Unlink: False; SortField: ''),
    (Caption: 'Account Statement (3 levels)';
     FileBase: 'sample_statement_3level_A4';
     Desc: 'customers -> orders -> items master/detail/sub-detail, qualified [orders.field] access and an RTF note.';
     Unlink: False; SortField: ''),
    (Caption: 'Certificate of Partnership';
     FileBase: 'sample_certificate_A5';
     Desc: 'A decorative single page: nested borders, a wax-seal built from circles, centered typography, RTF body and the [Date] token.';
     Unlink: False; SortField: ''),
    (Caption: 'Feature Showcase';
     FileBase: 'sample_showcase_A4';
     Desc: 'Reference sheet: every element type plus live math / text / date / IIF / AND-OR-NOT / FORMAT expression results.';
     Unlink: False; SortField: ''),
    (Caption: 'Shipping Labels';
     FileBase: 'sample_labels_A4';
     Desc: 'One bordered label card per order with a barcode and an IIF priority badge (express vs standard).';
     Unlink: True; SortField: 'ShipCountry'),
    (Caption: 'Quarterly Pack (3-part bundle)';
     FileBase: 'sample_bundle_cover_A4;sample_bundle_summary_A4;sample_bundle_detail_A4';
     Desc: 'Cover + aggregate summary + data-bound detail table. On mobile the three parts merge into one PDF (ShowReports); on Windows each part previews on its own.';
     Unlink: True; SortField: 'ShipVia'),
    (Caption: 'Regional Roll-up (subreport)';
     FileBase: 'sample_subreport_main_A4';
     Desc: 'A host page that embeds a flowing sub-report; the child runs its own data loop and paginates inside the host.';
     Unlink: False; SortField: '')
  );

{ TWinDemoForm }

procedure TWinDemoForm.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  // Populate the sample list.
  lstSamples.BeginUpdate;
  try
    for I := Low(SAMPLES) to High(SAMPLES) do
      lstSamples.Items.Add(SAMPLES[I].Caption);
  finally
    lstSamples.EndUpdate;
  end;
  lstSamples.ItemIndex := 0;

  // Default report variables (consumed by {Company}/{DocNo}/{Address} tokens).
  edCompany.Text := 'ACME Trading Co.';
  edDocNo.Text   := 'STMT-2026-0042';
  edAddress.Text := '12 Harbour Road, Rotterdam, NL';
  // Target printer for "Print to named printer". Leave blank to use the
  // system default. The engine resolves the name with SelectPrinterByName and
  // falls back to the active printer if the name is not installed.
  edPrinter.Text := 'HP LaserJet 1020';

  // Document-level PDF metadata (engine property overrides the .frp value).
  ReportEngine1.PdfAuthor  := 'FmxPdfTools Windows Demo';
  ReportEngine1.PdfCreator := 'FmxPdfTools';

  // Surface the render lifecycle in the log.
  ReportEngine1.OnBeforeRender := EngineBeforeRender;
  ReportEngine1.OnPageRendered := EnginePageRendered;
  ReportEngine1.OnAfterRender  := EngineAfterRender;

  lstSamplesChange(nil);

  try
    OpenDatabase;
  except
    on E: Exception do
      SetStatus('Database error: ' + E.Message);
  end;
end;

procedure TWinDemoForm.FormDestroy(Sender: TObject);
begin
  QryItems.Close;
  QryOrders.Close;
  QryCustomers.Close;
  if FDConn.Connected then
    FDConn.Connected := False;
end;

function TWinDemoForm.ReportsDir: string;
begin
  // 1) Shipped next to the executable (customer layout: exe + db5.db + Raporlar\).
  Result := TPath.Combine(ExtractFilePath(ParamStr(0)), 'Raporlar');
  if TDirectory.Exists(Result) then Exit;
  // 2) Development tree (running from the IDE: Demos\Windows\Win32\<cfg>).
  Result := TPath.GetFullPath(
    TPath.Combine(ExtractFilePath(ParamStr(0)), '..\..\..\Raporlar'));
  if not TDirectory.Exists(Result) then
    Result := TPath.GetFullPath(
      TPath.Combine(ExtractFilePath(ParamStr(0)), '..\Raporlar'));
end;

function TWinDemoForm.FirstPart(const ABase: string): string;
var
  P: TArray<string>;
begin
  P := ABase.Split([';']);
  Result := TPath.Combine(ReportsDir, P[0] + '.frp');
end;

function TWinDemoForm.AllParts(const ABase: string): TArray<string>;
var
  P: TArray<string>;
  I: Integer;
begin
  P := ABase.Split([';']);
  SetLength(Result, Length(P));
  for I := 0 to High(P) do
    Result[I] := TPath.Combine(ReportsDir, P[I] + '.frp');
end;

procedure TWinDemoForm.OpenDatabase;
var
  DBFile: string;
begin
  DBFile := TPath.Combine(ExtractFilePath(ParamStr(0)), 'db5.db');
  if not TFile.Exists(DBFile) then
    DBFile := TPath.GetFullPath(
      TPath.Combine(ExtractFilePath(ParamStr(0)), '..\..\..\db5.db'));
  if not TFile.Exists(DBFile) then
  begin
    SetStatus('db5.db not found next to the executable.');
    Exit;
  end;

  FDConn.Params.Clear;
  FDConn.Params.Add('DriverID=SQLite');
  FDConn.Params.Add('Database=' + DBFile);
  FDConn.LoginPrompt := False;
  FDConn.Connected   := True;

  // Master sorted by country so the grouped directory groups cleanly.
  QryCustomers.SQL.Text := 'SELECT * FROM customers ORDER BY Country, CompanyName';
  QryCustomers.Open;
  QryOrders.SQL.Text := 'SELECT * FROM orders';     // auto-filtered by MasterSource
  QryOrders.Open;
  QryItems.SQL.Text := 'SELECT * FROM orderitems';  // auto-filtered by DSOrders
  QryItems.Open;

  SetStatus(Format('Ready - %d customers loaded.', [QryCustomers.RecordCount]));
end;

procedure TWinDemoForm.lstSamplesChange(Sender: TObject);
var
  I: Integer;
begin
  I := lstSamples.ItemIndex;
  if I < 0 then Exit;
  lblDesc.Text := SAMPLES[I].Desc;
  // Variables only matter for the statement/invoice; keep the panel enabled
  // everywhere so users can experiment.
end;

// Push the current variables into the engine and, when required, unlink the
// orders dataset so a template can iterate every order.
procedure TWinDemoForm.ApplyContext;
var
  I: Integer;
begin
  ReportEngine1.SetVariable('Company', edCompany.Text);
  ReportEngine1.SetVariable('DocNo',   edDocNo.Text);
  ReportEngine1.SetVariable('Address', edAddress.Text);

  I := lstSamples.ItemIndex;
  if (I >= 0) and SAMPLES[I].Unlink and (QryOrders.MasterSource <> nil) then
  begin
    QryOrders.MasterSource := nil;
    QryOrders.Close;
    if SAMPLES[I].SortField <> '' then
      QryOrders.IndexFieldNames := SAMPLES[I].SortField;
    QryOrders.Open;
  end;
end;

procedure TWinDemoForm.RestoreDataset;
begin
  if QryOrders.MasterSource = nil then
  begin
    QryOrders.Close;
    QryOrders.IndexFieldNames := '';
    QryOrders.MasterSource := DSCustomers;
    QryOrders.Open;
  end;
end;

procedure TWinDemoForm.btnPreviewClick(Sender: TObject);
var
  I: Integer;
  Base: string;
begin
  I := lstSamples.ItemIndex;
  if I < 0 then Exit;
  Base := SAMPLES[I].FileBase;
  ApplyContext;
  try
    SetStatus('Preview: ' + SAMPLES[I].Caption);
    if Pos(';', Base) > 0 then
    begin
      Log('Bundle: previewing all parts merged into one paged document.');
      ReportEngine1.Preview(AllParts(Base));   // multi-file vector preview
    end
    else
      ReportEngine1.Preview(FirstPart(Base));  // vector preview window
  except
    on E: Exception do SetStatus('Preview error: ' + E.Message);
  end;
  RestoreDataset;
end;

procedure TWinDemoForm.btnExportClick(Sender: TObject);
var
  I: Integer;
  Base, OutFile: string;
begin
  I := lstSamples.ItemIndex;
  if I < 0 then Exit;
  Base := SAMPLES[I].FileBase;
  SaveDlg.FileName := Base.Split([';'])[0] + '.pdf';
  if not SaveDlg.Execute then Exit;
  OutFile := SaveDlg.FileName;

  ApplyContext;
  try
    SetStatus('Exporting PDF...');
    if Pos(';', Base) > 0 then
      ReportEngine1.ExportPDF(AllParts(Base), OutFile)   // merged bundle PDF
    else
      ReportEngine1.ExportPDF(FirstPart(Base), OutFile); // selectable-text PDF
    Log('Exported: ' + OutFile);
    SetStatus('Exported: ' + ExtractFileName(OutFile));
  except
    on E: Exception do SetStatus('Export error: ' + E.Message);
  end;
  RestoreDataset;
end;

procedure TWinDemoForm.btnPrintClick(Sender: TObject);
var
  I: Integer;
  Printer: string;
begin
  I := lstSamples.ItemIndex;
  if I < 0 then Exit;
  ApplyContext;
  Printer := Trim(edPrinter.Text);
  try
    if Printer <> '' then
    begin
      // Named-printer overload: the engine calls SelectPrinterByName(Printer)
      // before printing. If that printer is not installed it prints on the
      // current default instead (FastReport-style fallback).
      SetStatus('Printing to "' + Printer + '"...');
      ReportEngine1.PrintReport(FirstPart(SAMPLES[I].FileBase), Printer);
      SetStatus('Sent to printer: ' + Printer);
    end
    else
    begin
      SetStatus('Printing to the default printer...');
      ReportEngine1.PrintReport(FirstPart(SAMPLES[I].FileBase));
      SetStatus('Sent to the default printer.');
    end;
  except
    on E: Exception do SetStatus('Print error: ' + E.Message);
  end;
  RestoreDataset;
end;

procedure TWinDemoForm.btnDesignerClick(Sender: TObject);
var
  I: Integer;
begin
  I := lstSamples.ItemIndex;
  if I < 0 then Exit;
  // OpenDesigner writes the form's TRptDataSet names to datasources.json so the
  // designer's data tree shows customers / orders / items fields.
  ReportEngine1.OpenDesigner(FirstPart(SAMPLES[I].FileBase), Self);
end;

procedure TWinDemoForm.EngineBeforeRender(Sender: TObject);
begin
  Log('-> render started');
end;

procedure TWinDemoForm.EnginePageRendered(Sender: TObject; PageNum, TotalPages: Integer);
begin
  Log(Format('   page %d / %d', [PageNum, TotalPages]));
end;

procedure TWinDemoForm.EngineAfterRender(Sender: TObject);
begin
  Log('<- render finished');
end;

procedure TWinDemoForm.Log(const AMsg: string);
begin
  mmoLog.Lines.Add(FormatDateTime('hh:nn:ss', Now) + '  ' + AMsg);
  mmoLog.GoToTextEnd;
end;

procedure TWinDemoForm.SetStatus(const AMsg: string);
begin
  lblStatus.Text := AMsg;
end;

end.
