library A2Arefueldemo;

{$mode delphi}{$H+}

{$R *.res}

uses
  Classes
  { you can add units after this }
  ,uHCDefines
  ,MainForm, uHCGCI, uHCGlobals, uHCTypeDefs, uHCEventHelpers
  ,uLazGUIGlobals
  ,Windows
  ,Forms
  ,ShellAPI
  ,SysUtils
  ,Types
  ,Dialogs, Interfaces, Controls;

const
  DLLVERN = 9;
  DLLVERINTN = 8;
  SIDES = 3;
  SLIVER360 = 134217728;

var
  frmMain: TfrmMain;
  intGameTime: Long;

{*** Return DLL internal version, i.e. revision 5 of my DLL ***}
function DLLVer():word;export;cdecl;
begin
  result := DLLVERN;
end;

{*** Return DLL interface version, i.e. revision 1 of my HCE interface spec ***}
function DLLVerInt():word;export;cdecl;
begin
  result := DLLVERINTN;
end;

{*** Return DLL description, 255 characters max ***}
function DLLDesc():PAnsiChar;export;cdecl;
begin
  result := 'lazarus GUI test/air refuel demo DLL by Tony Eischens/DonThomas';
end;

{*** Free DLL ***}
procedure DLLFree();export;cdecl;
begin
  // Do any cleanup here, this is the end
  frmMain.Free;
end;

{*** Init DLL ***}
procedure DLLInit(hwndHC: HWND; pathHC: PAnsiChar;
  verHC: PAnsiChar; verSaveGame: LongWord; funcRefs: PHCDLL_FUNC_REF;
  PGEClosing: Pointer; PDLLBusy: Pointer; verInterface: LongWord);export;cdecl;
var
  funcTmp: PHCDLL_FUNC_REF;
