unit Unit1;

{
  Demo: Master-Detail PDF Raporu
  -------------------------------------------------------------------
  Northwind db.db  > customers (master) + orders (detail)

  Rapor sablonlari (.frp) Demos\Raporlar\ klasorunde haziri durumda.
  Cihaza Documents klasorune deploy edilmeli (logo.png ile birlikte).

  QryOrders SQL'inde WHERE yazmaya GEREK YOK;
  FireDAC, MasterSource/MasterFields ile filtrelemeyi otomatik yapar.
}

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,
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
  FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async,
  FireDAC.Phys, FireDAC.FMXUI.Wait, FireDAC.Stan.Param, FireDAC.DatS,
  FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Stan.ExprFuncs,
  FireDAC.Phys.SQLiteWrapper.Stat, FireDAC.Phys.SQLiteDef, FireDAC.Phys.SQLite,
  FireDAC.Comp.Client, Data.DB, FireDAC.UI.Intf, FireDAC.Phys.Intf,
  FireDAC.Comp.DataSet,
  PDFDrawing, uReportEngine, uRptDataSet, FMX.ListBox;

type
  TForm1 = class(TForm)
    ToolBar1      : TToolBar;
    lblTitle      : TLabel;
    Layout1       : TLayout;
    lblInfo       : TLabel;
    btnShowReport    : TButton;
    btnOpenDesigner  : TButton;
    btnPreview       : TButton;
    lblSampleHdr     : TLabel;
    cbxSample        : TComboBox;
    btnRunSample     : TButton;
    btnPrintSample   : TButton;
    lblStatus        : TLabel;
    PDFDrawing1   : TPDFDrawing;
    ReportEngine1 : TReportEngine;
    FDConn        : TFDConnection;
    FDSQLiteDrv   : TFDPhysSQLiteDriverLink;
    QryCustomers  : TFDQuery;
    DSCustomers   : TDataSource;
    QryOrders     : TFDQuery;
    RptDataSet1: TRptDataSet;
    RptDataSet2: TRptDataSet;
    DSOrders   : TDataSource;
    QryItems   : TFDQuery;
    RptDataSet3: TRptDataSet;
    QryCustomersID: TFDAutoIncField;
    QryCustomersCustomerID: TWideMemoField;
    QryCustomersCompanyName: TWideMemoField;
    QryCustomersContactName: TWideMemoField;
    QryCustomersContactTitle: TWideMemoField;
    QryCustomersAddress: TWideMemoField;
    QryCustomersCity: TWideMemoField;
    QryCustomersRegion: TWideMemoField;
    QryCustomersPostalCode: TWideMemoField;
    QryCustomersCountry: TWideMemoField;
    QryCustomersPhone: TWideMemoField;
    QryCustomersFax: TWideMemoField;
    QryOrdersOrderID: TFDAutoIncField;
    QryOrdersCustomerID: TWideMemoField;
    QryOrdersEmployeeID: TIntegerField;
    QryOrdersOrderDate: TWideMemoField;
    QryOrdersRequiredDate: TWideMemoField;
    QryOrdersShippedDate: TWideMemoField;
    QryOrdersShipVia: TIntegerField;
    QryOrdersFreight: TLargeintField;
    QryOrdersShipName: TWideMemoField;
    QryOrdersShipAddress: TWideMemoField;
    QryOrdersShipCity: TWideMemoField;
    QryOrdersShipRegion: TWideMemoField;
    QryOrdersShipPostalCode: TWideMemoField;
    QryOrdersShipCountry: TWideMemoField;
    QryItemsItemID: TFDAutoIncField;
    QryItemsOrderID: TIntegerField;
    QryItemsProductName: TWideMemoField;
    QryItemsQuantity: TIntegerField;
    QryItemsUnitPrice: TFloatField;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnShowReportClick(Sender: TObject);
    procedure btnOpenDesignerClick(Sender: TObject);
    procedure btnRunSampleClick(Sender: TObject);
    procedure btnPrintSampleClick(Sender: TObject);
    procedure btnPreviewClick(Sender: TObject);
  private
    FReportFile: string;
    procedure SetStatus(const AMsg: string);
    procedure OpenDatabase;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  uPDFOpen
  {$IFDEF ANDROID}
  , Androidapi.AssetManager, Androidapi.NativeActivity
  {$ENDIF};

