unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, Winapi.ShellAPI,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Menus,
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdGlobal, Vcl.ExtCtrls,
  Vcl.Buttons, System.SyncObjs, System.Generics.Collections;

const
  WM_TRAYICON = WM_USER + 1;

type
  TForm1 = class(TForm)
    ButtonConnect: TButton;
    ButtonDisconnect: TButton;
    LabelStatus: TLabel;
    TCPClient: TIdTCPClient;
    ButtonClosePump: TButton;
    Button1: TButton;
    Edit1: TEdit;
    gp: TEdit;
    gr: TEdit;
    dp: TEdit;
    dr: TEdit;
    kr: TEdit;
    rc: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Button2: TButton;
    Label7: TLabel;
    surtidor1: TLabel;
    Label8: TLabel;
    surtidor2: TLabel;
    Label9: TLabel;
    surtidor3: TLabel;
    Label10: TLabel;
    surtidor4: TLabel;
    Label11: TLabel;
    surtidor5: TLabel;
    Label12: TLabel;
    surtidor6: TLabel;
    Label13: TLabel;
    surtidor7: TLabel;
    Label14: TLabel;
    surtidor8: TLabel;
    Label15: TLabel;
    surtidor9: TLabel;
    Label16: TLabel;
    surtidor10: TLabel;
    Label17: TLabel;
    surtidor11: TLabel;
    Label18: TLabel;
    surtidor12: TLabel;
    Label19: TLabel;
    surtidor13: TLabel;
    Label20: TLabel;
    surtidor14: TLabel;
    Label21: TLabel;
    surtidor15: TLabel;
    Label22: TLabel;
    surtidor16: TLabel;
    Label23: TLabel;
    surtidor17: TLabel;
    Label24: TLabel;
    surtidor18: TLabel;
    Label25: TLabel;
    surtidor19: TLabel;
    Label26: TLabel;
    surtidor20: TLabel;
    Timer1: TTimer;
    SpeedButton1: TSpeedButton;
    Button3: TButton;
    Timer2: TTimer;
    procedure ButtonConnectClick(Sender: TObject);
    procedure ButtonDisconnectClick(Sender: TObject);
    procedure ButtonClosePumpClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    FLastStatus: array[1..20] of string;
    FAuthSentCount: array[1..20] of Integer;
    FSurtidorLabels: array[1..20] of TLabel;
    FMonitoringActive: Boolean;
    FCurrentPump: Integer;
    FFileNeedsSave: Boolean;
    FPumpClients: array[1..4] of TIdTCPClient;
    FClientIndex: Integer;
    FSalesClient: TIdTCPClient;
    FLastSaleNumber: string;
    FSalesCheckCount: Integer;
    
    // System Tray
    FTrayIconData: TNotifyIconData;
    FTrayMenu: TPopupMenu;
    FInTray: Boolean;

    function GetPumpId3: string;
    procedure UpdateStatus(const AMsg: string; AColor: TColor);
    procedure EnsureConnected;
    procedure SendRawCommand(const ACmd: string);
    function ExtractStatusFromString(const Response, Prefix: string): string;
    procedure UpdatePumpStatus(PumpNumber: Integer; const Status: string);
    procedure SendAuthorizationCommand(PumpId: string);
    procedure SavePumpStatusToFile;
    procedure StartMonitoring;
    procedure StopMonitoring;
    procedure InitializeClientPool;
    procedure FreeClientPool;
    function GetNextClient: TIdTCPClient;
    procedure CheckPumpStatusFast(PumpNumber: Integer);
    procedure CheckLastSale;
    procedure ParseAndSaveSale(const Response: string);
    function ExtractValue(const Response, Key: string): string;
    procedure SaveSalesToFile(const SaleData: string);
    procedure LogDebug(const Msg: string);
    
    // System Tray methods
    procedure CreateTrayIcon;
    procedure RemoveTrayIcon;
    procedure TrayIconMessage(var Msg: TMessage); message WM_TRAYICON;
    procedure OnTrayMenuRestore(Sender: TObject);
    procedure OnTrayMenuExit(Sender: TObject);
  public
    procedure MinimizeToTray;
    procedure RestoreFromTray;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