begin
  // here we initialize globals and such for the life of the DLL
  Application.Initialize;
  // ALE 20120410 Freezes HC Application.Run;
  uLazGUIGlobals.PGEClosing:=PGEClosing;
  uLazGUIGlobals.PDLLBusy:=PDLLBusy;
  frmMain := TfrmMain.Create(nil);
  frmMain.ParentWindow := hwndHC;
  frmMain.Caption := 'lazarus GUI Export DLLair refuel demo v' + IntToStr(DLLVERN)
   + ', ExportDLL Interface v' + IntToStr(DLLVERINTN);
  frmMain.memDLLInit.Lines.Add('lazarus GUI Export DLL v' + IntToStr(DLLVERN));
  frmMain.memDLLInit.Lines.Add('ExportDLL Interface v' + IntToStr(DLLVERINTN));
  frmMain.memDLLInit.Lines.Add('hwndHC = 0x' + IntToHex(hwndHC, 8));
  frmMain.memDLLInit.Lines.Add('pathHC = ' + pathHC);
  frmMain.memDLLInit.Lines.Add('verHC = ' + verHC);
  frmMain.memDLLInit.Lines.Add('verSaveGame = 0x' + IntToHex(verSaveGame, 8));
  frmMain.memDLLInit.Lines.Add(Format('funcRefs = 0x%p', [funcRefs]));
  frmMain.memDLLInit.Lines.Add(Format('GE ExportDLL Interface Version = %d', [verInterface]));

  // ALE 20130908 List the function pointers sent over by the GE
  //  then capture one of them for example usage
  funcTmp := funcRefs;
  while (funcTmp <> nil) do
  begin
    frmMain.memDLLInit.Lines.Add(#9 + Format('funcRef: 0x%p %s', [funcTmp^.funcPtr, funcTmp^.funcName]));

    if (funcTmp^.funcName = 'NewPtr') then
      frmMain.NewPtr := funcTmp^.funcPtr
    else if (funcTmp^.funcName = 'QueueHarpoonEvent') then
      frmMain.HCQueueHarpoonEvent := THCQueueHarpoonEvent(funcTmp^.funcPtr)
    else if (funcTmp^.funcName = 'dllCheckLatLonDepth') then
      frmMain.HCdllCheckLatLonDepth := THCdllCheckLatLonDepth(funcTmp^.funcPtr);

    funcTmp := funcTmp^.next;
  end;

  frmMain.Show;
end;

procedure UnitLatLon(UnitID: Byte; GroupID: PAnsiChar; Lat: Double; Lon: Double);export;cdecl;
begin
  if (frmMain.ULLckbEnabled.Checked = True) then
  begin
    frmMain.ULLmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Lat=%3.2f, Lon=%3.2f, Depth/Altitude=%d', [intGameTime, GroupID, UnitID, Lat, Lon, frmMain.HCdllCheckLatLonDepth(Lat, Lon)]));
    if (frmMain.ULLmemMsgLog.Lines.Count > frmMain.ULLedtLines.Value) then
    begin
      frmMain.ULLmemMsgLog.Lines.Delete(frmMain.ULLedtLines.Value);
    end;
  end;
end;

procedure UnitShowing(UnitID: Byte; GroupID: PAnsiChar; Showing: Byte);export;cdecl;
begin
  if (frmMain.USckbEnabled.Checked = True) then
  begin
    frmMain.USmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Showing=%d', [intGameTime, GroupID, UnitID, Showing]));
    if (frmMain.USmemMsgLog.Lines.Count > frmMain.USedtLines.Value) then
    begin
      frmMain.USmemMsgLog.Lines.Delete(frmMain.USedtLines.Value);
    end;
  end;
end;

procedure UnitURegion(UnitID: Byte; GroupID: PAnsiChar; pUR: PTURegion);export;cdecl;
var
  intC1, intC2: integer;
  strText: string;
begin
  if (frmMain.UURckbEnabled.Checked = True) then
  begin
    strText := Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitURegion', [intGameTime, GroupID, UnitID]);
    for intC1 := 0 to SIDES-1 do
    begin
      for intC2 := 0 to 3 do // Four corners of uncertainty region
      begin
        // Dump data to text file
        // (double)((sLon*360.0)/SLIVER360))
        // (double)(90-(sLat*360.0/SLIVER360))
        strText := strText + Format(', %8.8f, %8.8f',
         [(90-(pUR^[intC1]^.sLat[intC2]*360.0/SLIVER360)),
         ((pUR^[intC1]^.sLon[intC2]*360.0)/SLIVER360)]);
      end;
    end;
    frmMain.UURmemMsgLog.Lines.Insert(0, strText);
    if (frmMain.UURmemMsgLog.Lines.Count > frmMain.UURedtLines.Value) then
    begin
      frmMain.UURmemMsgLog.Lines.Delete(frmMain.UURedtLines.Value);
    end;
  end;
end;

procedure UnitCourse(UnitID: Byte; GroupID: PAnsiChar; course: smallint);export;cdecl;
begin
  if (frmMain.UCckbEnabled.Checked = True) then
  begin
    frmMain.UCmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Course (deg)=%d', [intGameTime, GroupID, UnitID, course]));
    if (frmMain.UCmemMsgLog.Lines.Count > frmMain.UCedtLines.Value) then
    begin
      frmMain.UCmemMsgLog.Lines.Delete(frmMain.UCedtLines.Value);
    end;
  end;
end;

procedure UnitSpeed(UnitID: Byte; GroupID: PAnsiChar; speed: smallint);export;cdecl;
begin
  if (frmMain.USPckbEnabled.Checked = True) then
  begin
    frmMain.USPmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Speed (kts)=%d', [intGameTime, GroupID, UnitID, speed]));
    if (frmMain.USPmemMsgLog.Lines.Count > frmMain.USPedtLines.Value) then
    begin
      frmMain.USPmemMsgLog.Lines.Delete(frmMain.USPedtLines.Value);
    end;
  end;
end;

procedure UnitAlt(UnitID: Byte; GroupID: PAnsiChar; alt: byte);export;cdecl;
begin
  if (frmMain.UAckbEnabled.Checked = True) then
  begin
    frmMain.UAmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Altitude Band=%d', [intGameTime, GroupID, UnitID, alt]));
    if (frmMain.UAmemMsgLog.Lines.Count > frmMain.UAedtLines.Value) then
    begin
      frmMain.UAmemMsgLog.Lines.Delete(frmMain.UAedtLines.Value);
    end;
  end;
end;

procedure UnitShowingSide(UnitID: Byte; GroupID: PAnsiChar; showing: PTSidesUChar);export;cdecl;
var
  intC: integer;
  strText: string;
begin
  if (frmMain.USSckbEnabled.Checked = True) then
  begin
    strText := '';
    for intC := 0 to SIDES-1 do
    begin
      // Dump data to text file
      strText := strText + Format('%3.3d', [showing^[intC]]);
    end;
    frmMain.USSmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Showing=%s', [intGameTime, GroupID, UnitID, strText]));
    if (frmMain.USSmemMsgLog.Lines.Count > frmMain.USSedtLines.Value) then
    begin
      frmMain.USSmemMsgLog.Lines.Delete(frmMain.USSedtLines.Value);
    end;
  end;
end;

procedure UnitSink(UnitID: Byte; GroupID: PAnsiChar);export;cdecl;
begin
  if (frmMain.USKckbEnabled.Checked = True) then
  begin
    frmMain.USKmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Sinking', [intGameTime, GroupID, UnitID]));
    if (frmMain.USKmemMsgLog.Lines.Count > frmMain.USKedtLines.Value) then
    begin
      frmMain.USKmemMsgLog.Lines.Delete(frmMain.USKedtLines.Value);
    end;
  end;
end;

procedure UnitToGroup(UnitID: Byte; GroupID, GroupIDold: PAnsiChar);export;cdecl;
begin
  if (frmMain.UGckbEnabled.Checked = True) then
  begin
    frmMain.UGmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitToGroup OldGroup=%s', [intGameTime, GroupID, UnitID, GroupIDold]));
    if (frmMain.UGmemMsgLog.Lines.Count > frmMain.UGedtLines.Value) then
    begin
      frmMain.UGmemMsgLog.Lines.Delete(frmMain.UGedtLines.Value);
    end;
  end;
end;

procedure UnitNew(UnitID: Byte; GroupID: PAnsiChar; utype: shortint; side: byte; annexID: word; name: PAnsiChar);export;cdecl;
begin
  if (frmMain.UNckbEnabled.Checked = True) then
  begin
    frmMain.UNmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitNew Type=%d, Side=%d, AnnexID=%d, Name=%s', [intGameTime, GroupID, UnitID, utype, side, annexID, name]));
    if (frmMain.UNmemMsgLog.Lines.Count > frmMain.UNedtLines.Value) then
    begin
      frmMain.UNmemMsgLog.Lines.Delete(frmMain.UNedtLines.Value);
    end;
  end;
end;

procedure UnitFree(UnitID: Byte; GroupID: PAnsiChar);export;cdecl;
begin
  if (frmMain.UFckbEnabled.Checked = True) then
  begin
    frmMain.UFmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitFree', [intGameTime, GroupID, UnitID]));
    if (frmMain.UFmemMsgLog.Lines.Count > frmMain.UFedtLines.Value) then
    begin
      frmMain.UFmemMsgLog.Lines.Delete(frmMain.UFedtLines.Value);
    end;
  end;
end;

procedure ScenLoad(strHarpPath: PAnsiChar; intScenNum: SmallInt; strScenFPath: PAnsiChar; intBSNum: ShortInt; strBSFName: PAnsiChar; intSide: byte);export;cdecl;
begin
  if (frmMain.SLckbEnabled.Checked = True) then
  begin
    frmMain.SLmemMsgLog.Lines.Insert(0, Format('ScenLoad HC Path=%s, Scen#=%d, ScenFNP=%s, BattleSet=%d, BattleSet=%s, PlayerSide=%d', [strHarpPath, intScenNum, strScenFPath, intBSNum, strBSFName, intSide]));
    if (frmMain.SLmemMsgLog.Lines.Count > frmMain.SLedtLines.Value) then
    begin
      frmMain.SLmemMsgLog.Lines.Delete(frmMain.SLedtLines.Value);
    end;
  end;
end;

procedure TimeFactor(TimeFactor: longint);export;cdecl;
begin
  if (frmMain.TFckbEnabled.Checked = True) then
  begin
    frmMain.TFmemMsgLog.Lines.Insert(0, Format('%8.8d: TimeFactor=%d', [intGameTime, TimeFactor]));
    if (frmMain.TFmemMsgLog.Lines.Count > frmMain.TFedtLines.Value) then
    begin
      frmMain.TFmemMsgLog.Lines.Delete(frmMain.TFedtLines.Value);
    end;
  end;
end;

procedure MsgLog(MsgLine: PAnsiChar);export;cdecl;
begin
  if (frmMain.MLckbEnabled.Checked = True) then
  begin
    frmMain.MLmemMsgLog.Lines.Insert(0, Format('%8.8d: %s', [intGameTime, MsgLine]));
    if (frmMain.MLmemMsgLog.Lines.Count > frmMain.MLedtLines.Value) then
    begin
      frmMain.MLmemMsgLog.Lines.Delete(frmMain.MLedtLines.Value);
    end;
  end;
end;

procedure GameTime(GameTime: Long);export;cdecl;
var
  PEvent: PHCHarpoonEvent;
  EventType: uchar;
  strGroupID: String;
begin
  intGameTime := GameTime;

  frmMain.GTlblGameTime.Caption :=
   'GameTime: ' + Format('%8.8d', [intGameTime]);

  // ALE 20130812 If the user wants to look at data structures
  //  then make sure the GE doesn't keep processing until the
  //  user is done.  You would use this mechanism to manipulate
  //  the data structures so that they maintain a consistent state.
  // ALE 20130816 You don't have to make use of PDLLBusy if you don't
  //  mind the UI freezing during your processing.  Freezing is a fine
  //  approach for things like moving units or firing missiles that
  //  don't require immediate user feedback.
  // ALE 20130816 Note also that GameTime is called at the beginning of the
  //  game second.  Any of the enabled stock GE effects will run after this function
  //  or if using PDLLBusy then once you unbusy any DLLs.
  if (frmMain.GTckbPause.Checked) then
  begin
    Byte(PDLLBusy^) := CTRUE; // ALE 20130816 don't process other events in GE
    frmMain.GTbtnResume.Enabled:=True;
    frmMain.GTckbPause.Enabled:=False;
    frmMain.PageControl1.ActivePageIndex:=21; // GameTime/InvestigateStructures

    // ALE 20130816 this would be a good place to start your thread that does the long processing
    PEvent := frmMain.PHarpEventHead;
    frmMain.GEHEmemDetail.Clear;
    frmMain.GTHEEventList.Clear;
    frmMain.GTHElbxAirUnitsInfo.Clear;        // ddt
    while (PEvent <> nil) do
    begin
      EventType := PEvent^.flag AND CommandMask;
      strGroupID := '   ';

      if (EventType = AIR_REFUEL_EVENT) then // ddt I only want refuel events
      begin
      if (PEvent^.ourGroup <> nil) then
      strGroupID := PHCGroup(PEvent^.ourGroup)^.id;
      frmMain.GTHEEventList.Items.Add(Format('%8.5d 0x%p %s %3.3d',
      [PEvent^.Time, PEvent, strGroupID, PEvent^.UnitID]));
      end;
      PEvent := PEvent^.next;
    end;
  end;
end;

procedure StartGroup(StartGroup: PHCGroup);export;cdecl;
var
  PHCGThis: PHCGroup;
begin
  if (frmMain.SGckbEnabled.Checked = True) then
  begin
    if (StartGroup <> nil) then
    begin
      if (StartGroup^.Units <> nil) then
      begin
        frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: %s %s', [intGameTime, StartGroup^.id, StartGroup^.Units^.name]));
      end
      else
      begin
        frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: %s', [intGameTime, StartGroup^.id]));
      end
    end
    else
    begin
      frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: StartGroup is NULL', [intGameTime]));
    end;

    if (frmMain.SGmemMsgLog.Lines.Count > frmMain.SGedtLines.Value) then
    begin
      frmMain.SGmemMsgLog.Lines.Delete(frmMain.SGedtLines.Value);
    end;

    // ALE 20130317 Fill up the SGlbxGroups
    frmMain.PStartGroup:=StartGroup;
    frmMain.SGlbxGroups.Items.Clear;
    frmMain.SGlbxGroups.Sorted := True;
    PHCGThis := StartGroup;
    while (PHCGThis <> nil) do
    begin
      frmMain.SGlbxGroups.Items.Add(PHCGThis^.id);
      PHCGThis := PHCGThis^.toSouth;
    end;
  end;
  frmMain.PStartGroup:=StartGroup;
end;

procedure HarpoonEventHead(HarpEventHead: PHCHarpoonEvent);export;cdecl;
var
  PTEvent: PHCHarpoonEvent;
  EventType: uchar;
  strGroupID: String;

begin
  if (HarPEventHead <> nil) then
 begin
  if ((HarpEventHead^.flag AND CommandMask) = AIR_REFUEL_EVENT) then   // ddt
        begin
         //   add to pointer list
          PTEvent := HarpEventHead;
          frmMain.GTHElbxAirUnitsInfo.Clear;
          frmMain.GEHEmemDetail.Clear;
          frmMain.GTHEEventList.Clear;
          while (PTEvent <> nil) do
          begin
            EventType := PTEvent^.flag AND CommandMask;
            if (EventType = AIR_REFUEL_EVENT) then
            begin
              strGroupID := '   ';
              if (PTEvent^.ourGroup <> nil) then
              strGroupID := PHCGroup(PTEvent^.ourGroup)^.id;
              frmMain.GTHEEventList.Items.Add(Format('%8.5d 0x%p %s %3.3d',
              [PTEvent^.Time, PTEvent, strGroupID, PTEvent^.UnitID]));
            end;
            PTEvent := PTEvent^.next;
          end;
          //call up procedure to update refuel info
          frmMain.GTHEEventListRefuelData(PHCAirRefuelEvent(HarpEventHead^.DataPtr), HarpEventHead);
        end;      // now back to lazgui
  if (frmMain.HHckbEnabled.Checked = True) then
    begin
      frmMain.HHmemMsgLog.Lines.Insert(0, Format('GT: %8.8d: 0x%p type: %s', [intGameTime, HarpEventHead,
           HCEventType2String(HarpEventHead^.flag AND CommandMask)]));
    end;
  if (frmMain.HHmemMsgLog.Lines.Count > frmMain.HHedtLines.Value) then
      frmMain.HHmemMsgLog.Lines.Delete(frmMain.HHedtLines.Value);
 frmMain.PHarpEventHead:=HarpEventHead;
 end
 else
 frmMain.HHmemMsgLog.Lines.Insert(0, Format('%8.8d: HarpoonEventHead is NULL', [intGameTime]));
end;

procedure SonobuoyRegionHead(SonobuoyRegionHead: PHCSonobuoyRegion);export;cdecl;
begin
  if (frmMain.SHckbEnabled.Checked = True) then
  begin
    if (SonobuoyRegionHead <> nil) then
    begin
      frmMain.SHmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, SonobuoyRegionHead]));
    end
    else
    begin
      frmMain.SHmemMsgLog.Lines.Insert(0, Format('%8.8d: SonobuoyRegionHead is NULL', [intGameTime]));
    end;

    if (frmMain.SHmemMsgLog.Lines.Count > frmMain.SHedtLines.Value) then
    begin
      frmMain.SHmemMsgLog.Lines.Delete(frmMain.SHedtLines.Value);
    end;
  end;
  frmMain.PSonobuoyRegionHead := SonobuoyRegionHead;