// -----------------------------------------------------------------------------
// Ornek raporlar listesi - ComboBox bunu kullanir
// -----------------------------------------------------------------------------
const
  // FileBase containing ';' = multi-template (ShowReports), otherwise single (ShowReport)
  SAMPLES: array[0..19] of record Caption, FileBase: string end = (
    (Caption: 'All Controls (A4 Portrait)';                  FileBase: 'ornek_tum_kontroller_A4'),
    (Caption: 'Business Card (A5 Landscape)';                FileBase: 'ornek_kartvizit_A5_yatay'),
    (Caption: 'Certificate (A5 Portrait)';                   FileBase: 'ornek_belge_A5_dikey'),
    (Caption: 'Group: By Country (A4)';                      FileBase: 'ornek_grup_ulke_A4'),
    (Caption: 'Group: Country / City (A4)';                  FileBase: 'ornek_grup_ulke_sehir_A4'),
    (Caption: '3-Level Master-Detail (A4)';                  FileBase: 'ornek_master_detail_subdetail_A4'),
    (Caption: 'Expressions Demo (A4)';                       FileBase: 'ornek_ifadeler_A4'),
    (Caption: 'Mixed: Group + Detail + Expressions (A4)';    FileBase: 'ornek_karma_A4'),
    (Caption: 'Customer Card (A5 Landscape)';                FileBase: 'ornek_musteri_karti_A5_yatay'),
    (Caption: 'Report Bundle: Cover + Summary + Detail';
     FileBase: 'ornek_paket_kapak;ornek_paket_ozet;ornek_paket_detay'),
    (Caption: 'Barcode + QR Customer Card (A5 Landscape)';   FileBase: 'ornek_kart_barkod_qr_A5'),
    (Caption: 'Barcode + QR Invoice (A4 Portrait)';          FileBase: 'ornek_fatura_barkod_qr_A4'),
    (Caption: 'vCard QR Info Card (A5 Landscape)';           FileBase: 'ornek_vcard_qr_A5'),
    (Caption: 'Order + Product Labels (A4 Portrait)';        FileBase: 'ornek_siparis_etiket_A4'),
    (Caption: 'Aggregates: SUM/AVG/COUNT/MIN/MAX (A4)';      FileBase: 'ornek_aggregate_orders_A4'),
    (Caption: 'Table Element Demo (A4 Portrait)';            FileBase: 'ornek_tablo_A4'),
    (Caption: 'New Features: RecordNo / Qualified / FORMAT / Metadata (A4)';
                                                             FileBase: 'ornek_yeni_ozellikler_A4'),
    (Caption: 'Data-Bound Table (auto-grow + paging, A4)';   FileBase: 'ornek_veri_tablo_A4'),
    (Caption: 'Subreport (flowing child .frp, A4)';           FileBase: 'ornek_alt_rapor_A4'),
    (Caption: 'Subreport: Grouped child (A4)';                FileBase: 'ornek_alt_rapor_grup_A4')
  );

{$IFDEF ANDROID}
// System.StartUpCopy yalnizca dosya YOKSA kopyalar. Bu yardimci, APK assets
// icindeki dosyayi her zaman uzerine yazar - boylece tasarim guncellemeleri
// her deploy sonrasi cihazda anlik gorunur.
procedure ForceCopyAssetToDocs(const FileName: string);
const
  ASSET_PATH = 'internal/';
var
  AM: PAAssetManager;
  Asset: PAAsset;
  Buffer: array[0..65535] of Byte;
  ReadCount: Integer;
  FH: THandle;
  DocPath: string;
  M: TMarshaller;