const
  DEFAULT_PORT = 3011;
  STATUS_FILE_PATH = 'C:\opscode\fss3xp.txt';
  SALES_FILE_PATH = 'C:\opscode\sale.txt';
  DEBUG_LOG_PATH = 'C:\opscode\debug.log';

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  // Evitar que aparezca en la barra de tareas cuando está minimizado
end;

procedure TForm1.CreateTrayIcon;
var
  MenuItem: TMenuItem;
begin
  // Crear menú contextual
  FTrayMenu := TPopupMenu.Create(Self);
  
  MenuItem := TMenuItem.Create(FTrayMenu);
  MenuItem.Caption := 'Restaurar';
  MenuItem.OnClick := OnTrayMenuRestore;
  FTrayMenu.Items.Add(MenuItem);
  
  MenuItem := TMenuItem.Create(FTrayMenu);
  MenuItem.Caption := '-';
  FTrayMenu.Items.Add(MenuItem);
  
  MenuItem := TMenuItem.Create(FTrayMenu);
  MenuItem.Caption := 'Salir';
  MenuItem.OnClick := OnTrayMenuExit;
  FTrayMenu.Items.Add(MenuItem);
  
  // Configurar icono de bandeja
  FillChar(FTrayIconData, SizeOf(FTrayIconData), 0);
  FTrayIconData.cbSize := SizeOf(TNotifyIconData);
  FTrayIconData.Wnd := Handle;
  FTrayIconData.uID := 1;
  FTrayIconData.uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
  FTrayIconData.uCallbackMessage := WM_TRAYICON;
  FTrayIconData.hIcon := Application.Icon.Handle;
  StrPCopy(FTrayIconData.szTip, 'Wayne Fusion V3 - Conexión TCP');
  
  Shell_NotifyIcon(NIM_ADD, @FTrayIconData);
  FInTray := False;
end;

procedure TForm1.RemoveTrayIcon;
begin
  if FInTray then
    Shell_NotifyIcon(NIM_DELETE, @FTrayIconData);
end;

procedure TForm1.MinimizeToTray;
begin
  Hide;
  WindowState := wsMinimized;
  FInTray := True;
  LogDebug('Aplicación minimizada a bandeja del sistema');
end;

procedure TForm1.RestoreFromTray;
begin
  Show;
  WindowState := wsNormal;
  Application.BringToFront;
  SetForegroundWindow(Handle);
  FInTray := False;
  LogDebug('Aplicación restaurada desde bandeja del sistema');
end;

procedure TForm1.TrayIconMessage(var Msg: TMessage);
var
  Point: TPoint;
begin
  if Msg.LParam = WM_RBUTTONUP then
  begin
    // Click derecho: mostrar menú
    GetCursorPos(Point);
    SetForegroundWindow(Handle);
    FTrayMenu.Popup(Point.X, Point.Y);
  end
  else if Msg.LParam = WM_LBUTTONDBLCLK then
  begin
    // Doble click izquierdo: restaurar
    RestoreFromTray;
  end;
end;

procedure TForm1.OnTrayMenuRestore(Sender: TObject);
begin
  RestoreFromTray;
end;

