unit RadioController;

{
  EWSDR — Radio Controller (headless ядро)

  Назначение
  ----------
  TRadioController — «мозг» трансивера БЕЗ привязки к UI/LCL-формам. Владеет
  состоянием радио и движками (сеть HPSDR, WDSP, аудио, настройки, каналы) и
  предоставляет единый интерфейс управления для ЛЮБОГО фронтенда: GUI, Web, CAT,
  аппаратная панель Arduino.

    • Команды (SetMode, SetVfoA, TuneActiveBy, StartStop, …) — единая точка входа.
      Абсолютные (Set*) удобны UI/Web; относительные (*By/Up/Down) — энкодерам.
    • GetSnapshot → TRadioSnapshot — весь стейт одной записью (синхронизация
      нового фронтенда при подключении: LED/дисплеи панели, веб-клиент).
    • OnStateChanged(Field) — гранулярное уведомление об изменении поля; все
      фронтенды остаются синхронны, кто бы ни инициировал изменение.

  Маршалинг потоков
  -----------------
  Команды из сетевых/serial/web-потоков идут через OnInvoke (GUI = обёртка над
  TThread.Synchronize; демон = своя очередь/прямой вызов), чтобы вызовы движка
  шли из одного потока без зависимости от GUI-цикла LCL.

  СТАТУС МИГРАЦИИ (Фаза 1)
  ------------------------
  Состояние и движки вынесены сюда как ПУБЛИЧНЫЕ ПОЛЯ с теми же именами, что были
  в TMainForm (FVfoA, FNetwork, FDSPEngine, …), чтобы проводка MainForm свелась к
  механической замене FXxx -> FController.FXxx без изменения поведения. Логика
  операций (ApplyModeFilter, BtnStartStopClick, ApplyVfoA, …) пока остаётся в
  MainForm; команды контроллера обновляют поле + шлют событие и будут наполнены
  логикой на следующих фазах (помечено TODO-wiring). Поля станут приватными,
  когда MainForm перестанет писать в них напрямую.

  ВЛАДЕНИЕ ДВИЖКАМИ (Фаза 1): объекты создаёт и освобождает MainForm (в
  FormCreate/FormDestroy), сохраняя в поля контроллера. Перенос создания в
  Create/Destroy контроллера — отдельная фаза (нужен для режима демона).

  FPC 3.2+ / режим Delphi. Без inline var и анонимных процедур.
}

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

uses
  Classes, SysUtils,
  HPSDRProtocol, HPSDRNetwork,
  WDSPEngine, AudioOutput, AudioInput,
  Settings, ChannelStore;

