unit Aurelius.Design.ConnectionDesigner;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,

  Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ActnList, Vcl.Buttons,
  Vcl.Grids, Vcl.ValEdit,

  Aurelius.Drivers.Interfaces,
  Aurelius.Comp.Connection,
  Aurelius.Comp.Adapters,
  Aurelius.Comp.Drivers, Vcl.ComCtrls;

type
  TComboBox = class(Vcl.StdCtrls.TComboBox)
  protected
    procedure SetItemIndex(const Value: Integer); override;
  end;

  TfmConnectionDesigner = class(TForm)
    ActionList1: TActionList;
    actAdapterMode: TAction;
    actDriverMode: TAction;
    btOk: TBitBtn;
    btCancel: TBitBtn;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    rbAdapterMode: TRadioButton;
    rbDriverMode: TRadioButton;
    Label5: TLabel;
    Bevel1: TBevel;
    Bevel2: TBevel;
    nbConnectionMode: TNotebook;
    Label1: TLabel;
    Label2: TLabel;
    Label4: TLabel;
    cbAdaptedConnection: TComboBox;
    cbAdapters: TComboBox;
    cbSQLDialects: TComboBox;
    Label3: TLabel;
    cbDrivers: TComboBox;
    veParams: TValueListEditor;
    btResetToDefaultValues: TButton;
    btTestConnection: TButton;
    procedure FormShow(Sender: TObject);
    procedure cbAdaptedConnectionChange(Sender: TObject);
    procedure cbAdaptersChange(Sender: TObject);
    procedure actAdapterModeUpdate(Sender: TObject);
    procedure actDriverModeUpdate(Sender: TObject);
    procedure actAdapterModeExecute(Sender: TObject);
    procedure actDriverModeExecute(Sender: TObject);
    procedure cbDriversChange(Sender: TObject);
    procedure veParamsDrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure veParamsEditButtonClick(Sender: TObject);
    procedure btResetToDefaultValuesClick(Sender: TObject);
    procedure btOkClick(Sender: TObject);
    procedure ActionList1Update(Action: TBasicAction; var Handled: Boolean);
    procedure btTestConnectionClick(Sender: TObject);
  strict private
    FConnection: TAureliusConnection;
    FConnectionOwner: TComponent;
    function GetAdapter(AAdapterName: string): IAureliusAdapter;
    function GetDriver(ADriverName: string): IAureliusDriver;
    function GetParamDef(AParamName: string): TDriverParamDef;
    function ParamDefNotHasDefaultValue(AParamName: string; AParamValue: string): Boolean;
//    function ParamNotRegisteredInDriver(AParamName: string): Boolean;
  private
    { Private declarations }
    procedure LoadConfiguration;
    procedure LoadConnectionParamsValues;
    procedure EnableAdapterMode(AEnabled: boolean);
    procedure EnableDriverMode(AEnabled: boolean);
    procedure FillAdaptedConnectionList;
    procedure FillAdapterList;
    procedure FillSQLDialectList(AAdapterName: string);
    procedure FillDriverList;
    procedure FillParametersListEditor;
    function RetrieveSQLDialect(AAdaptedConnection: TComponent; AAdapterName: string): string;
    function GetAdapterNameFromAdaptedConnection(AAdaptedConnection: TComponent): string;
    procedure SaveConnectionProperties;
  public
    { Public declarations }
    class function Execute(Connection: TAureliusConnection): Boolean;
    constructor Create(AOwner: TComponent; AConnection: TAureliusConnection); reintroduce;
    destructor Destroy; override;
    function Connection: TAureliusConnection;
  end;

var
  fmConnectionDesigner: TfmConnectionDesigner;

implementation

{$R *.dfm}

type
  THackComponent = class(TComponent)
  end;

{ TfmConnectionDesigner }

procedure TfmConnectionDesigner.actAdapterModeExecute(Sender: TObject);
begin
  TAction(Sender).Checked := True;
end;

procedure TfmConnectionDesigner.actAdapterModeUpdate(Sender: TObject);
begin
  EnableAdapterMode(rbAdapterMode.Checked);
