{
 ***************************************************************************
 *                                                                         *
 *   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************
  Author: Felipe Monteiro de Carvalho
  Abstract:
    Unit to control the custom configurations of the application
}

unit appsettings;

{ Time zone database files
  Firmware update files
  INI file
}

interface

uses
  Classes, SysUtils,  IniFiles;

type

 { TConfigurations }

 TConfigurations = class(TObject)
 public
   {other settings as fields here}
   constructor Create;
   destructor Destroy; override;
   procedure WriteString(Section:String=''; Key:String=''; KeyValue:String='');
   procedure WriteDeletKey(Section:String=''; Key:String='');
   function ReadString(Section:String=''; Key:String=''; DefaultValue:String=''): String;
   procedure WriteBool(Section:String=''; Key:String=''; KeyValue:Boolean=False);
   function ReadBool(Section:String=''; Key:String=''; DefaultValue:Boolean=False): Boolean;
   procedure ReadSection(Section:String; var Strings: TStringList);
   procedure EraseSection(Section:String='');
   procedure ReadSectionNames(SectionNameStrings:TStringList);
 end;


var
 vConfigurations: TConfigurations;
 ConfigFilePath, DataDirectory, DataDirectoryAlternate, FirmwareDirectory, TZDirectory, LogsDirectory: String;
 MyFile: TMemIniFile;

procedure LogsDirectoryReset();
function LogsDirectoryDefault(): string;
implementation
uses
{$IFDEF Windows}
  //Windows, shlobj,
  SHFolder,
{$ENDIF}
{$ifdef Darwin}
  MacOSAll,
{$endif}
Unit1
, header_utils
;
{ TConfigurations }

constructor TConfigurations.Create;
{$ifdef Windows}
var
 //ApplicationDataPath:Array[0..MaxPathLen] of Char; //Allocate memory
 ApplicationDataPath:Array[0..200] of Char; //Allocate memory
begin
  { Get the path which defaults to:
    \Documents and Settings\All Users\Application Data same as C:\ProgramData }

  {There seems to be a problem with various versions of Windows 32/64 bit and the
   SHGetFolderPath CSIDL_COMMON_APPDATA commands/variables.
   https://stackoverflow.com/questions/18493484/shgetfolderpath-deprecated-what-is-alternative-to-retrieve-path-for-windows-fol

   See this link for possible solution using GetEnvironmentVariable
   https://wiki.lazarus.freepascal.org/Windows_Programming_Tips#Getting_special_folders_.28My_documents.2C_Desktop.2C_local_application_data.2C_etc.29
   }
  { TODO : SHGetFolder CSIDL deprecated }
  SHGetFolderPath(0,CSIDL_COMMON_APPDATA,0,0,@ApplicationDataPath[0]);

  if DirectoryExists(ApplicationDataPath + '\Unihedron\') then
    begin
     DataDirectory:= ExpandFileName(ApplicationDataPath + '\Unihedron\');
    end
  else
    begin
       DataDirectory:= ExpandFileName('.\'); //This directory
    end;
  ConfigFilePath:=ExpandFileName(DataDirectory + 'udm.ini');

  {Restore setting if exists }
  LogsDirectory:=vConfigurations.ReadString('Directories','LogsDirectory');
  if length(LogsDirectory)=0 then
    LogsDirectoryReset();

  if not FileExists(DataDirectory+'changelog.txt') then
    DataDirectory:= ExpandFileName('.'+DirectorySeparator);

{$endif}

{$ifdef Linux}
begin

   { Get this users configuration file.}
   ConfigFilePath := GetAppConfigFile(False);


   {Get DataDirectory. Look in this order:
    - "installed" location.
    - source/development location.
    - a user defined location.
    Note that; Running from LLDB uses the user home which is not the development location.}

    DataDirectoryAlternate:=vConfigurations.ReadString('Directories','DataDirectoryAlternate');

    if DirectoryExists('/usr/share/udm') then
       DataDirectory:= '/usr/share/udm/' {Installed data directory.}
    else if DirectoryExists('./tzdatabase') then {Development location.}
       DataDirectory:= ExpandFileName('./')
    else if DirectoryExists(DataDirectoryAlternate) then
       DataDirectory:= ExpandFileName(DataDirectoryAlternate) //This directory (assumes development directory)
    else
      Writeln('Data directory with Time Zone database could not be found. You can put a DataDirectoryAlternate entry into the [Directories] section of the configfile.');


 {Read setting if exists }
 LogsDirectory:=vConfigurations.ReadString('Directories','LogsDirectory');
 if length(LogsDirectory)=0 then
   LogsDirectoryReset();

 {Create logs directory if it does not exist}
 If Not DirectoryExists(LogsDirectory) then
   CreateDir (LogsDirectory);
 //If Not DirectoryExists(LogsDirectory) then
 //  If Not CreateDir (LogsDirectory) Then
 //    StatusMessage('Failed to create directory: '+LogsDirectory)
 //  else
 //    StatusMessage('Created LogsDirectory: '+LogsDirectory);
{$endif}

{$ifdef Darwin}
var
  pathRef: CFURLRef;
  pathCFStr: CFStringRef;
  pathStr: shortstring;
const
  BundleResourcesDirectory = '/Contents/Resources';
begin
  pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());
  pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);
  CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());
  CFRelease(pathRef);
  CFRelease(pathCFStr);
  //StatusMessage(pathStr + BundleResourcesDirectory);

  //StatusMessage(GetUserDir);
  //StatusMessage(CreateDir(GetUserDir+'/Documents'));//In the odd case that Macs change and this directory does not already exist.
  CreateDir(GetUserDir+'/Documents');//In the odd case that Macs change and this directory does not already exist.
  //StatusMessage(CreateDir(GetUserDir+'/Documents/udm'));//Create a directory to store the configuration and log files
  CreateDir(GetUserDir+'/Documents/udm');//Create a directory to store the configuration and log files

  ConfigFilePath:= GetUserDir+'/Documents/udm/udm.cfg';

  //CreateDir(ExpandFileName(GetAppConfigDir(False)+'..'));
  //Writeln(CreateDir(ExpandFileName(GetAppConfigDir(False)+'/..')));
  //ConfigFilePath := GetAppConfigFile(False); //personal configuration file
  //LogsDirectory:= ExpandFileName(GetAppConfigDir(False));
  { TODO : restore setting if exists }
  //LogsDirectoryReset();
  {Restore setting if exists }
  LogsDirectory:=vConfigurations.ReadString('Directories','LogsDirectory');
  if length(LogsDirectory)=0 then
    LogsDirectoryReset();

  {Create logs directory if it does not exist}
  If Not DirectoryExists(LogsDirectory) then
    CreateDir (LogsDirectory);
  //If Not DirectoryExists(LogsDirectory) then
  //  If Not CreateDir (LogsDirectory) Then
  //    StatusMessage('Failed to create directory: '+LogsDirectory)
  //  else
  //    StatusMessage('Created LogsDirectory: '+LogsDirectory);

  DataDirectory := pathStr + BundleResourcesDirectory+'/';
  if not FileExists(DataDirectory+'changelog.txt') then
    DataDirectory:= ExpandFileName('.'+DirectorySeparator);

 {$endif}