end;

procedure AllDeadUnitsPtr(AllDeadUnitsPtr: PHCDeadListItem);export;cdecl;
begin
  if (frmMain.DUckbEnabled.Checked = True) then
  begin
    if (AllDeadUnitsPtr <> nil) then
    begin
      frmMain.DUmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, AllDeadUnitsPtr]));
    end
    else
    begin
      frmMain.DUmemMsgLog.Lines.Insert(0, Format('%8.8d: AllDeadUnitsPtr is NULL', [intGameTime]));
    end;

    if (frmMain.DUmemMsgLog.Lines.Count > frmMain.DUedtLines.Value) then
    begin
      frmMain.DUmemMsgLog.Lines.Delete(frmMain.DUedtLines.Value);
    end;
  end;
  frmMain.PAllDeadUnitsPtr := AllDeadUnitsPtr;
end;

procedure WeatherGroups(WeatherGroups: PHCGroup);export;cdecl;
begin
  if (frmMain.WGckbEnabled.Checked = True) then
  begin
    if (WeatherGroups <> nil) then
    begin
      frmMain.WGmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, WeatherGroups]));
    end
    else
    begin
      frmMain.WGmemMsgLog.Lines.Insert(0, Format('%8.8d: WeatherGroups is NULL', [intGameTime]));
    end;

    if (frmMain.WGmemMsgLog.Lines.Count > frmMain.WGedtLines.Value) then
    begin
      frmMain.WGmemMsgLog.Lines.Delete(frmMain.WGedtLines.Value);
    end;
  end;
  frmMain.PWeatherGroups := WeatherGroups;