type
  // Поле, которое изменилось — для гранулярных уведомлений наружу.
  TRadioField = (
    rfVfoA, rfVfoB, rfActiveVfo,
    rfMode, rfFilter, rfFilterBW,
    rfAGCMode, rfAGCTop,
    rfVolume, rfMute, rfDrive, rfAtten,
    rfBand, rfXvtr,
    rfCenterFreq, rfSpan, rfSampleRate,
    rfCTun, rfDuplex,
    rfRunning, rfConnected, rfTransmitting, rfTuning,
    rfNR, rfNB, rfSNB, rfANF,
    rfWfAGC, rfWfNF, rfDither, rfRandom,
    rfFMDeviation, rfFMCTCSS, rfFMCTCSSTone, rfFMSQ, rfFMSQLevel,
    rfFMStep, rfFMStepIdx, rfFMRpt,
    rfChannel,
    rfSMeter, rfPower, rfSWR, rfSupply, rfPLL,   // телеметрия
    rfDevice                                     // подключённое устройство сменилось
  );

  TRadioStateEvent = procedure(Sender: TObject; Field: TRadioField) of object;

  // Маршалинг произвольного метода в поток контроллера (GUI: TThread.Synchronize).
  TUIInvokeEvent = procedure(M: TThreadMethod) of object;

  // Снимок состояния для синхронизации нового фронтенда (Arduino/Web на connect).
  TRadioSnapshot = record
    VfoA, VfoB, CenterFreq, SpanHz: Double;
    SampleRate: Integer;
    ActiveVfo: Integer;
    Mode, Filter, FilterBW: Integer;
    AGCMode, AGCTop: Integer;
    Volume, Drive, Atten: Integer;
    Band, Xvtr: Integer;
    Muted, CTun, Duplex: Boolean;
    Running, Connected, Transmitting, Tuning: Boolean;
    NRMode, NBMode: Integer;
    SNB, ANF: Boolean;
    WfAGC, WfNF: Boolean;
    FMStepOn: Boolean;
    FMStepIdx: Integer;
    FMCTCSSOn: Boolean;
    FMCTCSSToneIdx: Integer;
    FMSQOn: Boolean;
    FMSQLevel: Integer;
    FMRptDir: Integer;
    SMeter, FwdW, SWR, SupplyV, SupplyA: Double;
    PLLLock: Boolean;
  end;

  { TRadioController }
  TRadioController = class
  private
    FOnInvoke:       TUIInvokeEvent;
    FOnStateChanged: TRadioStateEvent;
    procedure Changed(Field: TRadioField);
  public
    // ============================================================
    //  ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm).
    //  Внешний чистый доступ для адаптеров — через команды / GetSnapshot /
    //  OnStateChanged. Прямая запись извне — временная мера на время Фазы 1.
    // ============================================================

    // ---- Движки (владение: см. шапку юнита) ----
    FNetwork:      THPSDRNetwork;
    FDSPEngine:    TWDSPEngine;
    FAudioOut:     TAudioOutput;
    FAudioIn:      TAudioInput;
    FSettings:     TSettingsManager;
    FChannelStore: TChannelStore;
    FWDSPReady:    Boolean;

    // ---- Частоты / режим / фильтр / AGC ----
    FVfoA, FVfoB:   Double;
    FActiveVfo:     Integer;
    FMode:          Integer;
    FFilter:        Integer;
    FFilterBW:      Integer;
    FAGCMode:       Integer;
    FAGCTop:        Integer;
    FCTun:          Boolean;
    FCenterFreq:    Double;
    FSpanHz:        Double;
    FSampleRate:    Integer;
    FCurrentBand:   Integer;
    FCurrentXvtr:   Integer;

    // ---- Аудио / TX ----
    FVolume:        Integer;
    FMuted:         Boolean;
    FDrivePercent:  Integer;   // драйв 0..100 % (UI-уровень, источник истины)
    FDriveLevel:    Byte;      // вычисляемый hardware-байт (CalcDriveByte)
    FAtten:         Integer;
    FPAMaxPower:    Double;
    FRunning:       Boolean;
    FTransmitting:  Boolean;
    FTuning:        Boolean;
    FSplitTxB:      Boolean;
    FDisplayDuplex: Boolean;
    FHWPTTActive:    Boolean;
    FHWPTTStartedTX: Boolean;
    FSendAudioToRadio: Boolean;
    FAudioOutDevName:  string;
    FAudioInDevName:   string;
    FDevConnected:  Boolean;

    // ---- DSP-фильтры шума / водопад ----
    FNRMode, FNBMode: Integer;   // источник истины (раньше Btn*.Tag)
    FSNB, FANF:       Boolean;   // источник истины (раньше Btn*.Tag)
    FWfAGCEnabled:    Boolean;
    FWfNFEnabled:     Boolean;
    FDitherEnabled:   Boolean;
    FRandomEnabled:   Boolean;

    // ---- FM ----
    FFMDeviation:    Double;
    FFMCTCSSOn:      Boolean;
    FFMCTCSSToneIdx: Integer;
    FFMSQOn:         Boolean;
    FFMSQLevel:      Integer;
    FFMStepOn:       Boolean;
    FFMStepIdx:      Integer;
    FFMRptDir:       Integer;
    FFMRptOffsetHz:  Double;
    FFMRptAutoActive:   Boolean;
    FFMCTCSSAutoActive: Boolean;

    // ---- Конфиги / устройство (per-MAC) ----
    FTXSettings:   TTXSettings;
    FAlexSettings: TAlexSettings;
    FXvtrSettings: TXvtrSettings;
    FBandCache:    array[0..CFG_BAND_COUNT-1] of TBandSettings;
    FPABandCal:    array[0..CFG_BAND_COUNT-1] of Double;
    FVHFBandCal:   array[0..CFG_XVTR_COUNT-1] of Double;
    FDevMAC:       array[0..5] of Byte;

    // ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ----
    FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double;
    FLastPLLLock: Boolean;

    constructor Create;
    destructor Destroy; override;

    // Маршалинг команды в поток контроллера (GUI: TThread.Synchronize).
    procedure Invoke(M: TThreadMethod);

    // Снимок всего состояния (для синхронизации нового фронтенда).
    function GetSnapshot: TRadioSnapshot;

    // ====================================================================
    //  КОМАНДЫ — единый API для GUI / Web / CAT / Arduino.
    //  Фаза 1: обновляют поле + шлют событие; логика движка — TODO-wiring.
    // ====================================================================

    // Частоты / VFO
    procedure SetVfoA(Hz: Double);
    procedure SetVfoB(Hz: Double);
    procedure SetActiveVfo(Idx: Integer);
    procedure TuneActiveBy(DeltaHz: Double);   // энкодер VFO
    procedure SetCenter(Hz: Double);
    procedure VfoSwap;
    procedure VfoACopyB;
    procedure VfoBCopyA;

    // Режим / фильтр / AGC
    procedure SetMode(M: Integer);
    procedure SetFilterIdx(Idx: Integer);
    procedure SetFilterBW(BWHz: Integer);
    procedure SetAGCMode(M: Integer);
    procedure SetAGCTop(DB: Integer);
    procedure AGCTopBy(Delta: Integer);

    // Громкость / mute / drive / аттенюатор
    procedure SetVolume(V: Integer);
    procedure VolumeBy(Delta: Integer);
    procedure SetMute(On_: Boolean);
    procedure ToggleMute;
    procedure SetDrive(V: Integer);
    procedure DriveBy(Delta: Integer);
    procedure SetAtten(Idx: Integer);

    // Диапазоны / трансвертеры / спан
    procedure SetBand(Idx: Integer);
    procedure BandUp;
    procedure BandDown;
    procedure SetXvtrBand(Idx: Integer);       // -1 = выход в HF
    procedure SetSpan(Hz: Integer);
    procedure SetSampleRate(Hz: Integer);

    // Отображение / приём
    procedure SetCTun(On_: Boolean);
    procedure SetDuplex(On_: Boolean);
    procedure SetWfAGC(On_: Boolean);
    procedure SetWfNF(On_: Boolean);

    // DSP-фильтры шума
    procedure SetNR(Mode: Integer);
    procedure SetNB(Mode: Integer);
    procedure SetSNB(On_: Boolean);
    procedure SetANF(On_: Boolean);

    // Передача
    procedure SetMOX(On_: Boolean);
    procedure SetTune(On_: Boolean);           // TUN

    // FM
    procedure SetFMStep(Idx: Integer);
    procedure SetFMCTCSS(On_: Boolean);
    procedure SetFMCTCSSTone(Idx: Integer);
    procedure SetFMSquelch(On_: Boolean);
    procedure SetFMSquelchLevel(V: Integer);
    procedure SetFMRpt(Dir: Integer);

    // Каналы
    procedure ApplyChannel(Idx: Integer);

    // Жизненный цикл движка / устройства
    procedure StartStop;
    procedure SetRun(On_: Boolean);
    procedure ConnectDevice(const Dev: THPSDRDevice);
    procedure Discover;

    // ---- События наружу ----
    property OnInvoke: TUIInvokeEvent read FOnInvoke write FOnInvoke;
    property OnStateChanged: TRadioStateEvent read FOnStateChanged write FOnStateChanged;
  end;