if DirectoryExists(DataDirectory + 'firmware') then
  FirmwareDirectory:= ExpandFileName(DataDirectory + 'firmware' + DirectorySeparator)
else
  FirmwareDirectory:= ExpandFileName('.'+DirectorySeparator+'firmware' + DirectorySeparator);

 if DirectoryExists(DataDirectory + 'tzdatabase') then
   TZDirectory:= ExpandFileName(DataDirectory + 'tzdatabase' + DirectorySeparator)
 else
   TZDirectory:= ExpandFileName('.'+DirectorySeparator+'tzdatabase' + DirectorySeparator);

end;

destructor TConfigurations.Destroy;
begin
  WriteString();
  inherited Destroy;
end;

procedure TConfigurations.WriteBool(Section:String=''; Key:String=''; KeyValue:Boolean=False);
var
  MyFile: TIniFile;
begin
  { Create file if it does not already exist. }
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Write the information. }
    MyFile.WriteBool(Section, Key, KeyValue);
  finally
    MyFile.Free;
  end;
end;

function TConfigurations.ReadBool(Section:String=''; Key:String=''; DefaultValue:Boolean=False): Boolean;
var
  MyFile: TIniFile;
begin
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Read the information. }
   Result := MyFile.ReadBool(Section,Key,DefaultValue);
  finally
    MyFile.Free;
  end;
end;

procedure TConfigurations.WriteString(Section:String=''; Key:String=''; KeyValue:String='');
var
  MyFile: TIniFile;
begin
  { Create file if it does not already exist. }
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Write the information. }
    MyFile.WriteString(Section, Key, KeyValue);
  finally
    MyFile.Free;
  end;
end;

procedure TConfigurations.WriteDeletKey(Section:String=''; Key:String='');
var
  MyFile: TIniFile;
begin
  { Create file if it does not already exist. }
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Delete the key. }
    MyFile.DeleteKey(Section, Key);
  finally
    MyFile.Free;
  end;
end;

procedure TConfigurations.EraseSection(Section:String='');
var
  MyFile: TIniFile;
begin
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Erase the section. }
    MyFile.EraseSection(Section);
  finally
    MyFile.Free;
  end;
end;

function TConfigurations.ReadString(Section:String=''; Key:String=''; DefaultValue:String=''): String;
var
  MyFile: TIniFile;
begin
  MyFile := TIniFile.Create(ConfigFilePath);
  try
    { Read the information. }
   Result := MyFile.ReadString(Section,Key,DefaultValue);
  finally
    MyFile.Free;
  end;
end;

{ Returns all the section names found in the configuration file to the referenced stringlist. }
procedure TConfigurations.ReadSectionNames(SectionNameStrings: TStringList);
begin
  MyFile := TMemIniFile.Create(ConfigFilePath); //does this need to be freed
  try
   MyFile.ReadSections(SectionNameStrings);
  finally
    MyFile.Free;
  end;
end;

procedure TConfigurations.ReadSection(Section:String; var Strings: TStringList);
//Reads all the keys in one section
var
  MyFile: TMemIniFile;
begin
  MyFile := TMemIniFile.Create(ConfigFilePath);
  try
   MyFile.ReadSection(Section,Strings);
  finally
    MyFile.Free;
  end;
end;

procedure LogsDirectoryReset();
begin
  appsettings.LogsDirectory:= RemoveMultiSlash(LogsDirectoryDefault());
end;

function LogsDirectoryDefault(): string;
begin
{$ifdef Windows}
  LogsDirectoryDefault:= RemoveMultiSlash(ExpandFileName(DataDirectory + 'logs' + DirectorySeparator));
{$endif}

{$ifdef Linux}
  LogsDirectoryDefault:= ExpandFileName(GetAppConfigDir(False));
{$endif}

{$ifdef Darwin}
  LogsDirectoryDefault:= GetUserDir+'/Documents/udm/';
{$endif}
end;

initialization
  vConfigurations := TConfigurations.Create;


finalization
  FreeAndNil(vConfigurations);


end.