procedure TForm1.OnTrayMenuExit(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TForm1.LogDebug(const Msg: string);
var
  F: TextFile;
  TimeStamp: string;
begin
  try
    TimeStamp := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
    AssignFile(F, DEBUG_LOG_PATH);
    if FileExists(DEBUG_LOG_PATH) then
      Append(F)
    else
      Rewrite(F);
    WriteLn(F, '[' + TimeStamp + '] ' + Msg);
    CloseFile(F);
  except
    // Silenciar errores de log
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  // Crear icono en bandeja del sistema
  CreateTrayIcon;
  
  TCPClient.Port := DEFAULT_PORT;
  TCPClient.ConnectTimeout := 2000;
  TCPClient.ReadTimeout := 2000;

  if Assigned(TCPClient.IOHandler) then
    TCPClient.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;

  if Trim(Edit1.Text) = '' then
    Edit1.Text := '001';

  FMonitoringActive := False;
  FCurrentPump := 1;
  FFileNeedsSave := False;
  FClientIndex := 0;
  FLastSaleNumber := '';
  FSalesCheckCount := 0;

  // Cliente dedicado para ventas
  FSalesClient := TIdTCPClient.Create(nil);
  FSalesClient.Host := '172.20.6.1';
  FSalesClient.Port := DEFAULT_PORT;
  FSalesClient.ConnectTimeout := 2000;
  FSalesClient.ReadTimeout := 2000;

  // Asignar labels
  FSurtidorLabels[1] := surtidor1;
  FSurtidorLabels[2] := surtidor2;
  FSurtidorLabels[3] := surtidor3;
  FSurtidorLabels[4] := surtidor4;
  FSurtidorLabels[5] := surtidor5;
  FSurtidorLabels[6] := surtidor6;
  FSurtidorLabels[7] := surtidor7;
  FSurtidorLabels[8] := surtidor8;
  FSurtidorLabels[9] := surtidor9;
  FSurtidorLabels[10] := surtidor10;
  FSurtidorLabels[11] := surtidor11;
  FSurtidorLabels[12] := surtidor12;
  FSurtidorLabels[13] := surtidor13;
  FSurtidorLabels[14] := surtidor14;
  FSurtidorLabels[15] := surtidor15;
  FSurtidorLabels[16] := surtidor16;
  FSurtidorLabels[17] := surtidor17;
  FSurtidorLabels[18] := surtidor18;
  FSurtidorLabels[19] := surtidor19;
  FSurtidorLabels[20] := surtidor20;

  for i := 1 to 20 do
  begin
    FLastStatus[i] := '';
    FAuthSentCount[i] := 0;
  end;

  // Crear directorios
  if not DirectoryExists('C:\opscode') then
    ForceDirectories('C:\opscode');

  // Configurar timers
  Timer1.Enabled := False;
  Timer1.Interval := 50;
  
  Timer2.Enabled := False;
  Timer2.Interval := 1000;

  UpdateStatus('Desconectado', clRed);
  LogDebug('=== APLICACIÓN INICIADA (Minimizada a Tray) ===');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  LogDebug('=== APLICACIÓN CERRANDO ===');
  RemoveTrayIcon;
  StopMonitoring;
  FreeClientPool;
  
  if Assigned(FSalesClient) then
  begin
    try
      if FSalesClient.Connected then
        FSalesClient.Disconnect;
    except
      // Ignorar errores al cerrar
    end;
    FSalesClient.Free;
  end;
  
  if Assigned(FTrayMenu) then
    FTrayMenu.Free;
end;

function TForm1.ExtractValue(const Response, Key: string): string;
var
  PosStart, PosEnd: Integer;
  SubStr: string;
begin
  Result := '';
  
  PosStart := Pos(Key + '=', Response);
  if PosStart = 0 then
    Exit;
    
  PosStart := PosStart + Length(Key) + 1;
  SubStr := Copy(Response, PosStart, Length(Response) - PosStart + 1);
  
  PosEnd := Pos('|', SubStr);
  if PosEnd = 0 then
    Result := SubStr
  else
    Result := Copy(SubStr, 1, PosEnd - 1);
end;

procedure TForm1.CheckLastSale;
var
  Command, Response: string;
  RetryCount: Integer;
begin
  Inc(FSalesCheckCount);
  
  // Reconectar si es necesario
  if not FSalesClient.Connected then
  begin
    LogDebug('CheckLastSale: Cliente desconectado, intentando reconectar...');
    try
      FSalesClient.Connect;
      if Assigned(FSalesClient.IOHandler) then
        FSalesClient.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;
      LogDebug('CheckLastSale: Reconexión exitosa');
    except
      on E: Exception do
      begin
        LogDebug('CheckLastSale: Error reconectando - ' + E.Message);
        Exit;
      end;
    end;
  end;

  // Comando para obtener última venta
  Command := '00041|5|2||POST|REQ_GET_PUMP_SALES|||PM=1|QT=1||^';
  LogDebug('CheckLastSale #' + IntToStr(FSalesCheckCount) + ': Enviando comando');

  RetryCount := 0;
  repeat
    try
      FSalesClient.IOHandler.InputBuffer.Clear;
      FSalesClient.IOHandler.Write(Command);
      FSalesClient.IOHandler.WriteBufferFlush;
      
      LogDebug('CheckLastSale: Comando enviado, esperando respuesta...');
      Sleep(150);
      
      FSalesClient.IOHandler.CheckForDataOnSource(800);
      
      if not FSalesClient.IOHandler.InputBufferIsEmpty then
      begin
        Response := FSalesClient.IOHandler.InputBufferAsString;
        LogDebug('CheckLastSale: Respuesta recibida (' + IntToStr(Length(Response)) + ' bytes)');
        
        // Verificar respuesta válida
        if (Pos('RES_GET_PUMP_SALES', Response) > 0) then
        begin
          if Pos('RC=OK', Response) > 0 then
          begin
            LogDebug('CheckLastSale: Respuesta válida (RC=OK)');
            ParseAndSaveSale(Response);
            Exit;
          end
          else
          begin
            LogDebug('CheckLastSale: Respuesta sin RC=OK - ' + Copy(Response, 1, 100));
          end;
        end
        else
        begin
          LogDebug('CheckLastSale: Respuesta no contiene RES_GET_PUMP_SALES');
        end;
      end
      else
      begin
        LogDebug('CheckLastSale: Buffer vacío, sin respuesta del servidor');
      end;
      
      Inc(RetryCount);
      if RetryCount < 2 then
      begin
        LogDebug('CheckLastSale: Reintentando (' + IntToStr(RetryCount + 1) + '/2)...');
        Sleep(200);
      end;
      
    except
      on E: Exception do
      begin
        LogDebug('CheckLastSale: Excepción - ' + E.Message);
        try
          if FSalesClient.Connected then
            FSalesClient.Disconnect;
        except
          // Ignorar errores al desconectar
        end;
        Exit;
      end;
    end;
  until RetryCount >= 2;
  
  LogDebug('CheckLastSale: Finalizó sin obtener venta válida');
end;

procedure TForm1.ParseAndSaveSale(const Response: string);
var
  SaleData: string;
  NumVenta, FechaFin, HoraFin, Surtidor, Manguera, Combustible, Volumen, VolCompensado: string;
  PPU, Monto, Totalizador, TCInicial, VolumenFinal, TCFinal, TiempoMedia, TipoVenta, TipoPago: string;
  GradeValue, PayTypeValue: string;
begin
  // Extraer número de venta
  NumVenta := ExtractValue(Response, 'TI1');
  
  if NumVenta = '' then
  begin
    LogDebug('ParseAndSaveSale: TI1 vacío, abortando');
    Exit;
  end;
  
  LogDebug('ParseAndSaveSale: Venta #' + NumVenta);
  
  // Si es la misma venta, salir
  if NumVenta = FLastSaleNumber then
  begin
    LogDebug('ParseAndSaveSale: Venta duplicada #' + NumVenta + ', ignorando');
    Exit;
  end;
  
  // Actualizar última venta procesada
  FLastSaleNumber := NumVenta;
  LogDebug('ParseAndSaveSale: Nueva venta detectada #' + NumVenta);
  
  // Extraer datos
  FechaFin := ExtractValue(Response, 'DA1');
  HoraFin := ExtractValue(Response, 'STI1');
  Surtidor := ExtractValue(Response, 'PM');
  Manguera := ExtractValue(Response, 'HO1');
  GradeValue := ExtractValue(Response, 'GR1');
  
  // Mapeo de combustibles
  case StrToIntDef(GradeValue, 0) of
    1: Combustible := 'Gasolina Premium';
    2: Combustible := 'Gasolina Regular';
    3: Combustible := 'Diesel Premium';
    4: Combustible := 'Diesel Regular';
    5: Combustible := 'Kerosene';
    6: Combustible := 'Racing Combustible';
    7: Combustible := 'Diesel Excellium';
  else
    Combustible := 'Desconocido';
  end;
  
  Volumen := ExtractValue(Response, 'VO1');
  VolCompensado := ExtractValue(Response, 'FVO1');
  PPU := ExtractValue(Response, 'PU1');
  Monto := ExtractValue(Response, 'AM1');
  Totalizador := ExtractValue(Response, 'IV1');
  TCInicial := '0,000';
  VolumenFinal := ExtractValue(Response, 'FV1');
  TCFinal := '0,000';
  TiempoMedia := ExtractValue(Response, 'AVGTM1');
  TipoVenta := 'Ventas';
  
  // Tipo de pago
  PayTypeValue := ExtractValue(Response, 'PAY_TY1');
  case StrToIntDef(PayTypeValue, 0) of
    1: TipoPago := 'Efectivo';
    2: TipoPago := 'Tarjeta';
    3: TipoPago := 'Crédito';
  else
    TipoPago := 'No Asignado';
  end;
  
  // Formatear fecha YYYYMMDD → DD/MM/YYYY
  if Length(FechaFin) = 8 then
    FechaFin := Copy(FechaFin, 7, 2) + '/' + Copy(FechaFin, 5, 2) + '/' + Copy(FechaFin, 1, 4);
  
  // Formatear hora HHMMSS → HH:MM:SS
  if Length(HoraFin) = 6 then
    HoraFin := Copy(HoraFin, 1, 2) + ':' + Copy(HoraFin, 3, 2) + ':' + Copy(HoraFin, 5, 2);

  // Crear línea de datos (separada por TABs)
  SaleData := Format('%s'#9'%s'#9'%s'#9'%s'#9'%s'#9'%s'#9'%s Gal'#9'%s Gal'#9'$ %s'#9'$ %s'#9'%s Gal'#9 +
                     '%s'#9'%s Gal'#9'%s'#9'%s'#9'%s'#9'%s',
    [
      NumVenta, FechaFin, HoraFin, Surtidor, Manguera, Combustible,
      Volumen, VolCompensado, PPU, Monto, Totalizador, TCInicial,
      VolumenFinal, TCFinal, TiempoMedia, TipoVenta, TipoPago
    ]);
  
  LogDebug('ParseAndSaveSale: Datos formateados, guardando...');
  SaveSalesToFile(SaleData);
end;

procedure TForm1.SaveSalesToFile(const SaleData: string);
var
  FileStream: TStringList;
  Header: string;
begin
  FileStream := TStringList.Create;
  try
    // Si no existe, crear con encabezado
    if not FileExists(SALES_FILE_PATH) then
    begin
      LogDebug('SaveSalesToFile: Creando archivo nuevo con encabezado');
      Header := '# de Venta'#9'Fecha de Fin'#9'Hora de Fin'#9'Surtidor'#9'Manguera'#9'Combustible'#9 +
                'Volumen'#9'Vol Compensado'#9'PPU'#9'Monto'#9'Totaliz. Inicial'#9'TC Inicial'#9 +
                'Volumen Final'#9'TC Final'#9'Temp media'#9'Tipo de Venta'#9'Tipo de Pago';
      FileStream.Add(Header);
    end
    else
    begin
      LogDebug('SaveSalesToFile: Cargando archivo existente');
      FileStream.LoadFromFile(SALES_FILE_PATH);
    end;
    
    // Agregar nueva venta
    FileStream.Add(SaleData);
    FileStream.SaveToFile(SALES_FILE_PATH);
    
    LogDebug('SaveSalesToFile: Venta #' + FLastSaleNumber + ' guardada exitosamente');
    UpdateStatus('Nueva venta guardada: #' + FLastSaleNumber, clGreen);
  except
    on E: Exception do
    begin
      LogDebug('SaveSalesToFile: ERROR - ' + E.Message);
      UpdateStatus('Error al guardar venta: ' + E.Message, clRed);
    end;
  end;
  FileStream.Free;
end;

procedure TForm1.Timer2Timer(Sender: TObject);
begin
  if not FMonitoringActive then
    Exit;
    
  CheckLastSale;
end;

procedure TForm1.InitializeClientPool;
var
  i: Integer;
  Client: TIdTCPClient;
begin
  LogDebug('InitializeClientPool: Creando pool de 4 clientes');
  for i := 1 to 4 do
  begin
    Client := TIdTCPClient.Create(nil);
    Client.Host := TCPClient.Host;
    Client.Port := TCPClient.Port;
    Client.ConnectTimeout := 500;
    Client.ReadTimeout := 500;
    FPumpClients[i] := Client;
    
    try
      Client.Connect;
      if Assigned(Client.IOHandler) then
        Client.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;
      LogDebug('InitializeClientPool: Cliente #' + IntToStr(i) + ' conectado');
    except
      on E: Exception do
        LogDebug('InitializeClientPool: Cliente #' + IntToStr(i) + ' falló - ' + E.Message);
    end;
  end;
end;

procedure TForm1.FreeClientPool;
var
  i: Integer;
begin
  LogDebug('FreeClientPool: Liberando pool de clientes');
  for i := 1 to 4 do
  begin
    if Assigned(FPumpClients[i]) then
    begin
      try
        if FPumpClients[i].Connected then
          FPumpClients[i].Disconnect;
      except
      end;
      FPumpClients[i].Free;
      FPumpClients[i] := nil;
    end;
  end;
end;

function TForm1.GetNextClient: TIdTCPClient;
begin
  Inc(FClientIndex);
  if FClientIndex > 4 then
    FClientIndex := 1;
  
  Result := FPumpClients[FClientIndex];
  
  if not Result.Connected then
  begin
    try
      Result.Connect;
      if Assigned(Result.IOHandler) then
        Result.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;
    except
    end;
  end;
end;

procedure TForm1.UpdateStatus(const AMsg: string; AColor: TColor);
begin
  LabelStatus.Caption := AMsg;
  LabelStatus.Font.Color := AColor;
end;

function TForm1.GetPumpId3: string;
var
  n: Integer;
  s: string;
begin
  s := Trim(Edit1.Text);
  if s = '' then
    s := '1';

  if TryStrToInt(s, n) then
    Result := Format('%.3d', [n])
  else
  begin
    s := StringReplace(s, ' ', '', [rfReplaceAll]);
    if Length(s) >= 3 then
      Result := Copy(s, Length(s) - 2, 3)
    else
      Result := StringOfChar('0', 3 - Length(s)) + s;
  end;
end;

procedure TForm1.EnsureConnected;
begin
  if TCPClient.Connected then
    Exit;
  TCPClient.Connect;
end;

procedure TForm1.SendRawCommand(const ACmd: string);
begin
  if not TCPClient.Connected then
    raise Exception.Create('No hay conexión activa.');

  TCPClient.IOHandler.InputBuffer.Clear;
  TCPClient.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;
  TCPClient.IOHandler.Write(ACmd);
  TCPClient.IOHandler.WriteBufferFlush;
end;

function TForm1.ExtractStatusFromString(const Response, Prefix: string): string;
var
  PosStart, PosEnd: Integer;
  SubStr: string;
begin
  Result := '';
  PosStart := Pos(Prefix, Response);
  if PosStart = 0 then
    Exit;

  PosStart := PosStart + Length(Prefix);
  SubStr := Copy(Response, PosStart, Length(Response) - PosStart + 1);

  PosEnd := Pos('|', SubStr);
  if PosEnd = 0 then
    Result := SubStr
  else
    Result := Copy(SubStr, 1, PosEnd - 1);
end;

procedure TForm1.SavePumpStatusToFile;
var
  FileStream: TStringList;
  i: Integer;
  StatusText: string;
begin
  if not FFileNeedsSave then
    Exit;
    
  FileStream := TStringList.Create;
  try
    for i := 1 to 20 do
    begin
      StatusText := FSurtidorLabels[i].Caption;
      if StatusText = 'Esperando...' then
        StatusText := '';
      FileStream.Add(IntToStr(i) + ', ' + StatusText);
    end;
    FileStream.SaveToFile(STATUS_FILE_PATH);
    FFileNeedsSave := False;
  except
    on E: Exception do
      UpdateStatus('Error al guardar archivo de estados: ' + E.Message, clRed);
  end;
  FileStream.Free;
end;

procedure TForm1.UpdatePumpStatus(PumpNumber: Integer; const Status: string);
var
  OldStatus: string;
begin
  if (PumpNumber < 1) or (PumpNumber > 20) then
    Exit;

  OldStatus := FSurtidorLabels[PumpNumber].Caption;

  if SameText(Status, 'IDLE') then
    FSurtidorLabels[PumpNumber].Caption := 'IDLE'
  else if SameText(Status, 'CALLING') then
    FSurtidorLabels[PumpNumber].Caption := 'CALLING'
  else if SameText(Status, 'FUELLING') then
    FSurtidorLabels[PumpNumber].Caption := 'FUELLING'
  else if SameText(Status, 'CLOSED') then
    FSurtidorLabels[PumpNumber].Caption := 'CLOSED'
  else if SameText(Status, 'STARTING') then
    FSurtidorLabels[PumpNumber].Caption := 'STARTING'
  else if SameText(Status, 'ERROR') then
    FSurtidorLabels[PumpNumber].Caption := 'ERROR'
  else if Status <> '' then
    FSurtidorLabels[PumpNumber].Caption := 'No reconocido';

  if not SameText(OldStatus, FSurtidorLabels[PumpNumber].Caption) then
    FFileNeedsSave := True;
end;

procedure TForm1.SendAuthorizationCommand(PumpId: string);
var
  Command: string;
  i: Integer;
begin
  Command := '00044|5|2||POST|REQ_PUMP_AUTH_ID_' + PumpId + '|||OS=CALLING|^';
  
  try
    EnsureConnected;
    for i := 1 to 4 do
    begin
      SendRawCommand(Command);
      Sleep(30);
    end;
  except
    on E: Exception do
      UpdateStatus('Error al enviar autorización P' + PumpId + ': ' + E.Message, clRed);
  end;
end;

procedure TForm1.CheckPumpStatusFast(PumpNumber: Integer);
var
  Command, Response, StatusSurtidor, PumpId: string;
  Client: TIdTCPClient;
begin
  Client := GetNextClient;
  if not Client.Connected then
    Exit;

  PumpId := Format('%.3d', [PumpNumber]);
  Command := '00035|5|2||POST|REQ_PUMP_STATUS_ID_' + PumpId + '||||^';

  try
    Client.IOHandler.InputBuffer.Clear;
    Client.IOHandler.Write(Command);
    Client.IOHandler.WriteBufferFlush;

    Client.IOHandler.CheckForDataOnSource(300);
    if not Client.IOHandler.InputBufferIsEmpty then
    begin
      Response := Client.IOHandler.InputBufferAsString;
      StatusSurtidor := ExtractStatusFromString(Response, 'ST=');
      
      if StatusSurtidor <> '' then
      begin
        UpdatePumpStatus(PumpNumber, StatusSurtidor);
        
        if SameText(StatusSurtidor, 'CALLING') then
        begin
          if not SameText(FLastStatus[PumpNumber], 'CALLING') then
            FAuthSentCount[PumpNumber] := 0;
          
          if FAuthSentCount[PumpNumber] < 4 then
          begin
            SendAuthorizationCommand(PumpId);
            FAuthSentCount[PumpNumber] := 4;
            UpdateStatus('Autorización enviada a surtidor ' + IntToStr(PumpNumber), clGreen);
          end;
        end
        else
        begin
          if not SameText(StatusSurtidor, FLastStatus[PumpNumber]) then
            FAuthSentCount[PumpNumber] := 0;
        end;
        
        FLastStatus[PumpNumber] := StatusSurtidor;
      end;
    end;
  except
  end;
end;

procedure TForm1.StartMonitoring;
begin
  FMonitoringActive := True;
  FCurrentPump := 1;
  FLastSaleNumber := '';
  FSalesCheckCount := 0;
  InitializeClientPool;
  Timer1.Enabled := True;
  Timer2.Enabled := True;
  LogDebug('=== MONITOREO INICIADO ===');
end;

procedure TForm1.StopMonitoring;
begin
  FMonitoringActive := False;
  Timer1.Enabled := False;
  Timer2.Enabled := False;
  FreeClientPool;
  LogDebug('=== MONITOREO DETENIDO ===');
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  i: Integer;
begin
  if not FMonitoringActive then
    Exit;

  for i := 0 to 3 do
  begin
    if FCurrentPump <= 20 then
    begin
      CheckPumpStatusFast(FCurrentPump);
      Inc(FCurrentPump);
    end
    else
    begin
      FCurrentPump := 1;
      if FFileNeedsSave then
        SavePumpStatusToFile;
      Break;
    end;
  end;

  UpdateStatus('Monitoreando surtidores (Ciclo: ' + IntToStr((FCurrentPump-1) div 4 + 1) + '/5)', clBlue);
end;

procedure TForm1.ButtonConnectClick(Sender: TObject);
begin
  try
    TCPClient.Port := DEFAULT_PORT;
    EnsureConnected;

    if TCPClient.Connected then
    begin
      UpdateStatus('Conectado', clGreen);
      StartMonitoring;
    end;
  except
    on E: Exception do
      UpdateStatus('No se pudo conectar: ' + E.Message, clRed);
  end;
end;

procedure TForm1.ButtonDisconnectClick(Sender: TObject);
var
  i: Integer;
begin
  try
    StopMonitoring;
    
    if TCPClient.Connected then
    begin
      TCPClient.Disconnect;
      UpdateStatus('Desconectado', clRed);
      
      for i := 1 to 20 do
      begin
        FLastStatus[i] := '';
        FAuthSentCount[i] := 0;
        FSurtidorLabels[i].Caption := 'Esperando...';
      end;
      
      FFileNeedsSave := True;
      SavePumpStatusToFile;
    end
    else
      UpdateStatus('No hay conexión activa', clBlack);
  except
    on E: Exception do
      UpdateStatus('Error al desconectar: ' + E.Message, clRed);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Command: string;
begin
  Command := '00033|5|2||POST|REQ_PUMP_OPEN_ID_' + GetPumpId3 + '||||^';

  try
    EnsureConnected;
    SendRawCommand(Command);
    UpdateStatus('Instrucción enviada: Abrir surtidor', clBlue);
  except
    on E: Exception do
      UpdateStatus('Error al enviar (Abrir): ' + E.Message, clRed);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  Command, Command2: string;
begin
  Command := '00218|5|2||POST|REQ_PRICES_SET_NEW_PRICE_CHANGE|||QTY=6|G01NR=1|G01LV=1|G01PR='+gp.Text+'|G02NR=2|G02LV=1|G02PR='+gr.Text;
  Command2 := '|G03NR=4|G03LV=1|G03PR='+dr.Text+'|G04NR=3|G04LV=1|G04PR='+dp.Text+'|G05NR=5|G05LV=1|G05PR='+kr.Text+'|G06NR=6|G06LV=1|G06PR='+rc.Text+'||^';

  try
    EnsureConnected;
    SendRawCommand(Command + Command2);
    UpdateStatus('Instrucción enviada: Cambio de precio', clBlue);
  except
    on E: Exception do
      UpdateStatus('Error al enviar instrucción: ' + E.Message, clRed);
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  StopMonitoring;
  ShowMessage('Tarea finalizada');
end;

procedure TForm1.ButtonClosePumpClick(Sender: TObject);
var
  Command: string;
begin
  Command := '00034|5|2||POST|REQ_PUMP_CLOSE_ID_' + GetPumpId3 + '||||^';

  try
    EnsureConnected;
    SendRawCommand(Command);
    UpdateStatus('Instrucción enviada: Cerrar surtidor', clBlue);
  except
    on E: Exception do
      UpdateStatus('Error al enviar (Cerrar): ' + E.Message, clRed);
  end;
end;

end.