end;

procedure TfmConnectionDesigner.actDriverModeExecute(Sender: TObject);
begin
  TAction(Sender).Checked := True;
end;

procedure TfmConnectionDesigner.actDriverModeUpdate(Sender: TObject);
begin
  EnableDriverMode(rbDriverMode.Checked);
end;

procedure TfmConnectionDesigner.ActionList1Update(Action: TBasicAction;
  var Handled: Boolean);
begin
  nbConnectionMode.Visible := actAdapterMode.Checked or actDriverMode.Checked
end;

procedure TfmConnectionDesigner.btOkClick(Sender: TObject);
begin
  SaveConnectionProperties;
end;

procedure TfmConnectionDesigner.btResetToDefaultValuesClick(Sender: TObject);
var
  Driver: IAureliusDriver;
  ParamDef: TDriverParamDef;
begin
  Driver := GetDriver(cbDrivers.Text);
  for ParamDef in Driver.ParamDefs.GetParamDefs do
    veParams.Values[ParamDef.Name] := ParamDef.DefaultValue;
end;

procedure TfmConnectionDesigner.btTestConnectionClick(Sender: TObject);
var
  Connection: IDBConnection;
begin
  SaveConnectionProperties;
  try
    Connection := FConnection.CreateConnection;
    Connection.Connect;
    if Connection.IsConnected then
      ShowMessage('Connection established successfully.')
    else
      ShowMessage('Connection not established.');
    Connection.Disconnect;
  finally
    Connection := nil;
  end;
end;

procedure TfmConnectionDesigner.cbAdaptedConnectionChange(Sender: TObject);
var
  AdaptedConnection: TComponent;
  AdapterName: string;
  SQLDialect: string;
begin
  if cbAdaptedConnection.ItemIndex = -1 then
    Exit;
  AdaptedConnection := TComponent(cbAdaptedConnection.Items.Objects[cbAdaptedConnection.ItemIndex]);
  AdapterName := GetAdapterNameFromAdaptedConnection(AdaptedConnection);
  cbAdapters.ItemIndex := cbAdapters.Items.IndexOf(AdapterName);
  FillSQLDialectList(AdapterName);
  SQLDialect := RetrieveSQLDialect(AdaptedConnection, AdapterName);
  if SQLDialect <> '' then
    cbSQLDialects.ItemIndex := cbSQLDialects.Items.IndexOf(SQLDialect);
end;

procedure TfmConnectionDesigner.cbAdaptersChange(Sender: TObject);
var
  AdaptedConnection: TComponent;
  SQLDialect: string;
begin
  if cbAdapters.ItemIndex = -1 then
    Exit;
  if cbAdaptedConnection.ItemIndex = -1 then
    AdaptedConnection := nil
  else
    AdaptedConnection := TComponent(cbAdaptedConnection.Items.Objects[cbAdaptedConnection.ItemIndex]);
  if Assigned(AdaptedConnection) and not GetAdapter(cbAdapters.Text).IsAdaptedConnectionValid(AdaptedConnection) then
    cbAdaptedConnection.ItemIndex := -1;
  FillSQLDialectList(cbAdapters.Text);

  if Assigned(AdaptedConnection) then
    SQLDialect := RetrieveSQLDialect(AdaptedConnection, cbAdapters.Text)
  else
    SQLDialect := '';
  cbSQLDialects.ItemIndex := cbSQLDialects.Items.IndexOf(SQLDialect);
  if cbSQLDialects.ItemIndex < 0 then
    cbSQLDialects.Text := '';
end;

procedure TfmConnectionDesigner.cbDriversChange(Sender: TObject);
begin
  if cbDrivers.Text <> '' then
  begin
    FillParametersListEditor;
    LoadConnectionParamsValues;
  end;
end;

function TfmConnectionDesigner.Connection: TAureliusConnection;
begin
  Result := FConnection;
end;

constructor TfmConnectionDesigner.Create(AOwner: TComponent;
  AConnection: TAureliusConnection);
