{$mode objfpc}{$H+}
unit uTracks;

// CC 20260313 course track line and waypoint dot create/update/remove helpers

interface

uses
  Graphics,
  TGClasses, Globe4, TGSysUtils, GlobeUtils, TGPresenters, TGObjects,
  uHCTypeDefs, uHCUtils,
  uCICG4Presenters, uGroupSnapshot;

const
  GC_SEGMENTS_PER_LEG = 20;  // intermediate great circle points per course leg

procedure BuildTrackChain(const pGroup: PHCGroup; pLineChain: TPointStore; pDotChain: TPointStore);

procedure BuildTrackChainDrag(const pGroup: PHCGroup; pLineChain: TPointStore; pDotChain: TPointStore;
  dragWaypointIdx: Integer; const dragPtLL: TPointLL);

procedure CreateTrackPair(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  const pCenter: TPointLL; aColor: TColor;
  var ioNextID: Integer;
  out linePresID, lineObjID, dotPresID, dotObjID: Integer);

procedure UpdateTrackChains(aLayer: TGlobeLayer;
  lineObjID, dotObjID: Integer;
  const pGroup: PHCGroup; visible: Boolean; const fallbackPos: TPointLL);

procedure RemoveTrackPair(aLayer: TGlobeLayer;
  linePresID, lineObjID, dotPresID, dotObjID: Integer);

procedure UpdateTrackColor(aLayer: TGlobeLayer;           // CC 20260322
  linePresID, dotPresID: Integer; aColor: TColor);

procedure BuildTrackChainFromSnapshot(groupSLon, groupSLat: LongInt;
  const aNodes: array of TTrackNodeSnapshot; nodeCount: Integer;
  pLineChain, pDotChain: TPointStore);

implementation

// CC 20260313 build line chain (great circle interpolated) and dot chain (waypoints only)
procedure BuildTrackChain(const pGroup: PHCGroup; pLineChain: TPointStore; pDotChain: TPointStore);
var
  pRec: PHCPathRec;
  ptFrom, ptTo, ptGC: TPointLL;
  i: Integer;
begin
  pLineChain.Count := 0;
  pDotChain.Count  := 0;
  ptFrom := PointLLH(
    DecimalToGlobeUnits(HCLon2Deg(pGroup^.sLon)),
    DecimalToGlobeUnits(HCLat2Deg(pGroup^.sLat)), 0);
  pLineChain.Add(ptFrom);

  if pGroup^.track <> nil then
  begin
    pRec := pGroup^.track;
    while pRec <> nil do
    begin
      ptTo := PointLLH(
        DecimalToGlobeUnits(HCLon2Deg(pRec^.DestLon)),
        DecimalToGlobeUnits(HCLat2Deg(pRec^.DestLat)), 0);
      // Interpolate great circle arc from ptFrom to ptTo
      for i := 1 to GC_SEGMENTS_PER_LEG do
      begin
        ptGC := GreatCirclePoint(ptFrom, ptTo, i / GC_SEGMENTS_PER_LEG);
        pLineChain.Add(ptGC);
      end;
      pDotChain.Add(ptTo);
      ptFrom := ptTo;
      pRec := PHCPathRec(pRec^.next);
    end;
  end;
end;

// CC 20260313 build interpolated chains with one waypoint replaced (for drag preview)
procedure BuildTrackChainDrag(const pGroup: PHCGroup; pLineChain: TPointStore; pDotChain: TPointStore;
  dragWaypointIdx: Integer; const dragPtLL: TPointLL);
var
  pRec: PHCPathRec;
  ptFrom, ptTo, ptGC: TPointLL;
  i, wpIdx: Integer;
begin
  pLineChain.Count := 0;
  pDotChain.Count  := 0;
  ptFrom := PointLLH(
    DecimalToGlobeUnits(HCLon2Deg(pGroup^.sLon)),
    DecimalToGlobeUnits(HCLat2Deg(pGroup^.sLat)), 0);
  pLineChain.Add(ptFrom);

  if pGroup^.track <> nil then
  begin
    pRec := pGroup^.track;
    wpIdx := 0;
    while pRec <> nil do
    begin
      if wpIdx = dragWaypointIdx then
        ptTo := dragPtLL
      else
        ptTo := PointLLH(
          DecimalToGlobeUnits(HCLon2Deg(pRec^.DestLon)),
          DecimalToGlobeUnits(HCLat2Deg(pRec^.DestLat)), 0);
      for i := 1 to GC_SEGMENTS_PER_LEG do
      begin
        ptGC := GreatCirclePoint(ptFrom, ptTo, i / GC_SEGMENTS_PER_LEG);
        pLineChain.Add(ptGC);
      end;
      pDotChain.Add(ptTo);
      ptFrom := ptTo;
      Inc(wpIdx);
      pRec := PHCPathRec(pRec^.next);
    end;
  end;
end;

procedure CreateTrackPair(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  const pCenter: TPointLL; aColor: TColor;
  var ioNextID: Integer;
  out linePresID, lineObjID, dotPresID, dotObjID: Integer);
var
  lLinePres: TPolylinePresenter;
  lDotPres:  TCICG4PointPresenter;
  lLineObj:  TGeoDataObject;
  lDotObj:   TGeoDataObject;
