{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    This program 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 program 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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses/

 ********************************************************************** 

    This file contains the configuration manager class implementation.

 **********************************************************************}


type
  TConfLimRec = record
    s: string;
    min, max: integer;
  end;

var
  {format: 'SECTION.ID', necessarily in uppercase}
  ConfigLimits: array [0..0] of TConfLimRec = (
    (s:'NONE.NONE'; min:0; max:1)
{    (s:'VIDEO.HEIGHT'; min:480; max:1536),
    (s:'VIDEO.WIDTH'; min:640; max:2048) }
  );

 function LimitsValid(sect, id: string; val: integer): boolean;
 var
   j: integer;
   u: string;
 begin
   Result:= Yes;
   u:=UpperCase(sect + '.' + id);
   For j:=0 to High(ConfigLimits) do
     with ConfigLimits[j] do
       if (s = u) and ((val < min) or (val > max)) then begin
         Result:= No;
         Exit;
       end;
 end;

 function TConfigManager._Find(s, i: string): string;
 var
   j, maxj: integer;
 begin
   Result:='';
   wherefound:=0;

   for j:=1 downto 0 do begin
     Result:= ini[j].ReadString(s, i, '');
     if Result <> '' then begin
       wherefound:=j;
       if (j=1) and (uppercase(s)='MAIN')
         then Die(MI_ERROR_CONFIG_MAIN_NO_OVERRIDE, [ini[1].FileName, ini[0].FileName, lowercase(i)]);
       exit;
     end;
   end;
 end;
 
 function TConfigManager._ReadStr(s, i: string): string;
 begin
   Result:=_Find(UpperCase(s), UpperCase(i));
 end;
 
 function TConfigManager._ReadPath(s, i: string): string;
 begin
   Result:=
   {$ifdef windows}
     StrReplace(
   {$endif}
     StrReplace(StrReplace(StrReplace(StrReplace(_ReadStr(s, i),
       '{$PLATFORM}', SystemSuffix),
         '{$HOMEDIR}', MotherState.HomePath),
            '{$INSDIR}', MotherState.InstallPath),
              '{$USERNAME}', MotherState.UserName)
            
   {$ifdef windows}
     , '{$CSIDL_APPDATA}', MotherState.CSIDL_AppDataDir)
   {$endif}
     ;
   Result:=OptiPath(Result) + ExtractFileName(Result);
   {$ifdef windows}
     Result:=StrReplace(Result, '/', '\');
   {$else}
     Result:=StrReplace(Result, '\', '/');
   {$endif}
 end;
 
 procedure TConfigManager._WritePath(s,i, d: string);
 begin
   _WriteStr(OptiPath(s), i, d);
 end;

 function TConfigManager._ReadBool(s, i: string): boolean;
 var j: integer;
 begin
   if StrictLimitsCheck then j:=_ReadInt(s, i, 0, 1)
                        else j:=_ReadInt(s, i);
   Result:=(j = 1);
 end;

 function TConfigManager._ReadInt(s, i: string): integer;
 var v: string;
 begin
   v:=_ReadStr(s, i);
   if v='' then v:='0';
   Try
     Result:=StrToInt(v);
   Except
     Die (MI_ERROR_INVALID_CONFIG_PARAMETER_NO_LIM, [Ini[wherefound].FileName, s, i, v])
   End;
 end;

function TConfigManager._ReadFloat(s, i: string): float;
var v: string;
begin
  v:=_ReadStr(s, i);
  if v='' then v:='0';
  Try
    Result:=StrToFloat(v);
  Except
    Die (MI_ERROR_INVALID_CONFIG_PARAMETER_NO_LIM, [Ini[wherefound].FileName, s, i, v])
  End;
end;
 
 function TConfigManager._ReadIntChk(s, i: string; min,max: integer): integer;
 begin
   Result:=_ReadInt(s, i);
   if (Result < min) or (Result > max) then
     ScreamLimitsRead(wherefound, s, i, Result, min, max);
 end;

 function TConfigManager._ReadFloatChk(s, i: string; min,max: float): float;
 begin
   Result:= _ReadFloat(s, i);
   if (Result < min) or (Result > max) then
     ScreamLimitsRead(wherefound, s, i, Result, min, max);
 end;

 function TConfigManager._ReadInt(s, i: string; min, max: integer): integer;
 var v: string;
 begin
   if not StrictLimitsCheck then begin
     Result:=_ReadInt(s, i);
     if Result < min then Result:=min;
     if Result > max then Result:=max;
   end
   else begin
     v:=_ReadStr(s, i);
     if v='' then v:='0';
     Try
       Result:=StrToInt(v);
       if (Result < min) or (Result > max) then raise Exception.Create('Value is out ot bounds!');
     Except
       Die (MI_ERROR_INVALID_CONFIG_PARAMETER, [Ini[wherefound].FileName, s, i, v, min, max])
     End;
   end;
 end;

 procedure TConfigManager._WriteStr(s,i,d: string);
 begin
   if uppercase(s) = 'MAIN'
     then Die(MI_ERROR_PROGRAMMER_NO_BAKA,
       [PervertedFormat('attempt to write [main].%0 = %1 to the config.', [i, d])]);
   ini[1].WriteString(s, i, d);
 end;

 procedure TConfigManager._WriteInt(s,i: string; v:integer);
 begin
   _WriteStr(s, i, IntToStr(v));
 end;

procedure TConfigManager._WriteFloat(s,i: string; v:float);
begin
  _WriteStr(s, i, FloatToStr(v));
end;
 
 procedure TConfigManager._WriteIntChk(s,i: string; min,max,v:integer);
 begin
   if (v < min) or (v > max) then begin
     VerboseLog('Attempt to write invalid configuration data. Section ='+s+' par='+i+' min='+IntToStr(min)+' max='+IntToStr(max)+' value='+IntToStr(v)+'. Value truncated.');
   end;
   if v < min then v:=min;
   if v > max then v:=max;
   _WriteStr(s, i, IntToStr(v));
 end;

 procedure TConfigManager._WriteFloatChk(s,i: string; min, max, v:float);
 begin
    if (v < min) or (v > max) then begin
     VerboseLog('Attempt to write invalid configuration data. Section ='+s+' par='+i+' min='+FloatToStr(min)+' max='+FloatToStr(max)+' value='+FloatToStr(v)+'. Value truncated.');
   end;
   if v < min then v:=min;
   if v > max then v:=max;
   _WriteStr(s, i, FloatToStr(v));
 end;

 procedure TConfigManager._WriteBool(s,i: string; b: boolean);
 begin
   if b then _WriteStr(s, i, '1') else _WriteStr(s, i, '0');
 end;

 Constructor TConfigManager.Create;
 var
   sl: TStringList;
 begin
   //initially, load it from the built-in constant:
   sl:=TStringList.Create;
   sl.Text:=DefaultConfString;
   ini[0]:= TMemIniFile.Create('', false);
   (ini[0] as TMemIniFile).SetStrings(sl);
   sl.Free;
   
   ini[1]:= TMemIniFile.Create('', false); { temporary, to allow functionig without
     the actual file to write to }
 end;

 Destructor TConfigManager.Destroy;
 var j: integer;
 begin
   if ini[1].ClassType <> TMemIniFile then ini[1].UpdateFile;
   ini[1].Free;
 end;

 procedure TConfigManager.ScreamLimitsRead(fnum: integer; s,i: string; val, min, max: integer);
 begin
   Die(MI_ERROR_INVALID_CONFIG_PARAMETER,[Ini[fnum].FileName, s, i, val, min, max]);
 end;

procedure TConfigManager.ScreamLimitsRead(fnum: integer; s,i: string; val, min, max: float);
begin
  Die(MI_ERROR_INVALID_CONFIG_PARAMETER,[Ini[fnum].FileName, s, i, val, min, max]);
end;
 
 procedure TConfigManager.LoadMainFile;
 begin
   ini[0].Free;
   ini[0]:=  TIniFile.Create(MotherState.InstallPath +  MyAppName + '.ini');
 end;

 procedure TConfigManager.LoadUserFile;
 begin
   if MotherState.HomePath = '' then Exit;
   ValidateWritePath(MotherState.HomePath + ExtractFilePath(ConfigFilePath));
   ini[1].Free;
   ini[1]:= TIniFile.Create(MotherState.HomePath + ConfigFilePath); //a strange bug in Debian 7 64-bit with wine 32-bit: Chentrah crashes with EFileCreate if the ini file already exists
 end;
 

 function TConfigManager.Wherefoundname: WideString;
 begin
   Result:=AnsiToWide(ini[wherefound].FileName);
 end;