begin
  inherited Create(AOwner);
  FConnection := TAureliusConnection.Create(Self);
  THackComponent(FConnection).SetDesigning(True);
  FConnection.Name := AConnection.Name;
  FConnection.AdaptedConnection := AConnection.AdaptedConnection;
  FConnection.AdapterName := AConnection.AdapterName;
  FConnection.SQLDialect := AConnection.SQLDialect;
  FConnection.DriverName := AConnection.DriverName;
  FConnection.Params.AddStrings(AConnection.Params);
  FConnectionOwner := AConnection.Owner;
end;

destructor TfmConnectionDesigner.Destroy;
begin
  FConnection.Free;
  inherited;
end;

procedure TfmConnectionDesigner.EnableAdapterMode(AEnabled: boolean);
begin
  cbAdaptedConnection.Enabled := AEnabled;
  cbAdapters.Enabled := AEnabled;
  cbSQLDialects.Enabled := AEnabled;
  if AEnabled then
    nbConnectionMode.ActivePage := 'AdapterMode';
end;

procedure TfmConnectionDesigner.EnableDriverMode(AEnabled: boolean);
begin
  cbDrivers.Enabled := AEnabled;
  veParams.Enabled := AEnabled;
  if AEnabled then
    nbConnectionMode.ActivePage := 'DriverMode';
end;

class function TfmConnectionDesigner.Execute(
  Connection: TAureliusConnection): Boolean;
var
  Form: TfmConnectionDesigner;
begin
  Form := TfmConnectionDesigner.Create(Application, Connection);
  try
    Result := (Form.ShowModal = mrOk);
    if Result then
    begin
      Connection.AdaptedConnection := Form.Connection.AdaptedConnection;
      Connection.AdapterName := Form.Connection.AdapterName;
      Connection.SQLDialect := Form.Connection.SQLDialect;
      Connection.DriverName := Form.Connection.DriverName;
      Connection.Params.Clear;
      Connection.Params.AddStrings(Form.Connection.Params);
    end;
  finally
    Form.Free;
  end;
end;

procedure TfmConnectionDesigner.FillAdaptedConnectionList;
var
  I: integer;
  Adapter: IAureliusAdapter;
begin
  cbAdaptedConnection.Items.Clear;
  for I := 0 to FConnectionOwner.ComponentCount - 1 do
    for Adapter in TAureliusAdapterRegister.Instance.Adapters do
      if Adapter.IsAdaptedConnectionValid(FConnectionOwner.Components[I]) then
        cbAdaptedConnection.Items.AddObject(FConnectionOwner.Components[I].Name, FConnectionOwner.Components[I]);
end;

procedure TfmConnectionDesigner.FillAdapterList;
var
  Adapter: IAureliusAdapter;
begin
  cbAdapters.Items.Clear;
  for Adapter in TAureliusAdapterRegister.Instance.Adapters do
    cbAdapters.Items.Add(Adapter.Name);
end;

procedure TfmConnectionDesigner.FillDriverList;
var
  Driver: IAureliusDriver;
begin
  cbDrivers.Items.Clear;
  for Driver in TAureliusDriverRegister.Instance.Drivers do
    cbDrivers.Items.Add(Driver.Name);
end;

procedure TfmConnectionDesigner.FillParametersListEditor;
var
  Driver: IAureliusDriver;
  ParamDef: TDriverParamDef;
  Item: string;
begin
  veParams.Strings.Clear;
  veParams.Strings.Add('=');
  Driver := GetDriver(cbDrivers.Text);
  for ParamDef in Driver.ParamDefs.GetParamDefs do
  begin
    veParams.InsertRow(ParamDef.Name, ParamDef.DefaultValue, True);

    case ParamDef.EditorType of
      TEditorType.ptFilename:
        veParams.ItemProps[ParamDef.Name].EditStyle := TEditStyle.esEllipsis;
      TEditorType.ptCombo:
        begin
          veParams.ItemProps[ParamDef.Name].EditStyle := TEditStyle.esPickList;
          for Item in ParamDef.PickList do
            veParams.ItemProps[ParamDef.Name].PickList.Add(Item);
        end
    else
      //TEditorType.ptText:
      veParams.ItemProps[ParamDef.Name].EditStyle := TEditStyle.esSimple;
    end;
  end;
