{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    This unit helps to setup and configure the codetools.

  Example:
    Create an empty unit empty.pas and do
  
    Options:=TCodeToolsOptions.Create;
    Options.LoadFromFile('config.xml');
    Options.FPCPath:='/usr/bin/ppc386';
    Options.FPCSrcDir:='/home/username/freepascal/fpc';
    Options.LazarusSrcDir:='/home/username/pascal/lazarus';
    Options.ProjectDir:='/home/username/pascal/project1/';
    Options.TestPascalFile:=Options.ProjectDir+'empty.pas';
    CodeToolBoss.Init(Options);
    Options.SaveToFile('config.xml');
    Options.Free;
    
    .. use CodeToolBoss ..
    
}
unit CodeToolsConfig;

{$mode objfpc}{$H+}

{$I codetools.inc}

interface

uses
  Classes, SysUtils, Laz2_XMLCfg, Laz2_XMLRead, Laz2_XMLWrite, Laz2_DOM,
  FileProcs, LazFileUtils, LazFileCache, LazUTF8, CodeCache, DefineTemplates;
  
type

  { TCodeBufXMLConfig }

  TCodeBufXMLConfig = class(TRttiXMLConfig)
  private
    FCodeCache: TCodeCache;
  protected
    fKeepFileAttributes: boolean;
    procedure ReadXMLFile(out ADoc: TXMLDocument; const AFilename: String); override;
    procedure WriteXMLFile(ADoc: TXMLDocument; const AFileName: String); override;
    function GetCache: TCodeCache;
  public
    constructor CreateWithCache(AFilename: string;
      LoadContent: boolean = true;        // init/load from disk
      LoadFileAttributes: boolean = true; // load lineending and encoding
      ASource: string = '';               // init with this source
      ACache: TCodeCache = nil);
    property CodeCache: TCodeCache read FCodeCache write FCodeCache;
    property KeepFileAttributes: boolean read fKeepFileAttributes write fKeepFileAttributes;
  end;

var
  DefaultConfigCodeCache: TCodeCache = nil; // set by CodeToolBoss