implementation

constructor TRadioController.Create;
begin
  inherited Create;
  // Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны).
  FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0;
  FMode := 1; FFilter := 5; FFilterBW := 2700;
  FAGCMode := 1; FAGCTop := 90;
  FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
  FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
  FCurrentBand := 5; FCurrentXvtr := -1;
  FPAMaxPower := 100.0;
  FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
  FFMStepOn := True;
  FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
  FillChar(FDevMAC, SizeOf(FDevMAC), 0);
end;

destructor TRadioController.Destroy;
begin
  // Фаза 1: движки освобождает MainForm. Здесь ничего не освобождаем.
  inherited Destroy;
end;

procedure TRadioController.Changed(Field: TRadioField);
begin
  if Assigned(FOnStateChanged) then FOnStateChanged(Self, Field);
end;

procedure TRadioController.Invoke(M: TThreadMethod);
begin
  if Assigned(FOnInvoke) then FOnInvoke(M)
  else if Assigned(M) then M();   // нет хозяина-маршалера — прямой вызов
end;

function TRadioController.GetSnapshot: TRadioSnapshot;
begin
  FillChar(Result, SizeOf(Result), 0);
  Result.VfoA := FVfoA; Result.VfoB := FVfoB;
  Result.CenterFreq := FCenterFreq; Result.SpanHz := FSpanHz;
  Result.SampleRate := FSampleRate; Result.ActiveVfo := FActiveVfo;
  Result.Mode := FMode; Result.Filter := FFilter; Result.FilterBW := FFilterBW;
  Result.AGCMode := FAGCMode; Result.AGCTop := FAGCTop;
  Result.Volume := FVolume; Result.Drive := FDrivePercent; Result.Atten := FAtten;
  Result.Band := FCurrentBand; Result.Xvtr := FCurrentXvtr;
  Result.Muted := FMuted; Result.CTun := FCTun; Result.Duplex := FDisplayDuplex;
  Result.Running := FRunning; Result.Connected := FDevConnected;
  Result.Transmitting := FTransmitting; Result.Tuning := FTuning;
  Result.NRMode := FNRMode; Result.NBMode := FNBMode;
  Result.SNB := FSNB; Result.ANF := FANF;
  Result.WfAGC := FWfAGCEnabled; Result.WfNF := FWfNFEnabled;
  Result.FMStepOn := FFMStepOn; Result.FMStepIdx := FFMStepIdx;
  Result.FMCTCSSOn := FFMCTCSSOn; Result.FMCTCSSToneIdx := FFMCTCSSToneIdx;
  Result.FMSQOn := FFMSQOn; Result.FMSQLevel := FFMSQLevel;
  Result.FMRptDir := FFMRptDir;
  Result.SMeter := FLastSMeter; Result.FwdW := FLastFwdW; Result.SWR := FLastSWR;
  Result.SupplyV := FLastSupplyV; Result.SupplyA := FLastSupplyA;
  Result.PLLLock := FLastPLLLock;