end;

procedure TfmConnectionDesigner.FillSQLDialectList(AAdapterName: string);
var
  Adapter: IAureliusAdapter;
  SQLDialect: string;
begin
  cbSQLDialects.Items.Clear;
  if AAdapterName <> '' then
    for Adapter in TAureliusAdapterRegister.Instance.Adapters do
      if SameText(Adapter.Name, AAdapterName) then
      begin
        for SQLDialect in Adapter.SqlDialects do
          cbSQLDialects.Items.Add(SQLDialect);
        Break;
      end;
end;

procedure TfmConnectionDesigner.FormShow(Sender: TObject);
begin
  Self.Caption := Format('Aurelius Connection Editor - [%s.%s]',
    [FConnectionOwner.Name, FConnection.Name]);
  FillAdaptedConnectionList;
  FillAdapterList;
  FillSQLDialectList(FConnection.AdapterName);
  FillDriverList;
  LoadConfiguration;
end;

function TfmConnectionDesigner.GetAdapter(
  AAdapterName: string): IAureliusAdapter;
begin
  Result := TAureliusAdapterRegister.Instance.FindAdapter(AAdapterName);
end;

function TfmConnectionDesigner.GetAdapterNameFromAdaptedConnection(
  AAdaptedConnection: TComponent): string;
var
  Adapter: IAureliusAdapter;
begin
  Result := '';
  // Get AdapterName from AdaptedConnection
  if AAdaptedConnection <> nil then
    for Adapter in TAureliusAdapterRegister.Instance.Adapters do
      if Adapter.IsAdaptedConnectionValid(AAdaptedConnection) then
      begin
        Result := Adapter.Name;
        break;
      end;
end;

function TfmConnectionDesigner.GetDriver(ADriverName: string): IAureliusDriver;
begin
  Result := TAureliusDriverRegister.Instance.FindDriver(ADriverName);
end;

function TfmConnectionDesigner.GetParamDef(AParamName: string): TDriverParamDef;
var
  Driver: IAureliusDriver;
begin
  Result := nil;
  Driver := GetDriver(cbDrivers.Text);
  if Driver = nil then
    Exit;
  Result := Driver.ParamDefs.FindByName(AParamName);
end;

procedure TfmConnectionDesigner.LoadConfiguration;
begin
  actAdapterMode.Checked := (FConnection.AdapterName <> '') or (FConnection.AdaptedConnection <> nil);
  actDriverMode.Checked := FConnection.DriverName <> '';

  nbConnectionMode.Visible := actAdapterMode.Checked or actDriverMode.Checked;

  if FConnection.AdaptedConnection <> nil then
    cbAdaptedConnection.ItemIndex := cbAdaptedConnection.Items.IndexOf(FConnection.AdaptedConnection.Name);

  if FConnection.AdapterName <> '' then
    cbAdapters.ItemIndex := cbAdapters.Items.IndexOf(FConnection.AdapterName);

  if FConnection.SQLDialect <> '' then
  begin
    cbSQLDialects.ItemIndex := cbSQLDialects.Items.IndexOf(FConnection.SQLDialect);
    if cbSQLDialects.ItemIndex < 0 then
      cbSQLDialects.Text := FConnection.SQLDialect;
  end;

  if FConnection.DriverName <> '' then
  begin
    cbDrivers.ItemIndex := cbDrivers.Items.IndexOf(FConnection.DriverName);
    LoadConnectionParamsValues;
  end
  else
    veParams.Strings.Clear;
end;

procedure TfmConnectionDesigner.LoadConnectionParamsValues;
var
  I, Row: Integer;
  Key: string;
begin
  for I := FConnection.Params.Count - 1 downto 0 do
  begin
    Key := FConnection.Params.Names[I];
    if Key = '' then
      Key := FConnection.Params[I];
    if veParams.FindRow(Key, Row) then
      veParams.Values[Key] := FConnection.Params.Values[Key];
  end;