type

  { TCodeToolsOptions }

  TCodeToolsOptions = class
  private
    FConfigCaches: TPCTargetConfigCaches;
    FFPCOptions: string;
    FFPCPath: string;
    FFPCSrcDir: string;
    FLazarusSrcDir: string;
    FLazarusSrcOptions: string;
    FLCLWidgetType: string;
    FModified: boolean;
    FPPUExt: string;
    FProjectDir: string;
    FSourceCaches: TFPCSourceCaches;
    FTargetOS: string;
    FTargetProcessor: string;
    FTestPascalFile: string;
    procedure SetFPCOptions(const AValue: string);
    procedure SetFPCPath(const AValue: string);
    procedure SetFPCSrcDir(const AValue: string);
    procedure SetLazarusSrcDir(const AValue: string);
    procedure SetLCLWidgetType(const AValue: string);
    procedure SetLazarusSrcOptions(const AValue: string);
    procedure SetModified(const AValue: boolean);
    procedure SetPPUExt(const AValue: string);
    procedure SetProjectDir(const AValue: string);
    procedure SetTargetOS(const AValue: string);
    procedure SetTargetProcessor(const AValue: string);
    procedure SetTestPascalFile(const AValue: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure InitWithEnvironmentVariables;
    function FindDefaultCompilerFilename: string;

    procedure SaveToXMLConfig(XMLConfig: TXMLConfig; const Path: string);
    procedure LoadFromXMLConfig(XMLConfig: TXMLConfig; const Path: string);
    procedure SaveToFile(const Filename: string);
    procedure LoadFromFile(const Filename: string);

    property Modified: boolean read FModified write SetModified;

    // FPC
    property FPCSrcDir: string read FFPCSrcDir write SetFPCSrcDir; // e.g. /usr/share/fpcsrc
    property FPCPath: string read FFPCPath write SetFPCPath; // e.g. /usr/bin/fpc or /usr/bin/ppc386
    property FPCOptions: string read FFPCOptions write SetFPCOptions; // extra options for fpc
    property TargetOS: string read FTargetOS write SetTargetOS;
    property TargetProcessor: string read FTargetProcessor write SetTargetProcessor;
    property TestPascalFile: string read FTestPascalFile write SetTestPascalFile; // points to an empty unit
    property PPUExt: string read FPPUExt write SetPPUExt;
    property SourceCaches: TFPCSourceCaches read FSourceCaches;
    property ConfigCaches: TPCTargetConfigCaches read FConfigCaches;

    // Project
    property ProjectDir: string read FProjectDir write SetProjectDir;
    
    // Lazarus
    property LazarusSrcDir: string read FLazarusSrcDir write SetLazarusSrcDir;
    property LCLWidgetType: string read FLCLWidgetType write SetLCLWidgetType;
    property LazarusSrcOptions: string read FLazarusSrcOptions write SetLazarusSrcOptions;
  end;

implementation

{ TCodeToolsOptions }

procedure TCodeToolsOptions.SetFPCOptions(const AValue: string);
begin
  if FFPCOptions=AValue then exit;
  FFPCOptions:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetFPCPath(const AValue: string);
var
  NewValue: String;
begin
  NewValue:=TrimAndExpandFilename(AValue);
  if FFPCPath=NewValue then exit;
  FFPCPath:=NewValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetFPCSrcDir(const AValue: string);
var
  NewValue: String;
begin
  NewValue:=TrimAndExpandFilename(AValue);
  if FFPCSrcDir=NewValue then exit;
  FFPCSrcDir:=NewValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetLazarusSrcDir(const AValue: string);
var
  NewValue: String;
begin
  NewValue:=TrimAndExpandFilename(AValue);
  if FLazarusSrcDir=NewValue then exit;
  FLazarusSrcDir:=NewValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetLCLWidgetType(const AValue: string);
begin
  if FLCLWidgetType=AValue then exit;
  FLCLWidgetType:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetLazarusSrcOptions(const AValue: string);
begin
  if FLazarusSrcOptions=AValue then exit;
  FLazarusSrcOptions:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetModified(const AValue: boolean);
begin
  if FModified=AValue then exit;
  FModified:=AValue;
end;

procedure TCodeToolsOptions.SetPPUExt(const AValue: string);
begin
  if FPPUExt=AValue then exit;
  FPPUExt:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetProjectDir(const AValue: string);
begin
  if FProjectDir=AValue then exit;
  FProjectDir:=AppendPathDelim(AValue);
  Modified:=true;
end;

procedure TCodeToolsOptions.SetTargetOS(const AValue: string);
begin
  if FTargetOS=AValue then exit;
  FTargetOS:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetTargetProcessor(const AValue: string);
begin
  if FTargetProcessor=AValue then exit;
  FTargetProcessor:=AValue;
  Modified:=true;
end;

procedure TCodeToolsOptions.SetTestPascalFile(const AValue: string);
begin
  if FTestPascalFile=AValue then exit;
  FTestPascalFile:=AValue;
  Modified:=true;
end;

constructor TCodeToolsOptions.Create;
begin
  FPPUExt:='.ppu';
  FLCLWidgetType:='gtk2';
  FConfigCaches:=TPCTargetConfigCaches.Create(nil);
  FSourceCaches:=TFPCSourceCaches.Create(nil);
end;

destructor TCodeToolsOptions.Destroy;
begin
  FreeAndNil(FConfigCaches);
  FreeAndNil(FSourceCaches);
  inherited Destroy;
end;

procedure TCodeToolsOptions.InitWithEnvironmentVariables;

{  procedure WriteEnv;
  var
    i: Integer;
  begin
    for i:=0 to GetEnvironmentVariableCount-1 do
      debugln(['TCodeToolsOptions.InitWithEnvironmentVariables ',i,' ',GetEnvironmentStringUTF8(i)]);
  end;
}
begin
  if GetEnvironmentVariableUTF8('PP')<>'' then
    FPCPath:=GetEnvironmentVariableUTF8('PP')
  else if (FPCPath='') or not FileExistsCached(FPCPath) then
    FPCPath:=FindDefaultCompilerFilename;
  if GetEnvironmentVariableUTF8('FPCDIR')<>'' then
    FPCSrcDir:=GetEnvironmentVariableUTF8('FPCDIR');
  if GetEnvironmentVariableUTF8('LAZARUSDIR')<>'' then
    LazarusSrcDir:=GetEnvironmentVariableUTF8('LAZARUSDIR');
  if GetEnvironmentVariableUTF8('FPCTARGET')<>'' then
    TargetOS:=GetEnvironmentVariableUTF8('FPCTARGET');
  if GetEnvironmentVariableUTF8('FPCTARGETCPU')<>'' then
    TargetProcessor:=GetEnvironmentVariableUTF8('FPCTARGETCPU');
end;

function TCodeToolsOptions.FindDefaultCompilerFilename: string;
begin
  Result:=SearchFileInPath(GetDefaultCompilerFilename,'',
                           GetEnvironmentVariableUTF8('PATH'),PathSeparator,ctsfcDefault);
end;

procedure TCodeToolsOptions.SaveToXMLConfig(XMLConfig: TXMLConfig;
  const Path: string);
begin
  XMLConfig.SetDeleteValue(Path+'FPC/Options/Value',FPCOptions,'');
  XMLConfig.SetDeleteValue(Path+'FPC/CompilerPath/Value',FPCPath,'');
  XMLConfig.SetDeleteValue(Path+'FPC/SrcDir/Value',FPCSrcDir,'');
  XMLConfig.SetDeleteValue(Path+'FPC/TargetOS/Value',TargetOS,'');
  XMLConfig.SetDeleteValue(Path+'FPC/TargetProcessor/Value',TargetProcessor,'');
  XMLConfig.SetDeleteValue(Path+'FPC/PPUExt/Value',PPUExt,'.ppu');
  XMLConfig.SetDeleteValue(Path+'FPC/TestPascalFile/Value',TestPascalFile,'');
  XMLConfig.SetDeleteValue(Path+'Lazarus/SrcDir/Value',LazarusSrcDir,'');
  XMLConfig.SetDeleteValue(Path+'Lazarus/SrcDirOptions/Value',LazarusSrcOptions,'');
  XMLConfig.SetDeleteValue(Path+'Lazarus/LCLWidgetType/Value',LCLWidgetType,'');
  XMLConfig.SetDeleteValue(Path+'Project/Dir/Value',ProjectDir,'');
  FConfigCaches.SaveToXMLConfig(XMLConfig,Path+'FPCConfigCaches/');
  FSourceCaches.SaveToXMLConfig(XMLConfig,Path+'FPCSrcDirCaches/');
  Modified:=false;
end;

procedure TCodeToolsOptions.LoadFromXMLConfig(XMLConfig: TXMLConfig;
  const Path: string);
begin
  FPCOptions:=XMLConfig.GetValue(Path+'FPC/Options/Value','');
  FPCPath:=XMLConfig.GetValue(Path+'FPC/CompilerPath/Value','');
  FPCSrcDir:=XMLConfig.GetValue(Path+'FPC/SrcDir/Value','');
  TargetOS:=XMLConfig.GetValue(Path+'FPC/TargetOS/Value','');
  TargetProcessor:=XMLConfig.GetValue(Path+'FPC/TargetProcessor/Value','');
  PPUExt:=XMLConfig.GetValue(Path+'FPC/PPUExt/Value','.ppu');
  TestPascalFile:=XMLConfig.GetValue(Path+'FPC/TestPascalFile/Value','');
  FConfigCaches.LoadFromXMLConfig(XMLConfig,Path+'FPCConfigCaches/');
  FSourceCaches.LoadFromXMLConfig(XMLConfig,Path+'FPCSrcDirCaches/');

  LazarusSrcDir:=XMLConfig.GetValue(Path+'Lazarus/SrcDir/Value','');
  LazarusSrcOptions:=XMLConfig.GetValue(Path+'Lazarus/SrcDirOptions/Value','');
  LCLWidgetType:=XMLConfig.GetValue(Path+'Lazarus/LCLWidgetType/Value','');
  ProjectDir:=XMLConfig.GetValue(Path+'Project/Dir/Value','');
  Modified:=false;
end;

procedure TCodeToolsOptions.SaveToFile(const Filename: string);
var
  XMLConfig: TXMLConfig;
begin
  XMLConfig:=TXMLConfig.CreateClean(Filename);
  try
    SaveToXMLConfig(XMLConfig,'CodeToolsOptions/');
    XMLConfig.Flush;
  finally
    XMLConfig.Free;
  end;
end;

procedure TCodeToolsOptions.LoadFromFile(const Filename: string);
var
  XMLConfig: TXMLConfig;
begin
  XMLConfig:=TXMLConfig.Create(Filename);
  try
    LoadFromXMLConfig(XMLConfig,'CodeToolsOptions/');
    XMLConfig.Flush;
  finally
    XMLConfig.Free;
  end;
end;

{ TCodeBufXMLConfig }

procedure TCodeBufXMLConfig.ReadXMLFile(out ADoc: TXMLDocument;
  const AFilename: String);
var
  Buf: TCodeBuffer;
  ms: TMemoryStream;
  Cache: TCodeCache;
begin
  Cache:=GetCache;
  if Cache<>nil then begin
    Buf:=Cache.LoadFile(AFilename);
    if Buf<>nil then begin
      fKeepFileAttributes:=true;
      ms:=TMemoryStream.Create;
      try
        Buf.SaveToStream(ms);
        ms.Position:=0;
        Laz2_XMLRead.ReadXMLFile(ADoc, ms, ReadFlags);
        exit; // success
      finally
        ms.Free;
      end;
    end;
  end;
  // try default (this will create the normal exceptions)
  inherited ReadXMLFile(ADoc, AFilename);
end;

procedure TCodeBufXMLConfig.WriteXMLFile(ADoc: TXMLDocument;
  const AFileName: String);
var
  Buf: TCodeBuffer;
  ms: TMemoryStream;
  Cache: TCodeCache;
begin
  Cache:=GetCache;
  if Cache<>nil then begin
    Buf:=nil;
    if (not fKeepFileAttributes) or (not FileExistsCached(AFileName)) then
      Buf:=Cache.CreateFile(AFilename)
    else
      Buf:=Cache.LoadFile(AFilename);
    if Buf<>nil then begin
      fKeepFileAttributes:=true;
      ms:=TMemoryStream.Create;
      try
        Laz2_XMLWrite.WriteXMLFile(ADoc, ms, WriteFlags);
        ms.Position:=0;
        Buf.LoadFromStream(ms);
        if Buf.FileOnDiskIsEqual then exit;
        //debugln(['TCodeBufXMLConfig.WriteXMLFile writing ',AFileName,' ...']);
        if Buf.Save then exit; // success
      finally
        ms.Free;
      end;
    end;
  end;
  // try default (this will create the normal exceptions)
  inherited WriteXMLFile(ADoc, AFileName);
end;

function TCodeBufXMLConfig.GetCache: TCodeCache;
begin
  Result:=CodeCache;
  if Result=nil then
    Result:=DefaultConfigCodeCache;
end;

constructor TCodeBufXMLConfig.CreateWithCache(AFilename: string;
  LoadContent: boolean; LoadFileAttributes: boolean; ASource: string;
  ACache: TCodeCache);
begin
  CodeCache:=ACache;
  fKeepFileAttributes:=LoadFileAttributes;
  if (ASource<>'') then
    inherited CreateWithSource(AFilename,ASource)
  else if LoadContent then
    inherited Create(AFilename)
  else
    inherited CreateClean(AFilename);
end;

end.