end;

// =====================================================================
//  Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm.
// =====================================================================

procedure TRadioController.SetVfoA(Hz: Double);
begin FVfoA := Hz; Changed(rfVfoA); { TODO wiring: ApplyVfoA } end;

procedure TRadioController.SetVfoB(Hz: Double);
begin FVfoB := Hz; Changed(rfVfoB); { TODO wiring } end;

procedure TRadioController.SetActiveVfo(Idx: Integer);
begin FActiveVfo := Idx; Changed(rfActiveVfo); { TODO wiring: ActivateVfo } end;

procedure TRadioController.TuneActiveBy(DeltaHz: Double);
begin
  if FActiveVfo = 0 then SetVfoA(FVfoA + DeltaHz)
  else SetVfoB(FVfoB + DeltaHz);
end;

procedure TRadioController.SetCenter(Hz: Double);
begin FCenterFreq := Hz; Changed(rfCenterFreq); { TODO wiring } end;

procedure TRadioController.VfoSwap;
var T: Double;
begin T := FVfoA; FVfoA := FVfoB; FVfoB := T; Changed(rfVfoA); Changed(rfVfoB); { TODO wiring } end;

procedure TRadioController.VfoACopyB;
begin FVfoA := FVfoB; Changed(rfVfoA); { TODO wiring } end;

procedure TRadioController.VfoBCopyA;
begin FVfoB := FVfoA; Changed(rfVfoB); { TODO wiring } end;

procedure TRadioController.SetMode(M: Integer);
begin FMode := M; Changed(rfMode); { TODO wiring: ApplyModeFilter } end;

procedure TRadioController.SetFilterIdx(Idx: Integer);
begin FFilter := Idx; Changed(rfFilter); { TODO wiring } end;

procedure TRadioController.SetFilterBW(BWHz: Integer);
begin FFilterBW := BWHz; Changed(rfFilterBW); { TODO wiring } end;

procedure TRadioController.SetAGCMode(M: Integer);
const
  // UI-индекс 0..4 (FAST/MED/SLOW/LONG/OFF) -> WDSP enum. Единый маппинг для
  // всех фронтендов (раньше web ошибочно кастовал индекс прямо в enum).
  AGCModes: array[0..4] of TWDSPAGCMode = (agcFast, agcMedium, agcSlow, agcLong, agcOff);
begin
  if (M < 0) or (M > 4) then Exit;
  FAGCMode := M;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGC(AGCModes[M], 50.0);
  FBandCache[FCurrentBand].AGCMode := FAGCMode;
  Changed(rfAGCMode);
end;

procedure TRadioController.SetAGCTop(DB: Integer);
begin FAGCTop := DB; Changed(rfAGCTop); { TODO wiring } end;

procedure TRadioController.AGCTopBy(Delta: Integer);
begin SetAGCTop(FAGCTop + Delta); end;

procedure TRadioController.SetVolume(V: Integer);
begin FVolume := V; Changed(rfVolume); { TODO wiring: FDSPEngine.SetVolume } end;

procedure TRadioController.VolumeBy(Delta: Integer);
begin SetVolume(FVolume + Delta); end;

procedure TRadioController.SetMute(On_: Boolean);
begin
  FMuted := On_;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMute(FMuted);
  Changed(rfMute);
end;

procedure TRadioController.ToggleMute;
begin SetMute(not FMuted); end;

procedure TRadioController.SetDrive(V: Integer);
begin FDrivePercent := V; Changed(rfDrive); { TODO wiring: CalcDriveByte + WDSP } end;