end;

function TfmConnectionDesigner.ParamDefNotHasDefaultValue(AParamName,
  AParamValue: string): Boolean;
var
  Param: TDriverParamDef;
begin
  Result := False;
  Param := GetParamDef(AParamName);
  if Param = nil then
    Exit;
  Result := (Param.DefaultValue <> '') and (Param.DefaultValue <> AParamValue);
end;

function TfmConnectionDesigner.RetrieveSQLDialect(AAdaptedConnection: TComponent;
  AAdapterName: string): string;
var
  Conn: IDBConnection;
  ASConn: TAureliusConnection;
begin
  Result := '';
  if (AAdapterName <> '') and Assigned(AAdaptedConnection) then
  begin
    ASConn := TAureliusConnection.Create(Self);
    try
      THackComponent(ASConn).SetDesigning(True);
      ASConn.AdaptedConnection := AAdaptedConnection;
      ASConn.AdapterName := AAdapterName;
      try
        Conn := ASConn.CreateConnection;
        Result := Conn.SqlDialect;
      except
        Result := '';
      end;
    finally
      ASConn.Free;
    end;
  end;
end;

procedure TfmConnectionDesigner.SaveConnectionProperties;
var
  I: integer;
begin
  if rbAdapterMode.Checked then
  begin
    FConnection.DriverName := '';
    if cbAdaptedConnection.ItemIndex = -1 then
      FConnection.AdaptedConnection := nil
    else
      FConnection.AdaptedConnection := TComponent(cbAdaptedConnection.Items.Objects[cbAdaptedConnection.ItemIndex]);
    FConnection.AdapterName := cbAdapters.Text;
    FConnection.SQLDialect := cbSQLDialects.Text;
  end
  else
  begin
    FConnection.AdaptedConnection := nil;
    FConnection.AdapterName := '';
    FConnection.SQLDialect := '';
    FConnection.DriverName := cbDrivers.Text;
    FConnection.Params.Clear;
    for I := 0 to veParams.RowCount - 1 do
      FConnection.Params.Values[veParams.Keys[I]] := veParams.Values[veParams.Keys[I]];
  end;
end;

procedure TfmConnectionDesigner.veParamsDrawCell(Sender: TObject; ACol,
  ARow: Integer; Rect: TRect; State: TGridDrawState);
var
  Key, Value: string;
begin
  Key := TValueListEditor(Sender).Keys[ARow];
  Value := TValueListEditor(Sender).Values[Key];
  if (ARow > 0) and (ACol = 1) and (not (gdSelected in State)) then
  begin
    TValueListEditor(Sender).Canvas.Font.Color := clNavy;
    if ParamDefNotHasDefaultValue(Key, Value) then
      TValueListEditor(Sender).Canvas.Font.Style := [fsBold]
    else
      TValueListEditor(Sender).Canvas.Font.Style := [];
    TValueListEditor(Sender).Canvas.FillRect(Rect);
    TValueListEditor(Sender).Canvas.TextOut(Rect.Left + 2, Rect.Top + 2, TValueListEditor(Sender).Cells[ACol, ARow]);
  end;
end;

procedure TfmConnectionDesigner.veParamsEditButtonClick(Sender: TObject);
var
  Key: string;
  Param: TDriverParamDef;
  FileDialog: TOpenDialog;
begin
  Key := TValueListEditor(Sender).Keys[TValueListEditor(Sender).Row];
  Param := GetParamDef(Key);
  if Param.EditorType = TEditorType.ptFilename then
  begin
    FileDialog := TOpenDialog.Create(Self);
    try
      if FileDialog.Execute then
        TValueListEditor(Sender).Values[Key] := FileDialog.FileName;
    finally
      FileDialog.Free;
    end;
  end;
end;

{ TComboBox }

procedure TComboBox.SetItemIndex(const Value: Integer);
begin
  inherited;
  if Assigned(OnChange) then
    OnChange(Self);
end;

end.
