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;
    auto_conectar: 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);
    procedure auto_conectarTimer(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;
    FSalesCheckCount: Integer;
    FCurrentSalesPump: Integer;
    FProductNames: TStringList;
    FProductID2: TStringList;
    
    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 CheckLastSaleForPump(PumpNumber: Integer);
    procedure ParseAndSaveLastSale(const Response: string; PumpNumber: Integer);
    function ExtractValue(const Response, Key: string): string;
    procedure LogDebug(const Msg: string);
    function GetFuelName(const GradeCode: string): string;
    function GetProductID2(const GradeCode: string): string;
    function FormatNumber(const Value: string; Decimals: Integer): string;
    function BuildSalesCommand(PumpNumber: Integer): string;
    procedure LoadProductNames;
    
    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';
  PRODUCTS_FILE_PATH = 'C:\opscode\productos.txt';
  MAX_SALES = 20;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
end;

procedure TForm1.CreateTrayIcon;
var
  MenuItem: TMenuItem;
begin
  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);
  
  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
    GetCursorPos(Point);
    SetForegroundWindow(Handle);
    FTrayMenu.Popup(Point.X, Point.Y);
  end
  else if Msg.LParam = WM_LBUTTONDBLCLK then
  begin
    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
  end;
end;

procedure TForm1.LoadProductNames;
var
  FileLines: TStringList;
  i: Integer;
  Line, ProductID1, ProductName, ProductID2: string;
  PosComma1, PosComma2: Integer;
begin
  FProductNames.Clear;
  FProductID2.Clear;
  
  LogDebug('========================================');
  LogDebug('LoadProductNames: INICIANDO CARGA (FORMATO CSV CON COMAS)');
  LogDebug('========================================');
  
  if not FileExists(PRODUCTS_FILE_PATH) then
  begin
    LogDebug('LoadProductNames: *** ADVERTENCIA *** Archivo productos.txt NO ENCONTRADO');
    FProductNames.Values['3'] := 'Gasolina Excellium';
    FProductNames.Values['6'] := 'Gasolina Regular';
    FProductNames.Values['7'] := 'Diesel Regular';
    FProductNames.Values['8'] := 'Diesel Excellium';
    FProductNames.Values['9'] := 'Gasolina Racing X';
    
    FProductID2.Values['3'] := '1';
    FProductID2.Values['6'] := '4';
    FProductID2.Values['7'] := '2';
    FProductID2.Values['8'] := '3';
    FProductID2.Values['9'] := '5';
    Exit;
  end;
  
  FileLines := TStringList.Create;
  try
    FileLines.LoadFromFile(PRODUCTS_FILE_PATH);
    LogDebug('LoadProductNames: Archivo encontrado - Total líneas: ' + IntToStr(FileLines.Count));
    LogDebug('----------------------------------------');
    
    for i := 0 to FileLines.Count - 1 do
    begin
      Line := Trim(FileLines[i]);
      
      LogDebug('Línea ' + IntToStr(i + 1) + ': "' + Line + '"');
      
      if Line = '' then
      begin
        LogDebug('  -> Línea vacía, saltando');
        Continue;
      end;
      
      // Formato esperado: 6,Gasolina Regular,4
      PosComma1 := Pos(',', Line);
      if PosComma1 > 0 then
      begin
        ProductID1 := Trim(Copy(Line, 1, PosComma1 - 1));
        
        // Buscar la segunda coma
        PosComma2 := Pos(',', Copy(Line, PosComma1 + 1, Length(Line)));
        
        if PosComma2 > 0 then
        begin
          // Hay segunda coma, extraer nombre y ID2
          ProductName := Trim(Copy(Line, PosComma1 + 1, PosComma2 - 1));
          ProductID2 := Trim(Copy(Line, PosComma1 + PosComma2 + 1, Length(Line)));
          
          FProductNames.Values[ProductID1] := ProductName;
          FProductID2.Values[ProductID1] := ProductID2;
          
          LogDebug('  -> *** PARSEADO EXITOSO ***');
          LogDebug('     ID1 (Consola): "' + ProductID1 + '"');
          LogDebug('     Nombre: "' + ProductName + '"');
          LogDebug('     ID2 (Sistema): "' + ProductID2 + '"');
        end
        else
        begin
          // Solo hay una coma, extraer ID1 y nombre
          ProductName := Trim(Copy(Line, PosComma1 + 1, Length(Line)));
          FProductNames.Values[ProductID1] := ProductName;
          FProductID2.Values[ProductID1] := ProductID1;
          
          LogDebug('  -> *** SIN ID2 *** usando ID1 como ID2');
          LogDebug('     ID1: "' + ProductID1 + '"');
          LogDebug('     Nombre: "' + ProductName + '"');
        end;
      end
      else
      begin
        LogDebug('  -> ERROR: No se encontró coma en la línea');
      end;
      
      LogDebug('----------------------------------------');
    end;
    
    LogDebug('========================================');
    LogDebug('RESUMEN FINAL:');
    LogDebug('Total productos en FProductNames: ' + IntToStr(FProductNames.Count));
    LogDebug('Total ID2 en FProductID2: ' + IntToStr(FProductID2.Count));
    LogDebug('');
    LogDebug('Contenido completo de FProductID2:');
    for i := 0 to FProductID2.Count - 1 do
    begin
      LogDebug('  [' + IntToStr(i) + '] ' + FProductID2[i]);
    end;
    LogDebug('========================================');
    
  except
    on E: Exception do
    begin
      LogDebug('LoadProductNames: *** ERROR CRÍTICO *** ' + E.Message);
      FProductNames.Values['3'] := 'Gasolina Excellium';
      FProductNames.Values['6'] := 'Gasolina Regular';
      FProductNames.Values['7'] := 'Diesel Regular';
      FProductNames.Values['8'] := 'Diesel Excellium';
      FProductNames.Values['9'] := 'Gasolina Racing X';
      
      FProductID2.Values['3'] := '1';
      FProductID2.Values['6'] := '4';
      FProductID2.Values['7'] := '2';
      FProductID2.Values['8'] := '3';
      FProductID2.Values['9'] := '5';
    end;
  end;
  
  FileLines.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  CreateTrayIcon;
  
  FProductNames := TStringList.Create;
  FProductID2 := TStringList.Create;
  LoadProductNames;
  
  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;
  FCurrentSalesPump := 1;
  FFileNeedsSave := False;
  FClientIndex := 0;
  FSalesCheckCount := 0;

  FSalesClient := TIdTCPClient.Create(nil);
  FSalesClient.Host := '172.20.6.1';
  FSalesClient.Port := DEFAULT_PORT;
  FSalesClient.ConnectTimeout := 2000;
  FSalesClient.ReadTimeout := 2000;

  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;

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

  Timer1.Enabled := False;
  Timer1.Interval := 50;
  
  Timer2.Enabled := False;
  Timer2.Interval := 2000;

  UpdateStatus('Desconectado', clRed);
  LogDebug('=== APLICACIÓN INICIADA ===');
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
    end;
    FSalesClient.Free;
  end;
  
  if Assigned(FTrayMenu) then
    FTrayMenu.Free;
    
  if Assigned(FProductNames) then
    FProductNames.Free;
    
  if Assigned(FProductID2) then
    FProductID2.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;

