{$mode objfpc}{$H+}
unit uMapConfig;

// CC 20260922 single settings model for the map window. Render logic reads
//  gMapSettings only; the main-panel controls and the settings dialog are views
//  onto it. Persisted to MapWindow\MapWindow.ini next to the DLL.

interface

uses
  SysUtils, IniFiles,
  uRangeRings;

const
  // CC 20260922 VisibilityMode values (mirror rgVisibility items)
  VIS_PLAYER = 0;
  VIS_GOD    = 1;

  // CC 20260922 CourseMode values (mirror rgCourseMode items)
  COURSE_NONE     = 0;
  COURSE_SELECTED = 1;
  COURSE_ALL      = 2;

  // CC 20260924 FormationZoneMode values, same order as CourseMode
  ZONES_NONE     = 0;
  ZONES_SELECTED = 1;
  ZONES_ALL      = 2;

  SIDE_COUNT = 4;  // BLUESIDE, REDSIDE, GREENSIDE, UNKNOWNSIDE

type
  TMapSettings = record
    RangeCircles: array[0..RING_COUNT-1, 0..SIDE_COUNT-1] of Boolean; // per ring type, per side
    FormationZoneMode: Integer;                     // ZONES_*
    VisibilityMode: Integer;                        // VIS_*
    CourseMode: Integer;                            // COURSE_*
    CourseSides: array[0..SIDE_COUNT-1] of Boolean; // All Groups mode side filter
  end;

var
  gMapSettings: TMapSettings;

procedure DefaultMapSettings(out aSettings: TMapSettings);
function  MapSettingsFileName: string;
procedure LoadMapSettings;
procedure SaveMapSettings;

implementation

uses
  uThreadComm;  // gDLLPathFileName

const
  SEC_RINGS    = 'RangeRings';
  SEC_OVERLAYS = 'Overlays';
  SEC_VIEW     = 'View';

  RING_KEYS: array[0..RING_COUNT-1] of string = (
    'ASuW', 'AAW', 'SurfRadar', 'AirRadar', 'AirRange', 'Bingo');
  SIDE_KEYS: array[0..SIDE_COUNT-1] of string = (
    'Blue', 'Red', 'Green', 'Unknown');

// CC 20260922 defaults equal the pre-settings-file behaviour
procedure DefaultMapSettings(out aSettings: TMapSettings);
var
  r, s: Integer;
begin
  for r := 0 to RING_COUNT - 1 do
    for s := 0 to SIDE_COUNT - 1 do
      aSettings.RangeCircles[r, s] := True;
  aSettings.FormationZoneMode := ZONES_NONE;
  aSettings.VisibilityMode := VIS_PLAYER;
  aSettings.CourseMode := COURSE_SELECTED;
  for s := 0 to SIDE_COUNT - 1 do
    aSettings.CourseSides[s] := True;
end;

function MapSettingsFileName: string;
begin
  Result := ExtractFilePath(gDLLPathFileName) + 'MapWindow\MapWindow.ini';
end;

// CC 20260922 missing file/keys fall back to defaults; out-of-range values are clamped
procedure LoadMapSettings;
var
  lIni: TIniFile;
  r, s: Integer;
begin
  DefaultMapSettings(gMapSettings);
  try
    if not FileExists(MapSettingsFileName) then Exit;
    lIni := TIniFile.Create(MapSettingsFileName);
    try
      for r := 0 to RING_COUNT - 1 do
        for s := 0 to SIDE_COUNT - 1 do
          gMapSettings.RangeCircles[r, s] := lIni.ReadBool(SEC_RINGS,
            RING_KEYS[r] + '.' + SIDE_KEYS[s], gMapSettings.RangeCircles[r, s]);
      // CC 20260924 earlier v16 builds stored an on/off FormationZones key; on maps to All Groups
      if lIni.ValueExists(SEC_OVERLAYS, 'FormationZoneMode') then
        gMapSettings.FormationZoneMode := lIni.ReadInteger(SEC_OVERLAYS,
          'FormationZoneMode', gMapSettings.FormationZoneMode)
      else if lIni.ReadBool(SEC_OVERLAYS, 'FormationZones', False) then
        gMapSettings.FormationZoneMode := ZONES_ALL;
      if (gMapSettings.FormationZoneMode < ZONES_NONE) or (gMapSettings.FormationZoneMode > ZONES_ALL) then
        gMapSettings.FormationZoneMode := ZONES_NONE;
      gMapSettings.VisibilityMode := lIni.ReadInteger(SEC_VIEW,
        'Visibility', gMapSettings.VisibilityMode);
      if (gMapSettings.VisibilityMode < VIS_PLAYER) or (gMapSettings.VisibilityMode > VIS_GOD) then
        gMapSettings.VisibilityMode := VIS_PLAYER;
      gMapSettings.CourseMode := lIni.ReadInteger(SEC_VIEW,
        'CourseMode', gMapSettings.CourseMode);
      if (gMapSettings.CourseMode < COURSE_NONE) or (gMapSettings.CourseMode > COURSE_ALL) then
        gMapSettings.CourseMode := COURSE_SELECTED;
      for s := 0 to SIDE_COUNT - 1 do
        gMapSettings.CourseSides[s] := lIni.ReadBool(SEC_VIEW,
          'CourseSide.' + SIDE_KEYS[s], gMapSettings.CourseSides[s]);
    finally
      lIni.Free;
    end;
  except
    DefaultMapSettings(gMapSettings);  // unreadable file — run with defaults
  end;
end;

// CC 20260922 never raise into HC — a read-only install dir just means no persistence
procedure SaveMapSettings;
var
  lIni: TIniFile;
  r, s: Integer;
begin
  try
    ForceDirectories(ExtractFilePath(MapSettingsFileName));
    lIni := TIniFile.Create(MapSettingsFileName);
    try
      lIni.CacheUpdates := True;  // one write in UpdateFile, not one per key
      for r := 0 to RING_COUNT - 1 do
        for s := 0 to SIDE_COUNT - 1 do
          lIni.WriteBool(SEC_RINGS, RING_KEYS[r] + '.' + SIDE_KEYS[s],
            gMapSettings.RangeCircles[r, s]);
      lIni.WriteInteger(SEC_OVERLAYS, 'FormationZoneMode', gMapSettings.FormationZoneMode);
      lIni.DeleteKey(SEC_OVERLAYS, 'FormationZones');  // CC 20260924 superseded v16 key
      lIni.WriteInteger(SEC_VIEW, 'Visibility', gMapSettings.VisibilityMode);
      lIni.WriteInteger(SEC_VIEW, 'CourseMode', gMapSettings.CourseMode);
      for s := 0 to SIDE_COUNT - 1 do
        lIni.WriteBool(SEC_VIEW, 'CourseSide.' + SIDE_KEYS[s], gMapSettings.CourseSides[s]);
      lIni.UpdateFile;
    finally
      lIni.Free;
    end;
  except
    // swallow — settings simply aren't persisted
  end;
end;

initialization
  DefaultMapSettings(gMapSettings);

end.