end;

procedure VictoryConditionsList(VictoryConditionsList: PHCVictoryConditions);export;cdecl;
begin
  if (frmMain.VCckbEnabled.Checked = True) then
  begin
    if (VictoryConditionsList <> nil) then
    begin
      frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, VictoryConditionsList]));
    end
    else
    begin
      frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: VictoryConditionsList is NULL', [intGameTime]));
    end;

    if (frmMain.VCmemMsgLog.Lines.Count > frmMain.VCedtLines.Value) then
    begin
      frmMain.VCmemMsgLog.Lines.Delete(frmMain.VCedtLines.Value);
    end;
  end;
  frmMain.PVictoryConditionsList := VictoryConditionsList;
end;

exports
  DLLVer,
  DLLVerInt,
  DLLDesc,
  DLLFree,
  DLLInit,
  UnitLatLon,
  UnitShowing,
  UnitURegion,
  UnitCourse,
  UnitSpeed,
  UnitAlt,
  UnitShowingSide,
  UnitSink,
  UnitToGroup,
  UnitNew,
  UnitFree,
  ScenLoad,
  TimeFactor,
  MsgLog,
  GameTime
  ,StartGroup // ALE 20130205 Sends updated GroupPtr whenever that changes
  ,HarpoonEventHead // ALE 20130805 Sends updated soonest HarpoonEvent pointer whenever it changes
  ,SonobuoyRegionHead // ALE 20130812 Sends updated pointer to all sonobuoys when it changes
  ,AllDeadUnitsPtr // ALE 20130812 Sends updated pointer to the list of killed units used for victory condition evaluation
  ,WeatherGroups // ALE 20130812 Sends update GroupPtr whenever the top weather group changes
  ,VictoryConditionsList // ALE 20130812 Sends a pointer to the list of victory conditions when a scenario is loaded
  ;
end.