function TForm1.GetFuelName(const GradeCode: string): string;
begin
  Result := FProductNames.Values[GradeCode];
  
  if Result = '' then
  begin
    LogDebug('GetFuelName: Producto no encontrado para GR1=' + GradeCode);
    Result := 'Desconocido (ID:' + GradeCode + ')';
  end;
end;

function TForm1.GetProductID2(const GradeCode: string): string;
begin
  LogDebug('GetProductID2: Buscando ID2 para GR1="' + GradeCode + '"');
  Result := FProductID2.Values[GradeCode];
  
  if Result = '' then
  begin
    LogDebug('GetProductID2: *** NO ENCONTRADO *** usando GR1 como ID2');
    Result := GradeCode;
  end
  else
  begin
    LogDebug('GetProductID2: *** ENCONTRADO *** GR1=' + GradeCode + ' → ID2=' + Result);
  end;
end;

function TForm1.FormatNumber(const Value: string; Decimals: Integer): string;
var
  FloatValue: Double;
  IntegerPart: string;
  DecimalPart: string;
  i, Count: Integer;
  FormatStr: string;
begin
  if not TryStrToFloat(StringReplace(Value, '.', FormatSettings.DecimalSeparator, []), FloatValue) then
  begin
    Result := Value;
    Exit;
  end;
  
  case Decimals of
    1: FormatStr := '0.0';
    3: FormatStr := '0.000';
  else
    FormatStr := '0.0';
  end;
  
  Result := FormatFloat(FormatStr, FloatValue);
  
  IntegerPart := Copy(Result, 1, Pos(FormatSettings.DecimalSeparator, Result) - 1);
  DecimalPart := Copy(Result, Pos(FormatSettings.DecimalSeparator, Result) + 1, Length(Result));
  
  if Length(IntegerPart) > 3 then
  begin
    Result := '';
    Count := 0;
    for i := Length(IntegerPart) downto 1 do
    begin
      if (Count = 3) then
      begin
        Result := '.' + Result;
        Count := 0;
      end;
      Result := IntegerPart[i] + Result;
      Inc(Count);
    end;
  end
  else
    Result := IntegerPart;
  
  Result := Result + ',' + DecimalPart;