begin
  if System.DelphiActivity = nil then Exit;
  AM := PANativeActivity(System.DelphiActivity)^.assetManager;
  if AM = nil then Exit;
  Asset := AAssetManager_open(AM,
             M.AsUtf8(ASSET_PATH + FileName).ToPointer,
             AASSET_MODE_BUFFER);
  if Asset = nil then Exit;
  try
    DocPath := TPath.Combine(TPath.GetDocumentsPath, FileName);
    FH := FileCreate(DocPath);  // overwrite
    if FH = THandle(-1) then Exit;
    try
      repeat
        ReadCount := AAsset_read(Asset, @Buffer[0], SizeOf(Buffer));
        if ReadCount > 0 then
          FileWrite(FH, Buffer, ReadCount);
      until ReadCount <= 0;
    finally
      FileClose(FH);
    end;
  finally
    AAsset_close(Asset);
  end;
end;

procedure RefreshSampleReports;
var
  I, J: Integer;
  Base: string;
  Parts: TArray<string>;
begin
  for I := Low(SAMPLES) to High(SAMPLES) do
  begin
    Base := SAMPLES[I].FileBase;
    if Pos(';', Base) > 0 then
    begin
      Parts := Base.Split([';']);
      for J := 0 to High(Parts) do
        ForceCopyAssetToDocs(Parts[J] + '.frp');
    end
    else
      ForceCopyAssetToDocs(Base + '.frp');
  end;
  ForceCopyAssetToDocs('musteri_raporu.frp');
  // Subreport child (SAMPLES'ta degil; ana rapor tarafindan yuklenir)
  ForceCopyAssetToDocs('ornek_alt_rapor_detay.frp');
  ForceCopyAssetToDocs('ornek_alt_rapor_grup_detay.frp');