procedure TRadioController.DriveBy(Delta: Integer);
begin SetDrive(FDrivePercent + Delta); end;

procedure TRadioController.SetAtten(Idx: Integer);
begin FAtten := Idx; Changed(rfAtten); { TODO wiring } end;

procedure TRadioController.SetBand(Idx: Integer);
begin FCurrentBand := Idx; Changed(rfBand); { TODO wiring: RestoreBand } end;

procedure TRadioController.BandUp;
begin if FCurrentBand < CFG_BAND_COUNT - 1 then SetBand(FCurrentBand + 1); end;

procedure TRadioController.BandDown;
begin if FCurrentBand > 0 then SetBand(FCurrentBand - 1); end;

procedure TRadioController.SetXvtrBand(Idx: Integer);
begin FCurrentXvtr := Idx; Changed(rfXvtr); { TODO wiring } end;

procedure TRadioController.SetSpan(Hz: Integer);
begin FSpanHz := Hz; Changed(rfSpan); { TODO wiring } end;

procedure TRadioController.SetSampleRate(Hz: Integer);
begin FSampleRate := Hz; Changed(rfSampleRate); { TODO wiring: RecreateDSPEngine } end;

procedure TRadioController.SetCTun(On_: Boolean);
begin FCTun := On_; Changed(rfCTun); { TODO wiring } end;

procedure TRadioController.SetDuplex(On_: Boolean);
begin FDisplayDuplex := On_; Changed(rfDuplex); { TODO wiring } end;

procedure TRadioController.SetWfAGC(On_: Boolean);
begin FWfAGCEnabled := On_; Changed(rfWfAGC); end;

procedure TRadioController.SetWfNF(On_: Boolean);
begin FWfNFEnabled := On_; Changed(rfWfNF); end;

procedure TRadioController.SetNR(Mode: Integer);
begin
  if Mode < 0 then Mode := 0 else if Mode > 4 then Mode := 4;
  FNRMode := Mode;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetNRMode(FNRMode);
  Changed(rfNR);
end;

procedure TRadioController.SetNB(Mode: Integer);
begin
  if Mode < 0 then Mode := 0 else if Mode > 2 then Mode := 2;
  FNBMode := Mode;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetNBMode(FNBMode);
  Changed(rfNB);
end;

procedure TRadioController.SetSNB(On_: Boolean);
begin
  FSNB := On_;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetSNB(FSNB);
  Changed(rfSNB);
end;

procedure TRadioController.SetANF(On_: Boolean);
begin
  FANF := On_;
  if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetANF(FANF);
  Changed(rfANF);
end;

procedure TRadioController.SetMOX(On_: Boolean);
begin FTransmitting := On_; Changed(rfTransmitting); { TODO wiring: ApplyMOX } end;

procedure TRadioController.SetTune(On_: Boolean);
begin FTuning := On_; Changed(rfTuning); { TODO wiring: ApplyTUN } end;

procedure TRadioController.SetFMStep(Idx: Integer);
begin FFMStepIdx := Idx; Changed(rfFMStepIdx); { TODO wiring } end;

procedure TRadioController.SetFMCTCSS(On_: Boolean);
begin FFMCTCSSOn := On_; Changed(rfFMCTCSS); { TODO wiring } end;

procedure TRadioController.SetFMCTCSSTone(Idx: Integer);
begin FFMCTCSSToneIdx := Idx; Changed(rfFMCTCSSTone); { TODO wiring } end;

procedure TRadioController.SetFMSquelch(On_: Boolean);
begin FFMSQOn := On_; Changed(rfFMSQ); { TODO wiring } end;

procedure TRadioController.SetFMSquelchLevel(V: Integer);
begin FFMSQLevel := V; Changed(rfFMSQLevel); { TODO wiring } end;

procedure TRadioController.SetFMRpt(Dir: Integer);
begin FFMRptDir := Dir; Changed(rfFMRpt); { TODO wiring } end;

procedure TRadioController.ApplyChannel(Idx: Integer);
begin Changed(rfChannel); { TODO wiring } end;

procedure TRadioController.StartStop;
begin SetRun(not FRunning); end;

procedure TRadioController.SetRun(On_: Boolean);
begin FRunning := On_; Changed(rfRunning); { TODO wiring: логика BtnStartStopClick } end;

procedure TRadioController.ConnectDevice(const Dev: THPSDRDevice);
begin { TODO wiring: DoConnectDevice } Changed(rfDevice); end;

procedure TRadioController.Discover;
begin { TODO wiring: FNetwork.Discover } end;

end.