end;

function TForm1.BuildSalesCommand(PumpNumber: Integer): string;
var
  CommandBody: string;
  CommandLength: Integer;
  LengthStr: string;
begin
  CommandBody := '2||POST|REQ_GET_PUMP_SALES|||PM=' + IntToStr(PumpNumber) + '|QT=1||^';
  CommandLength := Length(CommandBody);
  LengthStr := Format('%.5d', [CommandLength]);
  Result := LengthStr + '|5|' + CommandBody;
end;

procedure TForm1.CheckLastSaleForPump(PumpNumber: Integer);
var
  Command, Response: string;
  RetryCount: Integer;
begin
  Inc(FSalesCheckCount);
  
  if not FSalesClient.Connected then
  begin
    try
      FSalesClient.Connect;
      if Assigned(FSalesClient.IOHandler) then
        FSalesClient.IOHandler.DefStringEncoding := IndyTextEncoding_ASCII;
    except
      on E: Exception do
        Exit;
    end;
  end;

  Command := BuildSalesCommand(PumpNumber);

  RetryCount := 0;
  repeat
    try
      FSalesClient.IOHandler.InputBuffer.Clear;
      FSalesClient.IOHandler.Write(Command);
      FSalesClient.IOHandler.WriteBufferFlush;
      
      Sleep(150);
      
      FSalesClient.IOHandler.CheckForDataOnSource(800);
      
      if not FSalesClient.IOHandler.InputBufferIsEmpty then
      begin
        Response := FSalesClient.IOHandler.InputBufferAsString;
        
        if (Pos('RES_GET_PUMP_SALES', Response) > 0) and (Pos('RC=OK', Response) > 0) then
        begin
          ParseAndSaveLastSale(Response, PumpNumber);
          Exit;
        end;
      end;
      
      Inc(RetryCount);
      if RetryCount < 2 then
        Sleep(200);
      
    except
      on E: Exception do
      begin
        try
          if FSalesClient.Connected then
            FSalesClient.Disconnect;
        except
        end;
        Exit;
      end;
    end;
  until RetryCount >= 2;
end;

procedure TForm1.ParseAndSaveLastSale(const Response: string; PumpNumber: Integer);
var
  FileStream: TStringList;
  SaleData: string;
  NumVenta, FechaFin, HoraFin, Surtidor, Manguera, Combustible, Volumen, VolCompensado: string;
  PPU, Monto, Totalizador, TCInicial, VolumenFinal, TCFinal, TiempoMedia, TipoVenta, TipoPago: string;
  GradeValue, PayTypeValue, SaleID, ProductID2: string;
  Header: string;
  VOValue: string;
  FileExists: Boolean;
  i, StartIndex: Integer;