end;
{$ENDIF}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  {$IFDEF ANDROID}
  // APK assets/internal/*.frp -> Documents (her seferinde overwrite)
  RefreshSampleReports;
  // Android: assets/internal icerisine deploy edilen yol
  FReportFile := TPath.Combine(TPath.GetDocumentsPath, 'musteri_raporu.frp');
  {$ELSE}
  // Windows: once exe'nin yaninda (musteri layout: exe + .frp + Raporlar\),
  // yoksa proje icindeki Demos\ klasoru (IDE'den calistirma).
  FReportFile := TPath.Combine(ExtractFilePath(ParamStr(0)), 'musteri_raporu.frp');
  if not TFile.Exists(FReportFile) then
    FReportFile := ExpandFileName(
      TPath.Combine(TPath.Combine(ExtractFilePath(ParamStr(0)), '..\..\'), 'musteri_raporu.frp'));
  {$ENDIF}

  // ComboBox'i ornek listesinden doldur
  cbxSample.Items.BeginUpdate;
  try
    cbxSample.Items.Clear;
    for I := Low(SAMPLES) to High(SAMPLES) do
      cbxSample.Items.Add(SAMPLES[I].Caption);
  finally
    cbxSample.Items.EndUpdate;
  end;
  cbxSample.ItemIndex := 0;

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

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

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

procedure TForm1.OpenDatabase;
var
  DBFile: string;
begin
  {$IFDEF ANDROID}
  DBFile := TPath.Combine(TPath.GetDocumentsPath, 'db5.db');
  {$ELSE}
  DBFile := TPath.Combine(ExtractFilePath(ParamStr(0)), 'db5.db');
  if not TFile.Exists(DBFile) then
    DBFile := ExpandFileName(
      TPath.Combine(TPath.Combine(ExtractFilePath(ParamStr(0)), '..\..\'), 'db5.db'));
  {$ENDIF}

  if not TFile.Exists(DBFile) then
  begin
    SetStatus('db5.db not found: ' + DBFile);
    Exit;
  end;

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

  // Master: tum musteriler - SELECT * kullanarak persistent field'larla uyumlu kaliyoruz
  QryCustomers.SQL.Text := 'SELECT * FROM customers ORDER BY Country, CompanyName';
  QryCustomers.Open;

  // Detail: FireDAC, MasterSource/MasterFields ile CustomerID'yi otomatik baglar.
  QryOrders.SQL.Text := 'SELECT * FROM orders';
  QryOrders.Open;

  // SubDetail (3. seviye): order kalemleri - DSOrders ile OrderID otomatik baglanir
  QryItems.SQL.Text := 'SELECT * FROM orderitems';
  QryItems.Open;

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

procedure TForm1.btnShowReportClick(Sender: TObject);
begin
  if (not QryCustomers.Active) or QryCustomers.IsEmpty then
  begin
    SetStatus('No database data. Please restart the application.');
    Exit;
  end;
  try
    SetStatus('Preparing report...');
    ReportEngine1.SetVariable('Company', 'Acme Trading Co.');
    ReportEngine1.ShowReport(FReportFile,
      TPath.Combine(TPath.GetDocumentsPath, 'musteri_raporu.pdf'));
    SetStatus('Ready.');
  except
    on E: Exception do
      SetStatus('ERROR: ' + E.Message);
  end;
end;

procedure TForm1.btnOpenDesignerClick(Sender: TObject);
begin
  ReportEngine1.OpenDesigner(FReportFile, Self);
end;

// Secili ornek raporu dogrudan yaziciya gonderir (onizlemesiz, Windows).
procedure TForm1.btnPrintSampleClick(Sender: TObject);
{$IFDEF MSWINDOWS}
var
  Idx            : Integer;
  Base           : string;
  Parts          : TArray<string>;
  RaporlarDir    : string;
  FrpPath        : string;
  SavedMasterSrc : TDataSource;
  UnlinkOrders   : Boolean;
{$ENDIF}
begin
  {$IFDEF MSWINDOWS}
  Idx := cbxSample.ItemIndex;
  if Idx < 0 then
  begin
    SetStatus('Please select a sample first.');
    Exit;
  end;
  Base := SAMPLES[Idx].FileBase;
  if Pos(';', Base) > 0 then
  begin
    Parts := Base.Split([';']);
    Base  := Parts[0];   // coklu sablon: ilk parcayi yazdir
  end;
  RaporlarDir := TPath.Combine(ExtractFilePath(FReportFile), 'Raporlar');
  FrpPath     := TPath.Combine(RaporlarDir, Base + '.frp');

  // Aggregate / Yeni Ozellikler: tum siparisleri gostermek icin master-detail ac
  UnlinkOrders := (Pos('aggregate', LowerCase(Base)) > 0) or
                  (Pos('yeni_ozellikler', LowerCase(Base)) > 0);
  SavedMasterSrc := nil;
  if UnlinkOrders then
  begin
    SavedMasterSrc := QryOrders.MasterSource;
    QryOrders.MasterSource := nil;
    QryOrders.Close;
    QryOrders.IndexFieldNames := 'ShipCountry';
    QryOrders.Open;
  end;

  try
    ReportEngine1.SetVariable('Company', 'Acme Trading Co.');
    SetStatus('Printing: ' + Base + ' ...');
    ReportEngine1.PrintReport(FrpPath);
    SetStatus('Sent to printer: ' + Base);
  except
    on E: Exception do
      SetStatus('PRINT ERROR: ' + E.Message);
  end;

  if UnlinkOrders then
  begin
    QryOrders.Close;
    QryOrders.IndexFieldNames := '';
    QryOrders.MasterSource := SavedMasterSrc;
    QryOrders.Open;
  end;
  {$ELSE}
  SetStatus('Direct printing is only available on Windows.');
  {$ENDIF}
end;

procedure TForm1.btnPreviewClick(Sender: TObject);
begin
  {$IFDEF MSWINDOWS}
  if FReportFile = '' then
  begin
    SetStatus('No report file selected.');
    Exit;
  end;
  ReportEngine1.Preview(FReportFile);
  {$ELSE}
  SetStatus('Report preview is only available on Windows.');
  {$ENDIF}
end;

// -----------------------------------------------------------------------------
// Ornek raporlar: ComboBox'tan secilen .frp dosyasini Documents klasorunden
// okuyup PDF uretir. Sablon dosyalari proje Demos\Raporlar\ klasoru icinde -
// cihaza Documents yoluna deploy edilmis olmali (logo.png ile birlikte).
// -----------------------------------------------------------------------------

procedure TForm1.btnRunSampleClick(Sender: TObject);
var
  Idx     : Integer;
  Base    : string;
  Files   : TArray<string>;
  Parts   : TArray<string>;
  OutFile : string;
  I       : Integer;
  SavedMasterSrc: TDataSource;
  UnlinkOrders  : Boolean;
  {$IFDEF MSWINDOWS}
  RaporlarDir: string;
  {$ENDIF}
begin
  Idx := cbxSample.ItemIndex;
  if Idx < 0 then
  begin
    SetStatus('Please select a sample first.');
    Exit;
  end;
  Base := SAMPLES[Idx].FileBase;

  // Aggregate + Yeni Ozellikler demolari: tum siparisleri gostermek icin
  // master-detail filtresini gecici olarak ac (orders bagimsiz iterasyon).
  UnlinkOrders := (Pos('aggregate', LowerCase(Base)) > 0) or
                  (Pos('yeni_ozellikler', LowerCase(Base)) > 0);
  SavedMasterSrc := nil;
  if UnlinkOrders then
  begin
    SavedMasterSrc := QryOrders.MasterSource;
    QryOrders.MasterSource := nil;
    QryOrders.Close;
    QryOrders.IndexFieldNames := 'ShipCountry';
    QryOrders.Open;
  end;

  try
    ReportEngine1.SetVariable('Company', 'Acme Trading Co.');

    {$IFDEF MSWINDOWS}
    // Windows: raporu bilesenin icinde onizle (Raporlar\ alt klasorunden)
    RaporlarDir := TPath.Combine(ExtractFilePath(FReportFile), 'Raporlar');
    if Pos(';', Base) > 0 then
    begin
      Parts := Base.Split([';']);
      SetStatus('Preview: ' + Parts[0]);
      ReportEngine1.Preview(TPath.Combine(RaporlarDir, Parts[0] + '.frp'));
    end
    else
    begin
      SetStatus('Preview: ' + Base);
      ReportEngine1.Preview(TPath.Combine(RaporlarDir, Base + '.frp'));
    end;
    {$ELSE}
    // Android: PDF uret ve ac
    if Pos(';', Base) > 0 then
    begin
      // Coklu sablon - her parca ayri .frp dosyasi, hepsi tek PDF'e
      Parts := Base.Split([';']);
      SetLength(Files, Length(Parts));
      for I := 0 to High(Parts) do
        Files[I] := TPath.Combine(TPath.GetDocumentsPath, Parts[I] + '.frp');
      OutFile := TPath.Combine(TPath.GetDocumentsPath, Parts[0] + '_paket.pdf');
      SetStatus('Preparing multi-template: ' + IntToStr(Length(Files)) + ' parts');
      ReportEngine1.ShowReports(Files, OutFile);
      SetStatus('PDF: ' + ExtractFileName(OutFile));
    end
    else
    begin
      // Tek sablon
      SetStatus('Preparing report: ' + Base);
      ReportEngine1.ShowReport(
        TPath.Combine(TPath.GetDocumentsPath, Base + '.frp'),
        TPath.Combine(TPath.GetDocumentsPath, Base + '.pdf'));
      SetStatus('PDF: ' + Base + '.pdf');
    end;
    {$ENDIF}
  except
    on E: Exception do
      SetStatus('ERROR: ' + E.Message);
  end;

  if UnlinkOrders then
  begin
    QryOrders.Close;
    QryOrders.IndexFieldNames := '';
    QryOrders.MasterSource := SavedMasterSrc;
    QryOrders.Open;
  end;
end;

end.