begin
  // Track line presenter + object
  lLinePres := TPolylinePresenter.Create(aGlobe, 0);
  lLinePres.PolyPen.PenColor := aColor;
  aLayer.Presenters.Add(lLinePres);
  linePresID := lLinePres.PresenterID;
  lLineObj := TGeoDataObject.Create(aLayer.Objects);
  lLineObj.PresenterID := lLinePres.PresenterID;
  lLineObj.ID := ioNextID;
  lLineObj.Centroid := pCenter;
  lLineObj.Chains[0].Count := 0;
  lineObjID := ioNextID;
  Inc(ioNextID);

  // Waypoint dot presenter + object (small filled circles)
  lDotPres := TCICG4PointPresenter.Create(aGlobe, 0);
  lDotPres.PointPen.PenColor   := aColor;
  lDotPres.PointBrush.BrushColor := aColor;
  lDotPres.PointType := ppCircle;
  lDotPres.PointSize := 6;
  lDotPres.PointUnit := Pixel;
  aLayer.Presenters.Add(lDotPres);
  dotPresID := lDotPres.PresenterID;
  lDotObj := TGeoDataObject.Create(aLayer.Objects);
  lDotObj.PresenterID := lDotPres.PresenterID;
  lDotObj.ID := ioNextID;
  lDotObj.Centroid := pCenter;
  lDotObj.Chains[0].Count := 0;
  dotObjID := ioNextID;
  Inc(ioNextID);
end;

procedure UpdateTrackChains(aLayer: TGlobeLayer;
  lineObjID, dotObjID: Integer;
  const pGroup: PHCGroup; visible: Boolean; const fallbackPos: TPointLL);
var
  lLineObj, lDotObj: TGlobeObject;
begin
  lLineObj := aLayer.ObjectByID(lineObjID);
  if lLineObj = nil then Exit;

  if visible then
  begin
    lDotObj := aLayer.ObjectByID(dotObjID);
    if lDotObj <> nil then
      BuildTrackChain(pGroup,
        TGeoDataObject(lLineObj).Chains[0],
        TGeoDataObject(lDotObj).Chains[0]);
    TGeoDataObject(lLineObj).RedrawObject;
  end
  else
  begin
    TGeoDataObject(lLineObj).Chains[0].Count := 0;
    lDotObj := aLayer.ObjectByID(dotObjID);
    if lDotObj <> nil then
    begin
      TGeoDataObject(lDotObj).Chains[0].Count := 0;
      TGeoDataObject(lDotObj).Centroid := fallbackPos;
    end;
  end;
end;

// CC 20260322 update pen/brush color on existing track line and dot presenters
procedure UpdateTrackColor(aLayer: TGlobeLayer;
  linePresID, dotPresID: Integer; aColor: TColor);
var
  pres: TGlobePresenter;
begin
  pres := aLayer.Presenters.ByID(linePresID, False);
  if pres <> nil then
    TPolylinePresenter(pres).PolyPen.PenColor := aColor;
  pres := aLayer.Presenters.ByID(dotPresID, False);
  if pres <> nil then
  begin
    TCICG4PointPresenter(pres).PointPen.PenColor := aColor;
    TCICG4PointPresenter(pres).PointBrush.BrushColor := aColor;
  end;
end;

// CC 20260323 build track chains from snapshot data (flat array instead of linked list)
procedure BuildTrackChainFromSnapshot(groupSLon, groupSLat: LongInt;
  const aNodes: array of TTrackNodeSnapshot; nodeCount: Integer;
  pLineChain, pDotChain: TPointStore);
var
  ptFrom, ptTo, ptGC: TPointLL;
  i, seg: Integer;
begin
  pLineChain.Count := 0;
  pDotChain.Count  := 0;
  ptFrom := PointLLH(
    DecimalToGlobeUnits(HCLon2Deg(groupSLon)),
    DecimalToGlobeUnits(HCLat2Deg(groupSLat)), 0);
  pLineChain.Add(ptFrom);

  for i := 0 to nodeCount - 1 do
  begin
    ptTo := PointLLH(
      DecimalToGlobeUnits(HCLon2Deg(aNodes[i].DestLon)),
      DecimalToGlobeUnits(HCLat2Deg(aNodes[i].DestLat)), 0);
    for seg := 1 to GC_SEGMENTS_PER_LEG do
    begin
      ptGC := GreatCirclePoint(ptFrom, ptTo, seg / GC_SEGMENTS_PER_LEG);
      pLineChain.Add(ptGC);
    end;
    pDotChain.Add(ptTo);
    ptFrom := ptTo;
  end;
end;

procedure RemoveTrackPair(aLayer: TGlobeLayer;
  linePresID, lineObjID, dotPresID, dotObjID: Integer);
var
  obj: TGlobeObject;
  pres: TGlobePresenter;
begin
  // Remove track line
  if lineObjID >= 0 then
  begin
    obj := aLayer.ObjectByID(lineObjID);
    if obj <> nil then begin aLayer.Objects.Delete(obj); obj.Free; end;
    pres := aLayer.Presenters.ByID(linePresID, False);
    if pres <> nil then begin aLayer.Presenters.Delete(pres); pres.Free; end;
  end;
  // Remove track dots
  if dotObjID >= 0 then
  begin
    obj := aLayer.ObjectByID(dotObjID);
    if obj <> nil then begin aLayer.Objects.Delete(obj); obj.Free; end;
    pres := aLayer.Presenters.ByID(dotPresID, False);
    if pres <> nil then begin aLayer.Presenters.Delete(pres); pres.Free; end;
  end;
end;

end.