begin
  NumVenta := ExtractValue(Response, 'TI1');
  
  if NumVenta = '' then
    Exit;
  
  FileStream := TStringList.Create;
  try
    FileExists := System.SysUtils.FileExists(SALES_FILE_PATH);
    
    if FileExists then
    begin
      FileStream.LoadFromFile(SALES_FILE_PATH);
      
      for i := 1 to FileStream.Count - 1 do
      begin
        if Pos(NumVenta + #9, FileStream[i]) = 1 then
          Exit;
      end;
    end
    else
    begin
      Header := 'ID Venta'#9'# de Venta'#9'Fecha de Fin'#9'Hora de Fin'#9'Surtidor'#9'Manguera'#9'ID Producto'#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;
    
    SaleID := ExtractValue(Response, 'SA1');
    FechaFin := ExtractValue(Response, 'DA1');
    HoraFin := ExtractValue(Response, 'STI1');
    Surtidor := ExtractValue(Response, 'PM');
    Manguera := ExtractValue(Response, 'HO1');
    GradeValue := ExtractValue(Response, 'GR1');
    
    Combustible := GetFuelName(GradeValue);
    ProductID2 := GetProductID2(GradeValue);
    
    LogDebug('ParseAndSaveLastSale: Venta #' + NumVenta + ' | GR1=' + GradeValue + ' → ID2 a guardar=' + ProductID2);
    
    VOValue := ExtractValue(Response, 'VO1');
    Volumen := VOValue;
    VolCompensado := VOValue;
    
    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');
    
    PayTypeValue := Trim(ExtractValue(Response, 'PAY_TY1'));
    
    if (PayTypeValue = '') or (PayTypeValue = '0') then
      TipoPago := 'No Asignado'
    else if SameText(PayTypeValue, 'EFECTIVO') then
      TipoPago := 'EFECTIVO'
    else
      TipoPago := PayTypeValue;
    
    if TipoPago = 'EFECTIVO' then
      TipoVenta := 'Ventas EFECTIVO'
    else
      TipoVenta := 'Ventas';
    
    if Length(FechaFin) = 8 then
      FechaFin := Copy(FechaFin, 7, 2) + '/' + Copy(FechaFin, 5, 2) + '/' + Copy(FechaFin, 1, 4);
    
    if Length(HoraFin) = 6 then
      HoraFin := Copy(HoraFin, 1, 2) + ':' + Copy(HoraFin, 3, 2) + ':' + Copy(HoraFin, 5, 2);

    Volumen := FormatNumber(Volumen, 3);
    VolCompensado := FormatNumber(VolCompensado, 3);
    Totalizador := FormatNumber(Totalizador, 3);
    VolumenFinal := FormatNumber(VolumenFinal, 3);
    
    PPU := FormatNumber(PPU, 1);
    PPU := FloatToStr(StrToFloat(StringReplace(StringReplace(PPU, '.', '', [rfReplaceAll]), ',', '.', [rfReplaceAll])), TFormatSettings.Create('en-US'));
    Monto := FormatNumber(Monto, 1);
    Monto := FloatToStr(StrToFloat(StringReplace(StringReplace(Monto, '.', '', [rfReplaceAll]), ',', '.', [rfReplaceAll])), TFormatSettings.Create('en-US'));
    SaleData := Format('%s'#9'%s'#9'%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',
      [
        SaleID, NumVenta, FechaFin, HoraFin, Surtidor, Manguera, ProductID2, Combustible,
        Volumen, VolCompensado, PPU, Monto, Totalizador, TCInicial,
        VolumenFinal, TCFinal, TiempoMedia, TipoVenta, TipoPago
      ]);
    
    FileStream.Add(SaleData);
    
    if FileStream.Count > (MAX_SALES + 1) then
    begin
      StartIndex := FileStream.Count - MAX_SALES;
      for i := 1 to StartIndex - 1 do
        FileStream.Delete(1);
    end;
    
    FileStream.SaveToFile(SALES_FILE_PATH);
    
    LogDebug('ParseAndSaveLastSale: *** GUARDADO *** Venta #' + NumVenta + ' | ID2=' + ProductID2 + ' | Producto=' + Combustible);
    UpdateStatus('Venta #' + NumVenta + ' (ID:' + SaleID + ' ProdID2:' + ProductID2 + ') - Surtidor ' + IntToStr(PumpNumber), clGreen);
    
  except
    on E: Exception do
      LogDebug('ParseAndSaveLastSale: ERROR - ' + E.Message);
  end;
  FileStream.Free;
end;

procedure TForm1.Timer2Timer(Sender: TObject);
begin
  if not FMonitoringActive then
    Exit;
  
  CheckLastSaleForPump(FCurrentSalesPump);
  
  Inc(FCurrentSalesPump);
  if FCurrentSalesPump > 20 then
    FCurrentSalesPump := 1;
end;

procedure TForm1.InitializeClientPool;
var
  i: Integer;
  Client: TIdTCPClient;
begin
  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;
    except
    end;
  end;
end;

procedure TForm1.FreeClientPool;
var
  i: Integer;
begin
  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
  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
  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;
          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;
  FCurrentSalesPump := 1;
  FSalesCheckCount := 0;
  InitializeClientPool;
  Timer1.Enabled := True;
  Timer2.Enabled := True;
end;

procedure TForm1.StopMonitoring;
begin
  FMonitoringActive := False;
  Timer1.Enabled := False;
  Timer2.Enabled := False;
  FreeClientPool;
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;
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;
  except
  end;
end;

procedure TForm1.auto_conectarTimer(Sender: TObject);
begin
  auto_conectar.Enabled := False;
  ButtonConnect.Click;
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
  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
  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
  end;
end;



initialization
  FormatSettings.DecimalSeparator := '.';
  FormatSettings.ThousandSeparator := ',';

end.
