package com.grigowashere.aismap.controllers;

import android.util.Log;
import com.grigowashere.aismap.models.Vessel;
import com.grigowashere.aismap.models.AISVessel;
import com.grigowashere.aismap.utils.LogSender;

import java.util.List;
import java.util.ArrayList;

/**
 * Контроллер для парсинга NMEA сообщений
 * Работает в гибридном режиме: координаты через Location API, остальное через NMEA
 * 
 * ВАЖНО: Размеры судна в AIS сообщениях рассчитываются относительно положения антенны:
 * - Длина = Dim.A + Dim.B (от носа до антенны + от антенны до кормы)
 * - Ширина = Dim.C + Dim.D (от левого борта до антенны + от антенны до правого борта)
 * Координаты в AIS указывают положение антенны, а не центра судна.
 */

/**
 * Контроллер для парсинга NMEA сообщений
 * Использует простой разбор по запятым вместо регулярных выражений
 */
public class NMEAParser {
    
    private static final String TAG = "NMEAParser";
    
    private Vessel ownVessel;
    private List<AISVessel> aisVessels;
    private NMEAParserListener listener;
    private GPSLocationListener gpsLocationListener;
    
    // Поля для работы с AIS фрагментами
    private java.util.Map<String, java.util.Map<Integer, String>> aisFragments = new java.util.HashMap<>();
    private java.util.Map<String, Long> aisFragmentTimestamps = new java.util.HashMap<>();
    private static final long AIS_FRAGMENT_TIMEOUT = 10000; // 10 секунд
    
    // Флаг для гибридного режима
    private boolean hybridMode = true;
    
    // Поля для отслеживания спутников по системам
    private int gpsSatellites = 0;
    private int glonassSatellites = 0;
    private int galileoSatellites = 0;
    
    public interface NMEAParserListener {
        void onVesselUpdated(Vessel vessel);
        void onAISVesselUpdated(AISVessel vessel);
        void onParseError(String error);
        void onDOPUpdated(double pdop, double hdop, double vdop);
    }
    
    public NMEAParser() {
        this.ownVessel = new Vessel();
        this.aisVessels = new ArrayList<>();
    }
    
    public void setListener(NMEAParserListener listener) {
        this.listener = listener;
    }
    
    /**
     * Устанавливает GPS Location Listener для гибридного режима
     */
    public void setGPSLocationListener(GPSLocationListener gpsLocationListener) {
        this.gpsLocationListener = gpsLocationListener;
    }
    
    /**
     * Включает/выключает гибридный режим
     */
    public void setHybridMode(boolean enabled) {
        this.hybridMode = enabled;
        Log.i(TAG, "🔄 Гибридный режим: " + (enabled ? "включен" : "отключен"));
        Log.i(TAG, "📍 В режиме " + (enabled ? "гибридном" : "только NMEA") + " координаты будут " + 
              (enabled ? "браться из Android GPS API" : "браться из NMEA сообщений"));
    }
    
    /**
     * Парсит NMEA сообщение
     */
    public void parseNMEA(String nmeaSentence) {
        if (nmeaSentence == null || nmeaSentence.trim().isEmpty()) {
            return;
        }
        
        // Очищаем сообщение от лишних символов
        String cleanedSentence = cleanNMEASentence(nmeaSentence);
        if (cleanedSentence == null) {
            Log.w(TAG, "NMEA сообщение не удалось очистить или слишком короткое: " + nmeaSentence);
            return;
        }
        Log.d(TAG, "Парсим NMEA: " + cleanedSentence);
        
        // Отправляем NMEA сообщение на внешний ресурс
        LogSender.logNMEA(cleanedSentence);
        
        try {
            // Разбираем сообщение по запятым
            String[] fields = cleanedSentence.split(",");
            if (fields.length < 2) {
                Log.w(TAG, "NMEA сообщение слишком короткое: " + cleanedSentence);
                return;
            }
            
            // Извлекаем приамбуду (первые 6 символов после $)
            String preamble = fields[0];
            if (preamble.length() < 6) {
                Log.w(TAG, "Некорректная приамбула: " + preamble);
                return;
            }
            
            // Определяем тип сообщения по последним трем символам приамбуды
            String messageType = preamble.substring(preamble.length() - 3);
            
            switch (messageType) {
                case "GGA":
                    parseGGA(fields);
                    break;
                case "RMC":
                    parseRMC(fields);
                    break;
                case "VTG":
                    parseVTG(fields);
                    break;
                case "GLL":
                    parseGLL(fields);
                    break;
                case "GSV":
                    parseGSV(fields);
                    break;
                case "GNS":
                    parseGNS(fields);
                    break;
                case "GSA":
                    parseGSA(fields);
                    break;
                case "ZDA":
                    parseZDA(fields);
                    break;
                default:
                    // Проверяем AIS сообщения
                    if (cleanedSentence.startsWith("!AIVDM")) {
                        parseAIS(cleanedSentence);
                    } else {
                        Log.d(TAG, "Неподдерживаемый тип NMEA сообщения: " + messageType);
                    }
                    break;
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка парсинга NMEA: " + e.getMessage(), e);
            if (listener != null) {
                listener.onParseError("Ошибка парсинга NMEA: " + e.getMessage());
            }
        }
    }
    
    /**
     * Безопасно получает поле по индексу
     */
    private String getField(String[] fields, int index) {
        if (index < fields.length && !fields[index].trim().isEmpty()) {
            return fields[index].trim();
        }
        return null;
    }
    
    /**
     * Безопасно парсит double значение из поля
     */
    private double parseDoubleField(String[] fields, int index, double defaultValue) {
        String field = getField(fields, index);
        if (field != null) {
            try {
                return Double.parseDouble(field);
            } catch (NumberFormatException e) {
                Log.w(TAG, "Не удалось распарсить double из поля " + index + ": '" + field + "'");
            }
        }
        return defaultValue;
    }
    
    /**
     * Безопасно парсит int значение из поля
     */
    private int parseIntField(String[] fields, int index, int defaultValue) {
        String field = getField(fields, index);
        if (field != null) {
            try {
                return Integer.parseInt(field);
            } catch (NumberFormatException e) {
                Log.w(TAG, "Не удалось распарсить int из поля " + index + ": '" + field + "'");
            }
        }
        return defaultValue;
    }
    
    /**
     * Очищает NMEA сообщение от лишних символов
     */
    private String cleanNMEASentence(String sentence) {
        if (sentence == null || sentence.trim().isEmpty()) {
            return null;
        }
        
        // Убираем пробелы в начале и конце
        String cleaned = sentence.trim();
        
        // Проверяем минимальную длину NMEA сообщения
        if (cleaned.length() < 6) { // Минимум: $GPGGA*XX
            Log.w(TAG, "Слишком короткое NMEA сообщение: '" + cleaned + "'");
            return null;
        }
        
        // Исправляем двойной $ ($$GNGGA -> $GNGGA)
        if (cleaned.startsWith("$$")) {
            cleaned = cleaned.substring(1);
            Log.d(TAG, "Исправлен двойной $: " + cleaned);
        }
        
        // Обрабатываем смешанные сообщения (например, VTG содержит GGA)
        if (cleaned.contains("$G") && cleaned.indexOf("$G") > 0) {
            // Находим первое полное NMEA сообщение
            int firstDollar = cleaned.indexOf("$G");
            if (firstDollar > 0) {
                String firstMessage = cleaned.substring(firstDollar);
                int asteriskIndex = firstMessage.indexOf('*');
                if (asteriskIndex > 0) {
                    // Проверяем, что после * есть достаточно символов для контрольной суммы
                    if (asteriskIndex + 2 < firstMessage.length()) {
                        cleaned = firstMessage.substring(0, asteriskIndex + 3);
                    } else if (asteriskIndex + 1 < firstMessage.length()) {
                        cleaned = firstMessage.substring(0, asteriskIndex + 2);
                    } else {
                        cleaned = firstMessage.substring(0, asteriskIndex + 1);
                    }
                    Log.d(TAG, "Извлечено первое NMEA сообщение: " + cleaned);
                }
            }
        }
        
        // Убираем все символы после последнего *
        int asteriskIndex = cleaned.lastIndexOf('*');
        if (asteriskIndex >= 0) {
            // Проверяем, что после * есть достаточно символов для контрольной суммы
            if (asteriskIndex + 2 < cleaned.length()) {
                cleaned = cleaned.substring(0, asteriskIndex + 3); // включаем * и 2 символа контрольной суммы
            } else if (asteriskIndex + 1 < cleaned.length()) {
                cleaned = cleaned.substring(0, asteriskIndex + 2); // включаем * и 1 символ контрольной суммы
            } else {
                cleaned = cleaned.substring(0, asteriskIndex + 1); // включаем только *
            }
        }
        
        // Убираем все непечатаемые символы
        cleaned = cleaned.replaceAll("[^\\x20-\\x7E]", "");
        
        Log.d(TAG, "Очищено NMEA: '" + cleaned + "' (длина: " + cleaned.length() + ")");
        
        return cleaned;
    }
    
    /**
     * Парсит GGA сообщение (Global Positioning System Fix Data)
     * В гибридном режиме используем только количество спутников и высоту
     * Формат: $GPGGA,time,lat,N/S,lon,E/W,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*checksum
     */
    private void parseGGA(String[] fields) {
        Log.d(TAG, "Парсим GGA с " + fields.length + " полями");
        
        // Поле 7: количество спутников
        int satellites = parseIntField(fields, 7, 0);
        
        // Поле 9: высота над эллипсоидом
        double altitude = parseDoubleField(fields, 9, 0.0);
        
        Log.d(TAG, String.format("GGA: sat=%d, alt=%.1f", satellites, altitude));
        
        // В гибридном режиме не обновляем координаты
        if (!hybridMode) {
            // Поля 2,3: широта и направление
            String latStr = getField(fields, 2);
            String latDir = getField(fields, 3);
            if (latStr != null && latDir != null) {
                double latitude = parseCoordinate(latStr, latDir.equals("N"));
                ownVessel.setLatitude(latitude);
            }
            
            // Поля 4,5: долгота и направление
            String lonStr = getField(fields, 4);
            String lonDir = getField(fields, 5);
            if (lonStr != null && lonDir != null) {
                double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
                ownVessel.setLongitude(longitude);
            }
        }
        
        ownVessel.setSatellites(satellites);
        ownVessel.setAltitude(altitude);
        
        // Синхронизируем с GPSLocationListener для получения активных спутников
        if (gpsLocationListener != null) {
            gpsLocationListener.setSatellitesInVessel(ownVessel);
        }
        
        if (listener != null) {
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит RMC сообщение (Recommended Minimum Navigation Information)
     * В гибридном режиме используем только курс и скорость
     * Формат: $GPRMC,time,status,lat,N/S,lon,E/W,speed,course,date,magVar,E/W,mode*checksum
     */
    private void parseRMC(String[] fields) {
        Log.d(TAG, "Парсим RMC с " + fields.length + " полями");
        
        // Поле 2: статус валидности (A = валидный, V = невалидный)
        String status = getField(fields, 2);
        boolean isValid = status != null && status.startsWith("A");
        Log.d(TAG, "RMC статус: " + status + " (валидный: " + isValid + ")");
        
        // Поле 7: скорость в узлах
        double speed = parseDoubleField(fields, 7, 0.0);
        
        // Поле 8: курс в градусах
        double course = parseDoubleField(fields, 8, 0.0);
        
        Log.d(TAG, String.format("RMC: speed=%.1f, course=%.1f, valid=%s", speed, course, isValid));
        
        // В гибридном режиме не обновляем координаты
        if (!hybridMode && isValid) {
            Log.d(TAG, "Режим НЕ гибридный - обрабатываем координаты из RMC");
            
            // Поля 3,4: широта и направление
            String latStr = getField(fields, 3);
            String latDir = getField(fields, 4);
            if (latStr != null && latDir != null) {
                double latitude = parseCoordinate(latStr, latDir.equals("N"));
                Log.d(TAG, "RMC широта: " + latStr + " " + latDir + " = " + latitude);
                ownVessel.setLatitude(latitude);
            }
            
            // Поля 5,6: долгота и направление
            String lonStr = getField(fields, 5);
            String lonDir = getField(fields, 6);
            if (lonStr != null && lonDir != null) {
                double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
                Log.d(TAG, "RMC долгота: " + lonStr + " " + lonDir + " = " + longitude);
                ownVessel.setLongitude(longitude);
            }
        } else if (hybridMode) {
            Log.d(TAG, "Гибридный режим - координаты из RMC игнорируются");
        } else {
            Log.d(TAG, "RMC данные невалидны (статус V) - координаты не обновляем");
        }
        
        // Обновляем скорость и курс только если данные валидны
        if (isValid) {
            ownVessel.setSpeed(speed);
            ownVessel.setCourse(course);
        }
        
        Log.d(TAG, "RMC обновлено судно: lat=" + ownVessel.getLatitude() + 
              ", lon=" + ownVessel.getLongitude() + 
              ", speed=" + speed + 
              ", course=" + course);
        
        if (listener != null) {
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит VTG сообщение (Course Over Ground and Ground Speed)
     * Формат: $GPVTG,course,T,course,M,speed,N,speed,K,mode*checksum
     */
    private void parseVTG(String[] fields) {
        Log.d(TAG, "Парсим VTG с " + fields.length + " полями");
        
        // Поле 1: курс в градусах (True)
        double course = parseDoubleField(fields, 1, 0.0);
        
        // Поле 5: скорость в узлах
        double speed = parseDoubleField(fields, 5, 0.0);
        
        Log.d(TAG, String.format("VTG: course=%.1f, speed=%.1f", course, speed));
        
        ownVessel.setCourse(course);
        ownVessel.setSpeed(speed);
        
        if (listener != null) {
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит GLL сообщение (Geographic Position - Latitude/Longitude)
     * В гибридном режиме игнорируем
     * Формат: $GPGLL,lat,N/S,lon,E/W,time,status,mode*checksum
     */
    private void parseGLL(String[] fields) {
        if (hybridMode) {
            Log.d(TAG, "GLL игнорируется в гибридном режиме");
            return;
        }
        
        Log.d(TAG, "Парсим GLL с " + fields.length + " полями");
        
        // Поля 1,2: широта и направление
        String latStr = getField(fields, 1);
        String latDir = getField(fields, 2);
        if (latStr != null && latDir != null) {
            double latitude = parseCoordinate(latStr, latDir.equals("N"));
            ownVessel.setLatitude(latitude);
        }
        
        // Поля 3,4: долгота и направление
        String lonStr = getField(fields, 3);
        String lonDir = getField(fields, 4);
        if (lonStr != null && lonDir != null) {
            double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
            ownVessel.setLongitude(longitude);
        }
        
        Log.d(TAG, String.format("GLL: lat=%.6f, lon=%.6f", ownVessel.getLatitude(), ownVessel.getLongitude()));
        
        if (listener != null) {
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит GSV сообщение (GPS Satellites in View)
     * Формат: $GPGSV,totalMsgs,msgNum,totalSats,satId1,elev1,azim1,snr1,satId2,elev2,azim2,snr2,...*checksum
     */
    private void parseGSV(String[] fields) {
        Log.d(TAG, "Парсим GSV с " + fields.length + " полями");
        
        // Поля 1,2,3: общее количество сообщений, номер сообщения, общее количество спутников
        int totalMessages = parseIntField(fields, 1, 1);
        int messageNumber = parseIntField(fields, 2, 1);
        int satellitesInView = parseIntField(fields, 3, 0);
        
        // Определяем тип системы спутников по приамбуде
        String systemType = "Unknown";
        String preamble = fields[0];
        if (preamble.startsWith("$GPGSV")) {
            systemType = "GPS";
        } else if (preamble.startsWith("$GLGSV")) {
            systemType = "GLONASS";
        } else if (preamble.startsWith("$GAGSV")) {
            systemType = "Galileo";
        } else if (preamble.startsWith("$GBGSV")) {
            systemType = "BeiDou";
        }
        
        Log.d(TAG, String.format("GSV [%s]: %d/%d, спутников в поле зрения: %d",
            systemType, messageNumber, totalMessages, satellitesInView));
        
        // Парсим данные о спутниках (начиная с поля 4, каждые 4 поля = 1 спутник)
        for (int i = 4; i < fields.length - 1; i += 4) { // -1 чтобы исключить контрольную сумму
            if (i + 3 < fields.length) {
                String satId = getField(fields, i);
                String elevation = getField(fields, i + 1);
                String azimuth = getField(fields, i + 2);
                String snr = getField(fields, i + 3);
                
                if (satId != null) {
                    Log.d(TAG, String.format("Спутник %s: elev=%s, azim=%s, SNR=%s",
                        satId, elevation, azimuth, snr));
                }
            }
        }
        
        // Обновляем количество спутников только для последнего сообщения в серии
        if (messageNumber == totalMessages) {
            // Обновляем количество спутников для соответствующей системы
            switch (systemType) {
                case "GPS":
                    gpsSatellites = satellitesInView;
                    break;
                case "GLONASS":
                    glonassSatellites = satellitesInView;
                    break;
                case "Galileo":
                    galileoSatellites = satellitesInView;
                    break;
                case "BeiDou":
                    // Пока не добавляем отдельный счетчик для BeiDou, считаем как GPS
                    gpsSatellites = Math.max(gpsSatellites, satellitesInView);
                    break;
            }
            
            // Обновляем общее количество спутников
            int totalSatellites = gpsSatellites + glonassSatellites + galileoSatellites;
            ownVessel.setSatellites(totalSatellites);
            
            // Синхронизируем с GPSLocationListener для получения активных спутников
            if (gpsLocationListener != null) {
                gpsLocationListener.setSatellitesInVessel(ownVessel);
            }
            
            Log.d(TAG, String.format("GSV [%s] завершен: %d спутников. Общий счет: GPS=%d, GLONASS=%d, Galileo=%d, Всего=%d",
                systemType, satellitesInView, gpsSatellites, glonassSatellites, galileoSatellites, totalSatellites));
            
            if (listener != null) {
                listener.onVesselUpdated(ownVessel);
            }
        }
    }
    
    /**
     * Парсит GNS сообщение (GNSS Fix Data)
     * В гибридном режиме используем только количество спутников и высоту
     * Формат: $GNGNS,time,lat,N/S,lon,E/W,mode,numSV,HDOP,alt,sep,diffAge,diffStation,navStatus*checksum
     */
    private void parseGNS(String[] fields) {
        Log.d(TAG, "Парсим GNS с " + fields.length + " полями");
        
        // Поле 7: количество спутников
        int satellites = parseIntField(fields, 7, 0);
        
        // Поле 9: высота над эллипсоидом
        double altitude = parseDoubleField(fields, 9, 0.0);
        
        Log.d(TAG, String.format("GNS: sat=%d, alt=%.1f", satellites, altitude));
        
        // В гибридном режиме не обновляем координаты
        if (!hybridMode) {
            // Поля 2,3: широта и направление
            String latStr = getField(fields, 2);
            String latDir = getField(fields, 3);
            if (latStr != null && latDir != null) {
                double latitude = parseCoordinate(latStr, latDir.equals("N"));
                ownVessel.setLatitude(latitude);
            }
            
            // Поля 4,5: долгота и направление
            String lonStr = getField(fields, 4);
            String lonDir = getField(fields, 5);
            if (lonStr != null && lonDir != null) {
                double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
                ownVessel.setLongitude(longitude);
            }
        }
        
        ownVessel.setSatellites(satellites);
        ownVessel.setAltitude(altitude);
        
        // Синхронизируем с GPSLocationListener для получения активных спутников
        if (gpsLocationListener != null) {
            gpsLocationListener.setSatellitesInVessel(ownVessel);
        }
        
        if (listener != null) {
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит ZDA сообщение (Date and Time)
     * Формат: $GPZDA,time,day,month,year,timezoneHours,timezoneMinutes*checksum
     */
    private void parseZDA(String[] fields) {
        Log.d(TAG, "Парсим ZDA с " + fields.length + " полями");
        
        try {
            // Поле 1: время (HHMMSS.SS)
            String timeStr = getField(fields, 1);
            
            // Поля 2,3,4: день, месяц, год
            int day = parseIntField(fields, 2, 0);
            int month = parseIntField(fields, 3, 0);
            int year = parseIntField(fields, 4, 0);
            
            // Поля 5,6: часовой пояс (часы и минуты)
            int timezoneHours = parseIntField(fields, 5, 0);
            int timezoneMinutes = parseIntField(fields, 6, 0);
            
            Log.d(TAG, String.format("ZDA: %04d-%02d-%02d %s, TZ: %+03d:%02d", 
                year, month, day, timeStr, timezoneHours, timezoneMinutes));
            
            // Обновляем время последнего обновления
            ownVessel.setLastUpdate(java.time.LocalDateTime.now());
            
            if (listener != null) {
                listener.onVesselUpdated(ownVessel);
            }
            
        } catch (Exception e) {
            Log.w(TAG, "Ошибка парсинга ZDA: " + e.getMessage());
        }
    }
    
    /**
     * Парсит GSA сообщение (GPS DOP and Active Satellites)
     * КЛЮЧЕВОЕ сообщение для получения DOP и активных спутников
     * Формат: $GPGSA,mode,fixType,sat1,sat2,...,sat12,PDOP,HDOP,VDOP*checksum
     */
    private void parseGSA(String[] fields) {
        Log.d(TAG, "Парсим GSA с " + fields.length + " полями");
        
        // Подсчитываем активные спутники (поля 3-14 содержат ID спутников)
        int activeSatellites = 0;
        for (int i = 3; i <= 14 && i < fields.length; i++) {
            String satId = getField(fields, i);
            if (satId != null && !satId.equals("0")) {
                activeSatellites++;
                Log.d(TAG, "Активный спутник: " + satId);
            }
        }
        
        // Получаем DOP значения - могут быть в разных позициях в зависимости от количества полей
        double pdop = 0.0;
        double hdop = 0.0;
        double vdop = 0.0;
        
        // DOP значения обычно в последних полях перед контрольной суммой
        if (fields.length >= 17) {
            // Полное GSA сообщение
            pdop = parseDoubleField(fields, 15, 0.0); // PDOP
            hdop = parseDoubleField(fields, 16, 0.0); // HDOP
            vdop = parseDoubleField(fields, 17, 0.0); // VDOP
        } else if (fields.length >= 6) {
            // Обрезанное GSA сообщение - DOP в последних полях
            int dopStartIndex = fields.length - 4; // -4 чтобы исключить контрольную сумму
            if (dopStartIndex >= 3) {
                pdop = parseDoubleField(fields, dopStartIndex, 0.0);
                hdop = parseDoubleField(fields, dopStartIndex + 1, 0.0);
                vdop = parseDoubleField(fields, dopStartIndex + 2, 0.0);
            }
        }
        
        Log.d(TAG, String.format("GSA: активных спутников=%d, PDOP=%.2f, HDOP=%.2f, VDOP=%.2f",
            activeSatellites, pdop, hdop, vdop));
        
        // Обновляем информацию о спутниках
        ownVessel.setActiveSatellites(activeSatellites);
        ownVessel.setPdop(pdop);
        ownVessel.setHdop(hdop);
        ownVessel.setVdop(vdop);
        
        // Отправляем DOP значения в GPS Location Listener
        if (gpsLocationListener != null) {
            gpsLocationListener.setDOPValues(pdop, hdop, vdop);
            // Синхронизируем с GPSLocationListener для получения активных спутников
            gpsLocationListener.setSatellitesInVessel(ownVessel);
        }
        
        // Уведомляем слушателя о DOP
        if (listener != null) {
            listener.onDOPUpdated(pdop, hdop, vdop);
            listener.onVesselUpdated(ownVessel);
        }
    }
    
    /**
     * Парсит AIS сообщение (Automatic Identification System)
     * Формат: !AIVDM,totalFragments,fragmentNumber,sequenceId,channel,payload,fillBits*checksum
     */
    private void parseAIS(String ais) {
        Log.d(TAG, "Парсим AIS: " + ais);
        
        // Разбираем AIS сообщение по запятым
        String[] fields = ais.split(",");
        Log.d(TAG, "AIS поля (" + fields.length + "): " + java.util.Arrays.toString(fields));
        if (fields.length < 7) {
            Log.w(TAG, "AIS сообщение слишком короткое: " + ais);
            return;
        }
        
        try {
            // Поля 1,2: общее количество фрагментов, номер фрагмента
            int totalFragments = parseIntField(fields, 1, 1);
            int fragmentNumber = parseIntField(fields, 2, 1);
            
            // Поле 3: ID последовательности
            String sequenceId = getField(fields, 3);
            
            // Поле 4: канал (A или B)
            String channel = getField(fields, 4);
            
            // Поле 5: payload (данные)
            String payload = getField(fields, 5);
            
            // Поле 6: количество бит заполнения (может содержать *checksum)
            String fillBitsField = getField(fields, 6);
            int fillBits = 0;
            if (fillBitsField != null) {
                // Если поле содержит *, берем только часть до *
                if (fillBitsField.contains("*")) {
                    fillBitsField = fillBitsField.split("\\*")[0];
                }
                try {
                    fillBits = Integer.parseInt(fillBitsField);
                } catch (NumberFormatException e) {
                    Log.w(TAG, "Не удалось распарсить fillBits из поля 6: '" + fillBitsField + "'");
                }
            }
            
            // Контрольная сумма находится в последнем поле после *
            String lastField = fields[fields.length - 1];
            String checksum = null;
            if (lastField != null && lastField.contains("*")) {
                String[] parts = lastField.split("\\*");
                if (parts.length > 1) {
                    checksum = parts[1];
                }
            }
            
            Log.d(TAG, String.format("AIS: %d/%d, seq='%s', ch='%s', payload='%s', fillBits=%d, checksum='%s'",
                fragmentNumber, totalFragments, sequenceId, channel, payload, fillBits, checksum));
            
            // Проверяем контрольную сумму
            if (!validateChecksum(ais)) {
                Log.w(TAG, "AIS сообщение с неверной контрольной суммой: " + ais);
                return;
            }
            
            // Проверяем, что payload не пустой
            if (payload != null && !payload.trim().isEmpty()) {
                if (totalFragments == 1) {
                    // Одноканальное сообщение - декодируем сразу
                    decodeAISPayload(payload, channel != null && channel.equals("A") ? 0 : 1);
                } else {
                    // Многочастное сообщение - собираем фрагменты
                    // Используем номер фрагмента как sequenceId если поле пустое
                    String actualSequenceId = (sequenceId != null && !sequenceId.trim().isEmpty()) ? 
                        sequenceId : String.valueOf(fragmentNumber);
                    collectAISFragments(actualSequenceId, fragmentNumber, totalFragments, payload, channel != null && channel.equals("A") ? 0 : 1);
                }
            } else {
                Log.w(TAG, "AIS payload пустой, пропускаем сообщение");
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка парсинга AIS сообщения: " + e.getMessage() + " для сообщения: " + ais);
            if (listener != null) {
                listener.onParseError("Ошибка парсинга AIS: " + e.getMessage());
            }
        }
    }
    
    /**
     * Декодирует AIS payload
     */
    private void decodeAISPayload(String payload, int channel) {
        try {
            // Определяем тип AIS сообщения по первым 6 битам
            String messageTypeBits = decodeAISField(payload, 0, 6);
            int messageType = Integer.parseInt(messageTypeBits, 2);
            
            Log.d(TAG, "Декодируем AIS тип " + messageType + " на канале " + channel + " (биты: " + messageTypeBits + ")");
            
            switch (messageType) {
                case 1:
                case 2:
                case 3:
                    // Position Report
                    Log.d(TAG, "Обрабатываем Position Report (тип " + messageType + ")");
                    decodePositionReport(payload, messageType);
                    break;
                case 5:
                    // Static Data
                    Log.d(TAG, "Обрабатываем Static Data (тип " + messageType + ")");
                    decodeStaticData(payload);
                    break;
                case 4: // Base Station Report
                    Log.d(TAG, "Обрабатываем Base Station Report (тип " + messageType + ")");
                    decodeBaseStationReport(payload);
                    break;
                case 14: // Safety Related Broadcast Message
                    Log.d(TAG, "Обрабатываем Safety Broadcast (тип " + messageType + ")");
                    decodeSafetyBroadcast(payload);
                    break;
                case 18: // Standard Class B Equipment Position Report
                    Log.d(TAG, "Обрабатываем Class B Position Report (тип " + messageType + ")");
                    decodeClassBPositionReport(payload);
                    break;
                case 19: // Extended Class B Equipment Position Report
                    Log.d(TAG, "Обрабатываем Extended Class B Position Report (тип " + messageType + ")");
                    decodeExtendedClassBPositionReport(payload);
                    break;
                case 21: // Aid-to-Navigation Report
                    Log.d(TAG, "Обрабатываем Aid-to-Navigation Report (тип " + messageType + ")");
                    decodeAidToNavigationReport(payload);
                    break;
                case 24: // Static Data Report
                    Log.d(TAG, "Обрабатываем Static Data Report (тип " + messageType + ")");
                    decodeStaticDataReport(payload);
                    break;
                default:
                    Log.d(TAG, "Неподдерживаемый тип AIS сообщения: " + messageType);
                    break;
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования AIS payload: " + e.getMessage(), e);
        }
    }
    
    /**
     * Собирает фрагменты многочастного AIS сообщения
     */
    private void collectAISFragments(String sequenceId, int fragmentNumber, int totalFragments, 
                                   String payload, int channel) {
        String key = sequenceId + "_" + channel;
        
        Log.d(TAG, String.format("Собираем AIS фраг  мент: %d/%d для %s",
            fragmentNumber, totalFragments, key));
        
        // Очищаем старые фрагменты
        cleanupOldFragments();
        
        // Получаем или создаем карту фрагментов для этой последовательности
        java.util.Map<Integer, String> fragments = aisFragments.get(key);
        if (fragments == null) {
            fragments = new java.util.HashMap<>();
            aisFragments.put(key, fragments);
            aisFragmentTimestamps.put(key, System.currentTimeMillis());
            Log.d(TAG, "Создан новый набор фрагментов для: " + key);
        }
        
        // Добавляем фрагмент
        fragments.put(fragmentNumber, payload);
        Log.d(TAG, String.format("Добавлен фрагмент %d/%d для %s", 
            fragmentNumber, totalFragments, key));
        
        // Проверяем, все ли фрагменты получены
        if (fragments.size() == totalFragments) {
            Log.d(TAG, "Все фрагменты получены для " + key + ", собираем сообщение");
            
            // Собираем полное сообщение
            StringBuilder fullPayload = new StringBuilder();
            for (int i = 1; i <= totalFragments; i++) {
                String fragment = fragments.get(i);
                if (fragment != null) {
                    fullPayload.append(fragment);
                } else {
                    Log.w(TAG, "Отсутствует фрагмент " + i + " для " + key);
                    return;
                }
            }
            
            String completePayload = fullPayload.toString();
            Log.d(TAG, "Собрано полное AIS сообщение длиной " + completePayload.length() + " символов");
            
            // Декодируем полное сообщение
            decodeAISPayload(completePayload, channel);
            
            // Удаляем собранные фрагменты
            aisFragments.remove(key);
            aisFragmentTimestamps.remove(key);
            Log.d(TAG, "Фрагменты удалены для " + key);
        } else {
            Log.d(TAG, String.format("Ожидаем еще %d фрагментов для %s", 
                totalFragments - fragments.size(), key));
        }
    }
    
    /**
     * Очищает старые AIS фрагменты
     */
    private void cleanupOldFragments() {
        long currentTime = System.currentTimeMillis();
        java.util.Iterator<java.util.Map.Entry<String, Long>> iterator = aisFragmentTimestamps.entrySet().iterator();
        
        while (iterator.hasNext()) {
            java.util.Map.Entry<String, Long> entry = iterator.next();
            if (currentTime - entry.getValue() > AIS_FRAGMENT_TIMEOUT) {
                String key = entry.getKey();
                aisFragments.remove(key);
                iterator.remove();
                Log.d(TAG, "Удален устаревший AIS фрагмент: " + key);
            }
        }
    }
    
    /**
     * Декодирует AIS поле из битовой строки
     */
    private String decodeAISField(String payload, int startBit, int length) {
        StringBuilder result = new StringBuilder();

        // Преобразуем каждый символ payload в 6-битное значение
        for (int i = 0; i < payload.length(); i++) {
            int ascii = payload.charAt(i);
            int value;

            if (ascii >= 48 && ascii <= 87) {
                value = ascii - 48;   // '0'..'W'
            } else if (ascii >= 88 && ascii <= 119) {
                value = ascii - 56;   // 'X'..'w'
            } else {
                throw new IllegalArgumentException("Недопустимый символ AIS payload: " + (char)ascii);
            }

            // Дополняем до 6 бит слева нулями и добавляем в общую строку
            String binary = String.format("%6s", Integer.toBinaryString(value)).replace(' ', '0');
            result.append(binary);
        }

        String fullBinary = result.toString();

        // Вырезаем нужный диапазон битов
        if (startBit + length <= fullBinary.length()) {
            String fieldResult = fullBinary.substring(startBit, startBit + length);
            // Дополнительное логирование для первых 6 бит (тип сообщения)
            if (startBit == 0 && length == 6) {
                Log.d(TAG, "AIS Message Type bits: " + fieldResult + " (payload: " + payload + ")");
            }
            return fieldResult;
        } else {
            Log.w(TAG,
                    "AIS поле выходит за границы: startBit=" + startBit +
                            ", length=" + length +
                            ", payloadLength=" + payload.length() +
                            ", binaryLength=" + fullBinary.length()
            );
            // Если поле выходит за границы, возвращаем то что есть, дополняя нулями
            if (startBit >= fullBinary.length()) {
                // Если startBit уже за границами, возвращаем строку из нулей
                return "0".repeat(length);
            } else {
                // Возвращаем доступную часть, дополняя нулями до нужной длины
                String available = fullBinary.substring(startBit);
                if (available.length() < length) {
                    available += "0".repeat(length - available.length());
                }
                return available;
            }
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 1, 2, 3 (Position Report)
     */
    private void decodePositionReport(String payload, int messageType) {
        try {
            Log.d(TAG, "Декодируем Position Report тип " + messageType + ", payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Navigation Status (4 бита) - бит 38
            String statusBits = decodeAISField(payload, 38, 4);
            int status = Integer.parseInt(statusBits, 2);
            Log.d(TAG, "Status bits: " + statusBits + " = " + status);
            
            // Rate of Turn (8 бит) - бит 42
            String rotBits = decodeAISField(payload, 42, 8);
            int rotRaw = Integer.parseInt(rotBits, 2);
            if (rotRaw > 127) {
                rotRaw -= 256;
            }
            double rateOfTurn = parseRateOfTurn(rotRaw);
            Log.d(TAG, "Rate of Turn bits: " + rotBits + " = " + rotRaw + " -> " + rateOfTurn + " °/min");
            
            // Дополнительная отладка - показываем все биты payload
            String fullBinary = payloadToBinary(payload);
            Log.d(TAG, "Full payload binary: " + fullBinary);
            Log.d(TAG, "ROT bits 42-49: " + fullBinary.substring(42, Math.min(50, fullBinary.length())));
            
            // Ищем ROT в разных позициях для отладки
            // for (int pos = 0; pos < Math.min(fullBinary.length() - 8, 100); pos++) {
            //     String testBits = fullBinary.substring(pos, pos + 8);
            //     int testValue = Integer.parseInt(testBits, 2);
            //     double testRot = parseRateOfTurn(testValue);
            //     Log.d(TAG, String.format("Position %d: bits=%s, value=%d, rot=%.1f", 
            //         pos, testBits, testValue, testRot));
            // }
            
            // Speed Over Ground (10 бит) - бит 50
            String speedBits = decodeAISField(payload, 50, 10);
            double speed = Integer.parseInt(speedBits, 2) / 10.0;
            Log.d(TAG, "Speed bits: " + speedBits + " = " + speed);
            
            // Position Accuracy (1 бит) - бит 60
            String accuracyBits = decodeAISField(payload, 60, 1);
            int accuracy = Integer.parseInt(accuracyBits, 2);
            Log.d(TAG, "Accuracy bits: " + accuracyBits + " = " + accuracy);
            
            // Longitude (28 бит) - бит 61
            String lonBits = decodeAISField(payload, 61, 28);
            double longitude = parseAISCoordinate(lonBits, 28);
            Log.d(TAG, "Longitude bits: " + lonBits + " (длина: " + lonBits.length() + ") = " + longitude);
            
            // Latitude (27 бит) - бит 89
            String latBits = decodeAISField(payload, 89, 27);
            double latitude = parseAISCoordinate(latBits, 27);
            Log.d(TAG, "Latitude bits: " + latBits + " (длина: " + latBits.length() + ") = " + latitude);
            
            // Course Over Ground (12 бит) - бит 116
            String courseBits = decodeAISField(payload, 116, 12);
            double course = Integer.parseInt(courseBits, 2) / 10.0;
            Log.d(TAG, "Course bits: " + courseBits + " = " + course);
            
            // True Heading (9 бит) - бит 128
            String headingBits = decodeAISField(payload, 128, 9);
            double heading = Integer.parseInt(headingBits, 2);
            Log.d(TAG, "Heading bits: " + headingBits + " = " + heading);
            
            // Time Stamp (6 бит) - бит 137
            String timestampBits = decodeAISField(payload, 137, 6);
            int timestamp = Integer.parseInt(timestampBits, 2);
            Log.d(TAG, "Timestamp bits: " + timestampBits + " = " + timestamp);
            
            // Проверяем, что координаты в разумных пределах
            if (latitude < -90 || latitude > 90) {
                Log.w(TAG, "Широта вне допустимых пределов: " + latitude);
            }
            if (longitude < -180 || longitude > 180) {
                Log.w(TAG, "Долгота вне допустимых пределов: " + longitude);
            }
            
            Log.d(TAG, String.format("AIS Position: MMSI=%d, lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, status=%d, heading=%.1f, rot=%.1f", 
                mmsi, latitude, longitude, course, speed, status, heading, rateOfTurn));
            
            // Создаем или обновляем AIS судно
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            vessel.updatePosition(latitude, longitude, course, speed, rateOfTurn);
            vessel.setPositionAccuracy(accuracy == 1);
            vessel.setHeading(heading);
            vessel.setNavigationalStatus(getNavigationStatus(status));
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            // Помечаем класс судна как Class A, чтобы предотвратить дальнейшее перезаписывание Class B сообщениями
            vessel.setVesselClass("Class A");
            
            // Отправляем информацию о корабле на внешний ресурс (помечаем как Class A и добавляем статические поля, если известны)
            StringBuilder infoA = new StringBuilder(
                String.format(java.util.Locale.US,
                        "Class A: lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, rot=%.1f, status=%s",
                        latitude, longitude, course, speed, rateOfTurn, getNavigationStatus(status))
            );
            if (vessel.getVesselName() != null && !vessel.getVesselName().trim().isEmpty()) {
                infoA.append(String.format(java.util.Locale.US, ", name='%s'", vessel.getVesselName()));
            }
            if (vessel.getCallSign() != null && !vessel.getCallSign().trim().isEmpty()) {
                infoA.append(String.format(java.util.Locale.US, ", callSign='%s'", vessel.getCallSign()));
            }
            if (vessel.getVesselType() != null && !vessel.getVesselType().trim().isEmpty()) {
                infoA.append(String.format(java.util.Locale.US, ", type=%s", vessel.getVesselType()));
            }
            if (vessel.getLength() > 0 || vessel.getWidth() > 0) {
                infoA.append(String.format(java.util.Locale.US, ", L=%.1f, W=%.1f", vessel.getLength(), vessel.getWidth()));
            }
            if (vessel.getDraft() > 0) {
                infoA.append(String.format(java.util.Locale.US, ", D=%.1f", vessel.getDraft()));
            }
            if (vessel.getDestination() != null && !vessel.getDestination().trim().isEmpty()) {
                infoA.append(String.format(java.util.Locale.US, ", dest='%s'", vessel.getDestination()));
            }
            LogSender.logShipUpdate(String.valueOf(mmsi), infoA.toString());
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Position Report: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 5 (Static Data)
     */
    private void decodeStaticData(String payload) {
        try {
            Log.d(TAG, "Декодируем Static Data, payload: " + payload + " (длина: " + payload.length() + ")");
            Log.d(TAG, "Общая длина в битах: " + (payload.length() * 6));
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // AIS Version (2 бита) - бит 38
            String aisVersionBits = decodeAISField(payload, 38, 2);
            int aisVersion = Integer.parseInt(aisVersionBits, 2);
            Log.d(TAG, "AIS Version bits: " + aisVersionBits + " = " + aisVersion);
            
            // IMO Number (30 бит) - бит 40
            String imoBits = decodeAISField(payload, 40, 30);
            int imo = Integer.parseInt(imoBits, 2);
            Log.d(TAG, "IMO bits: " + imoBits + " = " + imo);
            
            // Call Sign (42 бита) - бит 70
            String callSignBits = decodeAISField(payload, 70, 42);
            String callSign = decodeAISString(callSignBits);
            Log.d(TAG, "Call Sign bits: " + callSignBits + " = '" + callSign + "'");
            
            // Vessel Name (120 бит) - бит 112
            String nameBits = decodeAISField(payload, 112, 120);
            String vesselName = decodeAISString(nameBits);
            Log.d(TAG, "Name bits: " + nameBits + " = '" + vesselName + "'");
            
            // Ship Type (8 бит) - бит 232
            String typeBits = decodeAISField(payload, 232, 8);
            int vesselTypeCode = Integer.parseInt(typeBits, 2);
            Log.d(TAG, "Type bits: " + typeBits + " = " + vesselTypeCode);
            
            // Dimension Reference (9, 9, 6, 6 бит) - бит 240
            String dimRefABits = decodeAISField(payload, 240, 9);
            String dimRefBBits = decodeAISField(payload, 249, 9);
            String dimRefCBits = decodeAISField(payload, 258, 6);
            String dimRefDBits = decodeAISField(payload, 264, 6);
            
            int dimRefA = Integer.parseInt(dimRefABits, 2);
            int dimRefB = Integer.parseInt(dimRefBBits, 2);
            int dimRefC = Integer.parseInt(dimRefCBits, 2);
            int dimRefD = Integer.parseInt(dimRefDBits, 2);
            
            Log.d(TAG, "Dimension Reference: A=" + dimRefA + ", B=" + dimRefB + ", C=" + dimRefC + ", D=" + dimRefD);
            
            // Для сообщения типа 5 используем Dimension Reference поля (9, 9, 6, 6 бит)
            // Размеры судна рассчитываются как:
            // Длина = Dim.A + Dim.B (от носа до антенны + от антенны до кормы)
            // Ширина = Dim.C + Dim.D (от левого борта до антенны + от антенны до правого борта)
            double length = dimRefA + dimRefB;
            double width = dimRefC + dimRefD;
            
            // Draft (8 бит) - осадка - бит 294
            String draftBits = decodeAISField(payload, 294, 8);
            double draft = Integer.parseInt(draftBits, 2) / 10.0;
            
            Log.d(TAG, "Static Data - используем Dimension Reference поля (9, 9, 6, 6 бит):");
            Log.d(TAG, "  Dim.A (нос-антенна): " + dimRefABits + " = " + dimRefA + " м");
            Log.d(TAG, "  Dim.B (антенна-корма): " + dimRefBBits + " = " + dimRefB + " м");
            Log.d(TAG, "  Dim.C (левый борт-антенна): " + dimRefCBits + " = " + dimRefC + " м");
            Log.d(TAG, "  Dim.D (антенна-правый борт): " + dimRefDBits + " = " + dimRefD + " м");
            Log.d(TAG, "  Total Length (A+B): " + length + " м");
            Log.d(TAG, "  Total Width (C+D): " + width + " м");
            Log.d(TAG, "  Draft: " + draftBits + " = " + draft + " м");
            
            // ETA (20 бит) - бит 274
            String etaBits = decodeAISField(payload, 274, 20);
            int eta = Integer.parseInt(etaBits, 2);
            Log.d(TAG, "ETA bits: " + etaBits + " = " + eta);
            
            // Парсим ETA согласно стандарту: MMDDHHMM UTC
            // Bits 19-16: month; 1-12; 0 = not available = default
            // Bits 15-11: day; 1-31; 0 = not available = default  
            // Bits 10-6: hour; 0-23; 24 = not available = default
            // Bits 5-0: minute; 0-59; 60 = not available = default
            java.time.LocalDateTime etaDateTime = parseETA(eta);
            Log.d(TAG, "ETA parsed: " + etaDateTime);
            
            // Вычисляем доступную длину для оставшихся полей
            int totalBits = payload.length() * 6;
            int remainingBits = totalBits - 294; // Остается после ETA
            Log.d(TAG, "Remaining bits after ETA: " + remainingBits + " (total: " + totalBits + ")");
            
            String destination = "";
            double maxDraught = 0.0;
            String epfdDescription = "Unknown";
            boolean dteReady = false;
            
            // Destination (120 бит) - бит 302
            if (totalBits >= 302 + 120) {
                String destBits = decodeAISField(payload, 302, 120);
                destination = decodeAISString(destBits);
                Log.d(TAG, "Destination bits: " + destBits + " = '" + destination + "'");
            } else if (remainingBits > 0) {
                // Если сообщение короткое, читаем доступные биты
                int destStartBit = 302;
                int destLength = Math.min(remainingBits, 120);
                String destBits = decodeAISField(payload, destStartBit, destLength);
                destination = decodeAISString(destBits);
                Log.d(TAG, "Destination bits (short): " + destBits + " = '" + destination + "' (length: " + destLength + ")");
            }
            
            Log.d(TAG, String.format("AIS Static: MMSI=%d, IMO=%d, name='%s', callSign='%s', type=%d, L=%.1f, W=%.1f, D=%.1f, maxD=%.1f, ETA=%s, EPFD=%s, DTE=%s, dest='%s'", 
                mmsi, imo, vesselName, callSign, vesselTypeCode, length, width, draft, maxDraught, etaDateTime, epfdDescription, dteReady, destination));
            
            // Обновляем AIS судно
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            vessel.setVesselName(vesselName);
            vessel.setCallSign(callSign);
            vessel.setImo(imo);
            vessel.setVesselType(getVesselType(vesselTypeCode));
            vessel.setLength(length);
            vessel.setWidth(width);
            vessel.setDraft(draft);
            vessel.setDestination(destination);
            vessel.setEta(etaDateTime); // Добавляем ETA в модель
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            
            // Отправляем информацию о корабле на внешний ресурс (помечаем как Class A Static)
            String vesselInfo = String.format(java.util.Locale.US,
                "Class A Static: name='%s', callSign='%s', type=%s, L=%.1f, W=%.1f, D=%.1f, dest='%s'",
                vesselName, callSign, getVesselType(vesselTypeCode), length, width, draft, destination);
            LogSender.logShipUpdate(String.valueOf(mmsi), vesselInfo);
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Static Data: " + e.getMessage(), e);
        }
    }
    
    /**
     * Парсит ETA (Estimated Time of Arrival) из 20-битного значения
     * Формат: MMDDHHMM UTC
     * Bits 19-16: month; 1-12; 0 = not available = default
     * Bits 15-11: day; 1-31; 0 = not available = default  
     * Bits 10-6: hour; 0-23; 24 = not available = default
     * Bits 5-0: minute; 0-59; 60 = not available = default
     */
    private java.time.LocalDateTime parseETA(int eta) {
        if (eta == 0) {
            return null; // Not available
        }
        
        Log.d(TAG, "ETA raw value: " + eta + " (binary: " + Integer.toBinaryString(eta) + ")");
        
        // Извлекаем компоненты из 20-битного значения
        // Правильный порядок битов: MMMM DDDDD HHHHH MMMMMM
        int month = (eta >> 16) & 0x0F;  // Bits 19-16 (4 бита)
        int day = (eta >> 11) & 0x1F;    // Bits 15-11 (5 бит)
        int hour = (eta >> 6) & 0x1F;    // Bits 10-6 (5 бит)
        int minute = eta & 0x3F;         // Bits 5-0 (6 бит)
        
        Log.d(TAG, String.format("ETA components: month=%d, day=%d, hour=%d, minute=%d", 
            month, day, hour, minute));
        
        // Проверяем на значения по умолчанию
        if (month == 0 || month > 12) return null;      // Not available
        if (day == 0 || day > 31) return null;          // Not available
        if (hour == 24 || hour > 23) return null;       // Not available
        if (minute == 60 || minute > 59) return null;   // Not available
        
        try {
            // Создаем LocalDateTime для текущего года
            int currentYear = java.time.LocalDate.now().getYear();
            java.time.LocalDateTime etaDateTime = java.time.LocalDateTime.of(
                currentYear, month, day, hour, minute);
            
            Log.d(TAG, "ETA parsed as LocalDateTime: " + etaDateTime);
            return etaDateTime;
        } catch (Exception e) {
            Log.w(TAG, "Ошибка создания LocalDateTime для ETA: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * Преобразует AIS payload в полную битовую строку для отладки
     */
    private String payloadToBinary(String payload) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < payload.length(); i++) {
            int ascii = payload.charAt(i);
            int value;
            if (ascii >= 48 && ascii <= 87) {
                value = ascii - 48;
            } else if (ascii >= 88 && ascii <= 119) {
                value = ascii - 56;
            } else {
                value = 0;
            }
            String binary = String.format("%6s", Integer.toBinaryString(value)).replace(' ', '0');
            result.append(binary);
        }
        return result.toString();
    }
    
    /**
     * Парсит Rate of Turn согласно стандарту AIS
     * ROTAIS = 4.733 SQRT(ROTINDICATED) degrees/min
     * Значения: 0-126 = поворот вправо, 127 = поворот влево >5°/30с, 128-255 = поворот влево
     */
    private double parseRateOfTurn(int rotRaw) {
        if (rotRaw == -128) {
            return Double.NaN; // Нет данных
        }
        if (rotRaw == -127) {
            return -720.0; // Влево > 708°/мин
        }
        if (rotRaw == 127) {
            return 720.0; // Вправо > 708°/мин
        }

        // В диапазоне -126..126
        double rot = rotRaw / 4.733;
        return Math.signum(rotRaw) * rot * rot;
    }
    
    /**
     * Парсит AIS координаты
     */
    private double parseAISCoordinate(String bits, int bitLength) {
        // Проверяем знаковый бит
        boolean isNegative = bits.charAt(0) == '1';
        
        // Преобразуем в беззнаковое число
        long value = Long.parseLong(bits, 2);
        
        if (bitLength == 27) {
            // Широта: 27 бит, диапазон -90 до +90
            if (isNegative) {
                // Для отрицательных чисел применяем дополнение до двух
                value = value - (1L << 27);
            }
            return value / 600000.0;
        } else {
            // Долгота: 28 бит, диапазон -180 до +180
            if (isNegative) {
                // Для отрицательных чисел применяем дополнение до двух
                value = value - (1L << 28);
            }
            return value / 600000.0;
        }
    }
    
    /**
     * Декодирует AIS строку согласно стандарту ITU-R M.1371-5, таблица 44
     * Простой switch case для всех 64 возможных значений 6-битной кодировки
     */
    private String decodeAISString(String bits) {
        StringBuilder result = new StringBuilder();
        
        Log.d(TAG, "Декодируем AIS строку, биты: " + bits + " (длина: " + bits.length() + ")");
    
        for (int i = 0; i + 6 <= bits.length(); i += 6) {
            String charBits = bits.substring(i, i + 6);
            int value = Integer.parseInt(charBits, 2);
    
            char decodedChar;
            // Простой switch case для всех 64 возможных значений
            switch (value) {
                case 0: decodedChar = ' '; break;
                case 1: decodedChar = 'A'; break;
                case 2: decodedChar = 'B'; break;
                case 3: decodedChar = 'C'; break;
                case 4: decodedChar = 'D'; break;
                case 5: decodedChar = 'E'; break;
                case 6: decodedChar = 'F'; break;
                case 7: decodedChar = 'G'; break;
                case 8: decodedChar = 'H'; break;
                case 9: decodedChar = 'I'; break;
                case 10: decodedChar = 'J'; break;
                case 11: decodedChar = 'K'; break;
                case 12: decodedChar = 'L'; break;
                case 13: decodedChar = 'M'; break;
                case 14: decodedChar = 'N'; break;
                case 15: decodedChar = 'O'; break;
                case 16: decodedChar = 'P'; break;
                case 17: decodedChar = 'Q'; break;
                case 18: decodedChar = 'R'; break;
                case 19: decodedChar = 'S'; break;
                case 20: decodedChar = 'T'; break;
                case 21: decodedChar = 'U'; break;
                case 22: decodedChar = 'V'; break;
                case 23: decodedChar = 'W'; break;
                case 24: decodedChar = 'X'; break;
                case 25: decodedChar = 'Y'; break;
                case 26: decodedChar = 'Z'; break;
                case 27: decodedChar = '0'; break;
                case 28: decodedChar = '1'; break;
                case 29: decodedChar = '2'; break;
                case 30: decodedChar = '3'; break;
                case 31: decodedChar = '4'; break;
                case 32: decodedChar = ' '; break; // пробел
                case 33: decodedChar = '5'; break;
                case 34: decodedChar = '6'; break;
                case 35: decodedChar = '7'; break;
                case 36: decodedChar = '8'; break;
                case 37: decodedChar = '9'; break;
                case 38: decodedChar = ' '; break; // пробел
                case 39: decodedChar = ' '; break; // пробел
                case 40: decodedChar = ' '; break; // пробел
                case 41: decodedChar = ' '; break; // пробел
                case 42: decodedChar = ' '; break; // пробел
                case 43: decodedChar = ' '; break; // пробел
                case 44: decodedChar = ' '; break; // пробел
                case 45: decodedChar = ' '; break; // пробел
                case 46: decodedChar = ' '; break; // пробел
                case 47: decodedChar = ' '; break; // пробел
                case 48: decodedChar = '0'; break; // пробел
                case 49: decodedChar = '1'; break; // пробел
                case 50: decodedChar = '2'; break; // пробел
                case 51: decodedChar = '3'; break; // пробел
                case 52: decodedChar = '4'; break; // пробел
                case 53: decodedChar = '5'; break; // пробел
                case 54: decodedChar = '6'; break; // пробел
                case 55: decodedChar = '7'; break; // пробел
                case 56: decodedChar = '8'; break; // пробел
                case 57: decodedChar = '9'; break; // пробел
                case 58: decodedChar = ' '; break; // пробел
                case 59: decodedChar = ' '; break; // пробел
                case 60: decodedChar = ' '; break; // пробел
                case 61: decodedChar = ' '; break; // пробел
                case 62: decodedChar = ' '; break; // пробел
                case 63: decodedChar = ' '; break; // пробел
                default: decodedChar = ' '; break; // на всякий случай
            }
            
            Log.d(TAG, "Символ " + (i/6 + 1) + ": биты=" + charBits + ", значение=" + value + ", символ='" + decodedChar + "'");
            result.append(decodedChar);
        }
        
        String resultStr = result.toString().trim();
        Log.d(TAG, "Результат декодирования: '" + resultStr + "'");
        return resultStr;
    }
    
    /**
     * Получает навигационный статус по коду
     */
    private String getNavigationStatus(int status) {
        switch (status) {
            case 0: return "Under way using engine";
            case 1: return "At anchor";
            case 2: return "Not under command";
            case 3: return "Restricted manoeuvrability";
            case 4: return "Constrained by her draught";
            case 5: return "Moored";
            case 6: return "Aground";
            case 7: return "Engaged in fishing";
            case 8: return "Under way sailing";
            case 9: return "Reserved";
            case 10: return "Reserved";
            case 11: return "Reserved";
            case 12: return "Reserved";
            case 13: return "Reserved";
            case 14: return "AIS-SART";
            case 15: return "Not defined";
            default: return "Unknown";
        }
    }
    
    /**
     * Получает описание типа электронного устройства позиционирования
     */
    private String getEPFDType(int epfdType) {
        switch (epfdType) {
            case 0: return "Undefined";
            case 1: return "GPS";
            case 2: return "GLONASS";
            case 3: return "Combined GPS/GLONASS";
            case 4: return "Loran-C";
            case 5: return "Chayka";
            case 6: return "Integrated navigation system";
            case 7: return "Surveyed";
            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15: return "Not used";
            default: return "Unknown";
        }
    }
    
    /**
     * Получает тип судна по коду согласно стандарту AIS
     */
    private String getVesselType(int typeCode) {
        switch (typeCode) {
            case 0: return "Not available";
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
            case 16:
            case 17:
            case 18:
            case 19: return "Reserved for future use";
            case 20: return "Wing in ground (WIG), all ships";
            case 21: return "Wing in ground (WIG), Hazardous category A";
            case 22: return "Wing in ground (WIG), Hazardous category B";
            case 23: return "Wing in ground (WIG), Hazardous category C";
            case 24: return "Wing in ground (WIG), Hazardous category D";
            case 25:
            case 26:
            case 27:
            case 28:
            case 29: return "Wing in ground (WIG), Reserved";
            case 30: return "Fishing";
            case 31: return "Towing";
            case 32: return "Towing: length exceeds 200m or breadth exceeds 25m";
            case 33: return "Dredging or underwater ops";
            case 34: return "Diving ops";
            case 35: return "Military ops";
            case 36: return "Sailing";
            case 37: return "Pleasure Craft";
            case 38:
            case 39: return "Reserved";
            case 40: return "High speed craft (HSC), all ships";
            case 41: return "High speed craft (HSC), Hazardous category A";
            case 42: return "High speed craft (HSC), Hazardous category B";
            case 43: return "High speed craft (HSC), Hazardous category C";
            case 44: return "High speed craft (HSC), Hazardous category D";
            case 45:
            case 46:
            case 47:
            case 48: return "High speed craft (HSC), Reserved";
            case 49: return "High speed craft (HSC), No additional information";
            case 50: return "Pilot Vessel";
            case 51: return "Search and Rescue vessel";
            case 52: return "Tug";
            case 53: return "Port Tender";
            case 54: return "Anti-pollution equipment";
            case 55: return "Law Enforcement";
            case 56:
            case 57: return "Spare - Local Vessel";
            case 58: return "Medical Transport";
            case 59: return "Noncombatant ship according to RR Resolution No. 18";
            case 60: return "Passenger, all ships";
            case 61: return "Passenger, Hazardous category A";
            case 62: return "Passenger, Hazardous category B";
            case 63: return "Passenger, Hazardous category C";
            case 64: return "Passenger, Hazardous category D";
            case 65:
            case 66:
            case 67:
            case 68: return "Passenger, Reserved";
            case 69: return "Passenger, No additional information";
            case 70: return "Cargo, all ships";
            case 71: return "Cargo, Hazardous category A";
            case 72: return "Cargo, Hazardous category B";
            case 73: return "Cargo, Hazardous category C";
            case 74: return "Cargo, Hazardous category D";
            case 75:
            case 76:
            case 77:
            case 78: return "Cargo, Reserved";
            case 79: return "Cargo, No additional information";
            case 80: return "Tanker, all ships";
            case 81: return "Tanker, Hazardous category A";
            case 82: return "Tanker, Hazardous category B";
            case 83: return "Tanker, Hazardous category C";
            case 84: return "Tanker, Hazardous category D";
            case 85:
            case 86:
            case 87:
            case 88: return "Tanker, Reserved";
            case 89: return "Tanker, No additional information";
            case 90: return "Other Type, all ships";
            case 91: return "Other Type, Hazardous category A";
            case 92: return "Other Type, Hazardous category B";
            case 93: return "Other Type, Hazardous category C";
            case 94: return "Other Type, Hazardous category D";
            case 95:
            case 96:
            case 97:
            case 98: return "Other Type, Reserved";
            case 99: return "Other Type, no additional information";
            default: return "Unknown";
        }
    }
    
    /**
     * Находит существующее AIS судно или создает новое
     */
    private AISVessel findOrCreateAISVessel(String mmsi) {
        for (AISVessel vessel : aisVessels) {
            if (mmsi.equals(vessel.getMmsi())) {
                return vessel;
            }
        }
        
        // Создаем новое судно
        AISVessel newVessel = new AISVessel(mmsi);
        aisVessels.add(newVessel);
        Log.d(TAG, "Создано новое AIS судно: " + mmsi);
        return newVessel;
    }
    
    /**
     * Очищает устаревшие AIS суда (данные старше 10 минут)
     */
    public void cleanupStaleAISVessels() {
        java.util.Iterator<AISVessel> iterator = aisVessels.iterator();
        int removedCount = 0;
        
        while (iterator.hasNext()) {
            AISVessel vessel = iterator.next();
            if (vessel.isDataStale()) {
                iterator.remove();
                removedCount++;
                Log.d(TAG, "Удалено устаревшее AIS судно: " + vessel.getMmsi());
            }
        }
        
        if (removedCount > 0) {
            Log.i(TAG, "Удалено " + removedCount + " устаревших AIS судов");
        }
    }
    
    /**
     * Получает количество активных AIS судов
     */
    public int getActiveAISVesselCount() {
        cleanupStaleAISVessels();
        return aisVessels.size();
    }
    
    /**
     * Получает AIS судно по MMSI
     */
    public AISVessel getAISVesselByMMSI(String mmsi) {
        for (AISVessel vessel : aisVessels) {
            if (mmsi.equals(vessel.getMmsi())) {
                return vessel;
            }
        }
        return null;
    }
    
    /**
     * Обновляет статус активности AIS судов
     */
    public void updateAISVesselActivity() {
        long currentTime = System.currentTimeMillis();
        for (AISVessel vessel : aisVessels) {
            // Считаем судно активным, если данные получены менее 5 минут назад
            boolean isActive = (currentTime - vessel.getLastUpdate().toInstant(java.time.ZoneOffset.UTC).toEpochMilli()) < 300000;
            vessel.setActive(isActive);
        }
    }

    /**
     * Парсит координаты из NMEA формата
     */
    private double parseCoordinate(String coordinate, boolean isPositive) {
        // Проверяем, что координата не пустая
        if (coordinate == null || coordinate.trim().isEmpty()) {
            return 0.0;
        }
        
        try {
            double value = Double.parseDouble(coordinate);
            int degrees = (int) (value / 100);
            double minutes = value - (degrees * 100);
            double result = degrees + (minutes / 60.0);
            return isPositive ? result : -result;
        } catch (NumberFormatException e) {
            Log.w(TAG, "Ошибка парсинга координаты: " + coordinate + ", ошибка: " + e.getMessage());
            return 0.0;
        }
    }
    
    /**
     * Проверяет контрольную сумму NMEA сообщения
     */
    public boolean validateChecksum(String nmeaSentence) {
        if (nmeaSentence == null || !nmeaSentence.contains("*")) {
            return false;
        }
        
        int asteriskIndex = nmeaSentence.indexOf('*');
        String sentence = nmeaSentence.substring(1, asteriskIndex);
        String checksum = nmeaSentence.substring(asteriskIndex + 1);
        
        int calculatedChecksum = 0;
        for (char c : sentence.toCharArray()) {
            calculatedChecksum ^= c;
        }
        
        String hexChecksum = String.format("%02X", calculatedChecksum);
        return hexChecksum.equals(checksum);
    }
    
    public Vessel getOwnVessel() {
        return ownVessel;
    }
    
    public List<AISVessel> getAISVessels() {
        return new ArrayList<>(aisVessels);
    }
    
    /**
     * Получает количество спутников GPS
     */
    public int getGPSSatellites() {
        return gpsSatellites;
    }
    
    /**
     * Получает количество спутников GLONASS
     */
    public int getGLONASSSatellites() {
        return glonassSatellites;
    }
    
    /**
     * Получает количество спутников Galileo
     */
    public int getGalileoSatellites() {
        return galileoSatellites;
    }
    
    /**
     * Получает общее количество спутников всех систем
     */
    public int getTotalSatellites() {
        return gpsSatellites + glonassSatellites + galileoSatellites;
    }
    
    /**
     * Сбрасывает счетчики спутников
     */
    public void resetSatelliteCounters() {
        gpsSatellites = 0;
        glonassSatellites = 0;
        galileoSatellites = 0;
        ownVessel.setSatellites(0);
        Log.d(TAG, "Счетчики спутников сброшены");
    }
    
    /**
     * Синхронизирует данные о спутниках с GPSLocationListener
     */
    public void syncSatelliteData() {
        if (gpsLocationListener != null) {
            gpsLocationListener.setSatellitesInVessel(ownVessel);
        }
    }
    
    /**
     * Получает текущее состояние объекта Vessel
     */
    public String getVesselStatus() {
        return String.format("Vessel: satellites=%d, activeSatellites=%d, GPS=%d, GLONASS=%d, Galileo=%d", 
            ownVessel.getSatellites(), ownVessel.getActiveSatellites(), 
            gpsSatellites, glonassSatellites, galileoSatellites);
    }
    
    /**
     * Декодирует AIS сообщение типа 4 (Base Station Report)
     */
    private void decodeBaseStationReport(String payload) {
        try {
            Log.d(TAG, "Декодируем Base Station Report, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Year (14 бит) - бит 38
            String yearBits = decodeAISField(payload, 38, 14);
            int year = Integer.parseInt(yearBits, 2);
            Log.d(TAG, "Year bits: " + yearBits + " = " + year);
            
            // Month (4 бита) - бит 52
            String monthBits = decodeAISField(payload, 52, 4);
            int month = Integer.parseInt(monthBits, 2);
            Log.d(TAG, "Month bits: " + monthBits + " = " + month);
            
            // Day (5 бит) - бит 56
            String dayBits = decodeAISField(payload, 56, 5);
            int day = Integer.parseInt(dayBits, 2);
            Log.d(TAG, "Day bits: " + dayBits + " = " + day);
            
            // Hour (5 бит) - бит 61
            String hourBits = decodeAISField(payload, 61, 5);
            int hour = Integer.parseInt(hourBits, 2);
            Log.d(TAG, "Hour bits: " + hourBits + " = " + hour);
            
            // Minute (6 бит) - бит 66
            String minuteBits = decodeAISField(payload, 66, 6);
            int minute = Integer.parseInt(minuteBits, 2);
            Log.d(TAG, "Minute bits: " + minuteBits + " = " + minute);
            
            // Second (6 бит) - бит 72
            String secondBits = decodeAISField(payload, 72, 6);
            int second = Integer.parseInt(secondBits, 2);
            Log.d(TAG, "Second bits: " + secondBits + " = " + second);
            
            // Position Accuracy (1 бит) - бит 78
            String accuracyBits = decodeAISField(payload, 78, 1);
            int accuracy = Integer.parseInt(accuracyBits, 2);
            Log.d(TAG, "Accuracy bits: " + accuracyBits + " = " + accuracy);
            
            // Longitude (28 бит) - бит 79
            String lonBits = decodeAISField(payload, 79, 28);
            double longitude = parseAISCoordinate(lonBits, 28);
            Log.d(TAG, "Longitude bits: " + lonBits + " = " + longitude);
            
            // Latitude (27 бит) - бит 107
            String latBits = decodeAISField(payload, 107, 27);
            double latitude = parseAISCoordinate(latBits, 27);
            Log.d(TAG, "Latitude bits: " + latBits + " = " + latitude);
            
            // EPFD Type (4 бита) - бит 134
            String epfdBits = decodeAISField(payload, 134, 4);
            int epfdType = Integer.parseInt(epfdBits, 2);
            Log.d(TAG, "EPFD Type bits: " + epfdBits + " = " + epfdType);
            
            Log.d(TAG, String.format("AIS Base Station: MMSI=%d, date=%04d-%02d-%02d %02d:%02d:%02d, lat=%.6f, lon=%.6f, accuracy=%d, epfd=%d", 
                mmsi, year, month, day, hour, minute, second, latitude, longitude, accuracy, epfdType));
            
            // Создаем или обновляем AIS судно (базовая станция)
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            vessel.updatePosition(latitude, longitude, 0.0, 0.0);
            vessel.setPositionAccuracy(accuracy == 1);
            vessel.setVesselClass("Base Station");
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Base Station Report: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 14 (Safety Related Broadcast Message)
     */
    private void decodeSafetyBroadcast(String payload) {
        try {
            Log.d(TAG, "Декодируем Safety Broadcast, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Spare (2 бита) - бит 38
            String spareBits = decodeAISField(payload, 38, 2);
            int spare = Integer.parseInt(spareBits, 2);
            Log.d(TAG, "Spare bits: " + spareBits + " = " + spare);
            
            // Text (120 бит) - бит 40
            String textBits = decodeAISField(payload, 40, 120);
            String safetyText = decodeAISString(textBits);
            Log.d(TAG, "Safety Text bits: " + textBits + " = '" + safetyText + "'");
            
            Log.d(TAG, String.format("AIS Safety Broadcast: MMSI=%d, text='%s'", mmsi, safetyText));
            // Отправляем лог наружу
            try {
                com.grigowashere.aismap.utils.LogSender.logShipUpdate(String.valueOf(mmsi), "Safety: " + safetyText);
            } catch (Throwable t) {
                Log.w(TAG, "Ошибка отправки safety-лога: " + t.getMessage());
            }
            
            // Создаем или обновляем AIS судно
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            vessel.setLastSafetyMessage(safetyText);
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Safety Broadcast: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 18 (Standard Class B Equipment Position Report)
     */
    private void decodeClassBPositionReport(String payload) {
        try {
            Log.d(TAG, "Декодируем Class B Position Report, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Speed Over Ground (10 бит) - бит 46
            String speedBits = decodeAISField(payload, 46, 10);
            double speed = Integer.parseInt(speedBits, 2) / 10.0;
            Log.d(TAG, "Speed bits: " + speedBits + " = " + speed);
            
            // Position Accuracy (1 бит) - бит 56
            String accuracyBits = decodeAISField(payload, 56, 1);
            int accuracy = Integer.parseInt(accuracyBits, 2);
            Log.d(TAG, "Accuracy bits: " + accuracyBits + " = " + accuracy);
            
            // Longitude (28 бит) - бит 57
            String lonBits = decodeAISField(payload, 57, 28);
            double longitude = parseAISCoordinate(lonBits, 28);
            Log.d(TAG, "Longitude bits: " + lonBits + " = " + longitude);
            
            // Latitude (27 бит) - бит 85
            String latBits = decodeAISField(payload, 85, 27);
            double latitude = parseAISCoordinate(latBits, 27);
            Log.d(TAG, "Latitude bits: " + latBits + " = " + latitude);
            
            // Course Over Ground (12 бит) - бит 112
            String courseBits = decodeAISField(payload, 112, 12);
            double course = Integer.parseInt(courseBits, 2) / 10.0;
            Log.d(TAG, "Course bits: " + courseBits + " = " + course);
            
            // True Heading (9 бит) - бит 124
            String headingBits = decodeAISField(payload, 124, 9);
            double heading = Integer.parseInt(headingBits, 2);
            Log.d(TAG, "Heading bits: " + headingBits + " = " + heading);
            
            // Time Stamp (6 бит) - бит 133
            String timestampBits = decodeAISField(payload, 133, 6);
            int timestamp = Integer.parseInt(timestampBits, 2);
            Log.d(TAG, "Timestamp bits: " + timestampBits + " = " + timestamp);
            
            // Regional Reserved (2 бита) - бит 139
            String regionalBits = decodeAISField(payload, 139, 2);
            int regional = Integer.parseInt(regionalBits, 2);
            Log.d(TAG, "Regional bits: " + regionalBits + " = " + regional);
            
            // Spare (3 бита) - бит 141
            String spareBits = decodeAISField(payload, 141, 3);
            int spare = Integer.parseInt(spareBits, 2);
            Log.d(TAG, "Spare bits: " + spareBits + " = " + spare);
            
            Log.d(TAG, String.format("AIS Class B Position: MMSI=%d, lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, heading=%.1f", 
                mmsi, latitude, longitude, course, speed, heading));
            
            // Создаем или обновляем AIS судно
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            // Логика приоритета классов:
            // - Если уже Class A: игнорируем обновление типа 18 полностью
            // - Если Extended Class B: обновляем только динамику (позиция, скорость, курс и т.п.), класс не меняем
            String existingClass = vessel.getVesselClass();
            if ("Class A".equals(existingClass)) {
                Log.d(TAG, "Пропускаем обновление Class B (тип 18) для судна класса Class A: " + mmsi);
                return;
            }
            boolean keepExtended = "Extended Class B".equals(existingClass);
            vessel.updatePosition(latitude, longitude, course, speed);
            vessel.setHeading(heading);
            vessel.setPositionAccuracy(accuracy == 1);
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            if (!keepExtended) {
                vessel.setVesselClass("Class B");
            }
            
            // В Class B Position Report размеры не передаются, но мы сохраняем существующие
            Log.d(TAG, "Class B Position Report - размеры не передаются, сохраняем существующие: L=" + vessel.getLength() + ", W=" + vessel.getWidth());
            
            // Отправляем информацию о корабле на внешний ресурс
            // Добавляем статические поля, если они уже известны (из сообщений 24 и др.)
            StringBuilder info = new StringBuilder(
                String.format(java.util.Locale.US,
                        "Class B: lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, heading=%.1f, accuracy=%s",
                        latitude, longitude, course, speed, heading, (accuracy == 1 ? "high" : "low"))
            );
            if (vessel.getVesselName() != null && !vessel.getVesselName().trim().isEmpty()) {
                info.append(String.format(java.util.Locale.US, ", name='%s'", vessel.getVesselName()));
            }
            if (vessel.getCallSign() != null && !vessel.getCallSign().trim().isEmpty()) {
                info.append(String.format(java.util.Locale.US, ", callSign='%s'", vessel.getCallSign()));
            }
            if (vessel.getVesselType() != null && !vessel.getVesselType().trim().isEmpty()) {
                info.append(String.format(java.util.Locale.US, ", type=%s", vessel.getVesselType()));
            }
            if (vessel.getLength() > 0 || vessel.getWidth() > 0) {
                info.append(String.format(java.util.Locale.US, ", L=%.1f, W=%.1f", vessel.getLength(), vessel.getWidth()));
            }
            if (vessel.getDraft() > 0) {
                info.append(String.format(java.util.Locale.US, ", D=%.1f", vessel.getDraft()));
            }
            if (vessel.getDestination() != null && !vessel.getDestination().trim().isEmpty()) {
                info.append(String.format(java.util.Locale.US, ", dest='%s'", vessel.getDestination()));
            }
            LogSender.logShipUpdate(String.valueOf(mmsi), info.toString());
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Class B Position Report: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 19 (Extended Class B Equipment Position Report)
     */
    private void decodeExtendedClassBPositionReport(String payload) {
        try {
            Log.d(TAG, "Декодируем Extended Class B Position Report, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // Проверяем длину payload - для Extended Class B должно быть достаточно битов
            int totalBits = payload.length() * 6;
            Log.d(TAG, "Общая длина payload в битах: " + totalBits);
            
            if (totalBits < 312) { // Минимум для Extended Class B
                Log.w(TAG, "Extended Class B payload слишком короткий: " + totalBits + " бит, ожидается минимум 312");
                return;
            }
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Speed Over Ground (10 бит) - бит 46
            String speedBits = decodeAISField(payload, 46, 10);
            double speed = Integer.parseInt(speedBits, 2) / 10.0;
            Log.d(TAG, "Speed bits: " + speedBits + " = " + speed);
            
            // Position Accuracy (1 бит) - бит 56
            String accuracyBits = decodeAISField(payload, 56, 1);
            int accuracy = Integer.parseInt(accuracyBits, 2);
            Log.d(TAG, "Accuracy bits: " + accuracyBits + " = " + accuracy);
            
            // Longitude (28 бит) - бит 57
            String lonBits = decodeAISField(payload, 57, 28);
            double longitude = parseAISCoordinate(lonBits, 28);
            Log.d(TAG, "Longitude bits: " + lonBits + " = " + longitude);
            
            // Latitude (27 бит) - бит 85
            String latBits = decodeAISField(payload, 85, 27);
            double latitude = parseAISCoordinate(latBits, 27);
            Log.d(TAG, "Latitude bits: " + latBits + " = " + latitude);
            
            // Course Over Ground (12 бит) - бит 112
            String courseBits = decodeAISField(payload, 112, 12);
            double course = Integer.parseInt(courseBits, 2) / 10.0;
            Log.d(TAG, "Course bits: " + courseBits + " = " + course);
            
            // True Heading (9 бит) - бит 124
            String headingBits = decodeAISField(payload, 124, 9);
            double heading = Integer.parseInt(headingBits, 2);
            Log.d(TAG, "Heading bits: " + headingBits + " = " + heading);
            
            // Time Stamp (6 бит) - бит 133
            String timestampBits = decodeAISField(payload, 133, 6);
            int timestamp = Integer.parseInt(timestampBits, 2);
            Log.d(TAG, "Timestamp bits: " + timestampBits + " = " + timestamp);
            
            // Regional Reserved (4 бита) - бит 139
            String regionalBits = decodeAISField(payload, 139, 4);
            int regional = Integer.parseInt(regionalBits, 2);
            Log.d(TAG, "Regional bits: " + regionalBits + " = " + regional);
            
            // Vessel Name (120 бит) - бит 143
            String nameBits = decodeAISField(payload, 143, 120);
            String vesselName = decodeAISString(nameBits);
            Log.d(TAG, "Name bits: " + nameBits + " = '" + vesselName + "'");
            
            // Ship Type (8 бит) - бит 263
            String typeBits = decodeAISField(payload, 263, 8);
            int vesselTypeCode = Integer.parseInt(typeBits, 2);
            Log.d(TAG, "Type bits: " + typeBits + " = " + vesselTypeCode);
            
            // Dimension Reference (4 бита) - бит 271
            String dimRefABits = decodeAISField(payload, 271, 4);
            String dimRefBBits = decodeAISField(payload, 275, 4);
            String dimRefCBits = decodeAISField(payload, 279, 4);
            String dimRefDBits = decodeAISField(payload, 283, 4);
            
            int dimRefA = Integer.parseInt(dimRefABits, 2);
            int dimRefB = Integer.parseInt(dimRefBBits, 2);
            int dimRefC = Integer.parseInt(dimRefCBits, 2);
            int dimRefD = Integer.parseInt(dimRefDBits, 2);
            
            Log.d(TAG, "Dimension Reference: A=" + dimRefA + ", B=" + dimRefB + ", C=" + dimRefC + ", D=" + dimRefD);
            
            // Vessel Dimensions (40 бит) - начинаются с бита 287
            // Проверяем, есть ли достаточно битов для размеров
            if (totalBits < 327) {
                Log.w(TAG, "Extended Class B - недостаточно битов для размеров: " + totalBits + " < 327");
                // Создаем судно без размеров
                AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
                // Если судно уже Class A, не перезаписываем данными Extended Class B
                String existingClassShort = vessel.getVesselClass();
                if ("Class A".equals(existingClassShort)) {
                    Log.d(TAG, "Пропускаем обновление Extended Class B для судна класса Class A: " + mmsi);
                    return;
                }
                vessel.updatePosition(latitude, longitude, course, speed);
                vessel.setHeading(heading);
                vessel.setPositionAccuracy(accuracy == 1);
                vessel.setVesselName(vesselName);
                vessel.setVesselType(getVesselType(vesselTypeCode));
                vessel.setLastUpdate(java.time.LocalDateTime.now());
                vessel.setVesselClass("Extended Class B");
                
                if (listener != null) {
                    listener.onAISVesselUpdated(vessel);
                }
                // Логируем короткое сообщение типа 19 с доступными данными
                StringBuilder shortInfo = new StringBuilder(
                    String.format(java.util.Locale.US,
                        "Extended Class B (short): name='%s', lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, heading=%.1f, accuracy=%s",
                        vesselName, latitude, longitude, course, speed, heading, (accuracy == 1 ? "high" : "low"))
                );
                if (vessel.getCallSign() != null && !vessel.getCallSign().trim().isEmpty()) {
                    shortInfo.append(String.format(java.util.Locale.US, ", callSign='%s'", vessel.getCallSign()));
                }
                if (vessel.getDestination() != null && !vessel.getDestination().trim().isEmpty()) {
                    shortInfo.append(String.format(java.util.Locale.US, ", dest='%s'", vessel.getDestination()));
                }
                LogSender.logShipUpdate(String.valueOf(mmsi), shortInfo.toString());
                return;
            }
            
            // Dim.A (10 бит) - от носа до антенны
            String dimABits = decodeAISField(payload, 287, 10);
            // Dim.B (10 бит) - от антенны до кормы  
            String dimBBits = decodeAISField(payload, 297, 10);
            // Dim.C (10 бит) - от левого борта до антенны
            String dimCBits = decodeAISField(payload, 307, 10);
            // Dim.D (10 бит) - от антенны до правого борта
            String dimDBits = decodeAISField(payload, 317, 10);
            
            Log.d(TAG, "Raw dimension bits - Dim.A: " + dimABits + ", Dim.B: " + dimBBits + ", Dim.C: " + dimCBits + ", Dim.D: " + dimDBits);
            
            int dimA = Integer.parseInt(dimABits, 2);
            int dimB = Integer.parseInt(dimBBits, 2);
            int dimC = Integer.parseInt(dimCBits, 2);
            int dimD = Integer.parseInt(dimDBits, 2);
            
            // В AIS стандарте размеры кодируются как 6-битные значения:
            // 0 = не указано, 1-62 = размер в метрах, 63 = размер 63+ метра
            // Но мы получаем 10-битные значения, поэтому нужно их правильно интерпретировать
            
            // Проверяем, что размеры в разумных пределах (0-1000 метров)
            if (dimA > 1000 || dimB > 1000 || dimC > 1000 || dimD > 1000) {
                Log.w(TAG, "Размеры судна выходят за разумные пределы: A=" + dimA + ", B=" + dimB + ", C=" + dimC + ", D=" + dimD);
                // Возможно, мы неправильно интерпретируем битовые поля
                // Попробуем интерпретировать как 6-битные значения
                dimA = dimA & 0x3F; // Берем только младшие 6 бит
                dimB = dimB & 0x3F;
                dimC = dimC & 0x3F;
                dimD = dimD & 0x3F;
                Log.d(TAG, "Исправленные размеры (6-битные): A=" + dimA + ", B=" + dimB + ", C=" + dimC + ", D=" + dimD);
            }
            
            // Дополнительная проверка: если размеры все еще неразумные, используем Dimension Reference
            if (dimA > 100 || dimB > 100 || dimC > 100 || dimD > 100) {
                Log.w(TAG, "Размеры все еще неразумные, используем Dimension Reference: A=" + dimA + ", B=" + dimB + ", C=" + dimC + ", D=" + dimD);
                // Используем Dimension Reference как fallback
                dimA = dimRefA;
                dimB = dimRefB;
                dimC = dimRefC;
                dimD = dimRefD;
                Log.d(TAG, "Fallback размеры из Dimension Reference: A=" + dimA + ", B=" + dimB + ", C=" + dimC + ", D=" + dimD);
            }
            
            // Размеры судна рассчитываются как:
            // Длина = Dim.A + Dim.B (от носа до антенны + от антенны до кормы)
            // Ширина = Dim.C + Dim.D (от левого борта до антенны + от антенны до правого борта)
            double length = dimA + dimB;
            double width = dimC + dimD;
            
            Log.d(TAG, "Dimensions - Dim.A (нос-антенна): " + dimABits + " = " + dimA);
            Log.d(TAG, "Dimensions - Dim.B (антенна-корма): " + dimBBits + " = " + dimB);
            Log.d(TAG, "Dimensions - Dim.C (левый борт-антенна): " + dimCBits + " = " + dimC);
            Log.d(TAG, "Dimensions - Dim.D (антенна-правый борт): " + dimDBits + " = " + dimD);
            Log.d(TAG, "Dimensions - Total Length (A+B): " + length + "m");
            Log.d(TAG, "Dimensions - Total Width (C+D): " + width + "m");
            
            Log.d(TAG, String.format("AIS Extended Class B: MMSI=%d, name='%s', lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, type=%d, L=%.1f, W=%.1f", 
                mmsi, vesselName, latitude, longitude, course, speed, vesselTypeCode, length, width));
            
            // Создаем или обновляем AIS судно
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            // Если судно уже Class A, не перезаписываем данными Extended Class B
            String existingClassFull = vessel.getVesselClass();
            if ("Class A".equals(existingClassFull)) {
                Log.d(TAG, "Пропускаем обновление Extended Class B для судна класса Class A: " + mmsi);
                return;
            }
            vessel.updatePosition(latitude, longitude, course, speed);
            vessel.setHeading(heading);
            vessel.setPositionAccuracy(accuracy == 1);
            vessel.setVesselName(vesselName);
            vessel.setVesselType(getVesselType(vesselTypeCode));
            vessel.setLength(length);
            vessel.setWidth(width);
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            vessel.setVesselClass("Extended Class B");
            
            // Отправляем информацию о корабле на внешний ресурс
            String vesselInfo = String.format("Extended Class B: name='%s', lat=%.6f, lon=%.6f, course=%.1f, speed=%.1f, type=%s, L=%.1f, W=%.1f", 
                vesselName, latitude, longitude, course, speed, getVesselType(vesselTypeCode), length, width);
            LogSender.logShipUpdate(String.valueOf(mmsi), vesselInfo);
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Extended Class B Position Report: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 21 (Aid-to-Navigation Report)
     */
    private void decodeAidToNavigationReport(String payload) {
        try {
            Log.d(TAG, "Декодируем Aid-to-Navigation Report, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Aid Type (5 бит) - бит 38
            String aidTypeBits = decodeAISField(payload, 38, 5);
            int aidType = Integer.parseInt(aidTypeBits, 2);
            Log.d(TAG, "Aid Type bits: " + aidTypeBits + " = " + aidType);
            
            // Name (120 бит) - бит 43
            String nameBits = decodeAISField(payload, 43, 120);
            String aidName = decodeAISString(nameBits);
            Log.d(TAG, "Name bits: " + nameBits + " = '" + aidName + "'");
            
            // Position Accuracy (1 бит) - бит 163
            String accuracyBits = decodeAISField(payload, 163, 1);
            int accuracy = Integer.parseInt(accuracyBits, 2);
            Log.d(TAG, "Accuracy bits: " + accuracyBits + " = " + accuracy);
            
            // Longitude (28 бит) - бит 164
            String lonBits = decodeAISField(payload, 164, 28);
            double longitude = parseAISCoordinate(lonBits, 28);
            Log.d(TAG, "Longitude bits: " + lonBits + " = " + longitude);
            
            // Latitude (27 бит) - бит 192
            String latBits = decodeAISField(payload, 192, 27);
            double latitude = parseAISCoordinate(latBits, 27);
            Log.d(TAG, "Latitude bits: " + latBits + " = " + latitude);
            
            // Dimension Reference (4 бита) - бит 219
            String dimRefABits = decodeAISField(payload, 219, 4);
            String dimRefBBits = decodeAISField(payload, 223, 4);
            String dimRefCBits = decodeAISField(payload, 227, 4);
            String dimRefDBits = decodeAISField(payload, 231, 4);
            
            int dimRefA = Integer.parseInt(dimRefABits, 2);
            int dimRefB = Integer.parseInt(dimRefBBits, 2);
            int dimRefC = Integer.parseInt(dimRefCBits, 2);
            int dimRefD = Integer.parseInt(dimRefDBits, 2);
            
            // Vessel Dimensions (30 бит) - бит 235
            // Dim.A (10 бит) - от носа до антенны
            String dimABits = decodeAISField(payload, 235, 10);
            // Dim.B (10 бит) - от антенны до кормы  
            String dimBBits = decodeAISField(payload, 245, 10);
            // Dim.C (10 бит) - от левого борта до антенны
            String dimCBits = decodeAISField(payload, 255, 10);
            // Dim.D (10 бит) - от антенны до правого борта
            String dimDBits = decodeAISField(payload, 265, 10);
            // Draft (8 бит) - осадка
            String draftBits = decodeAISField(payload, 275, 8);
            
            int dimA = Integer.parseInt(dimABits, 2);
            int dimB = Integer.parseInt(dimBBits, 2);
            int dimC = Integer.parseInt(dimCBits, 2);
            int dimD = Integer.parseInt(dimDBits, 2);
            
            // Размеры судна рассчитываются как:
            // Длина = Dim.A + Dim.B (от носа до антенны + от антенны до кормы)
            // Ширина = Dim.C + Dim.D (от левого борта до антенны + от антенны до правого борта)
            double length = dimA + dimB;
            double width = dimC + dimD;
            double draft = Integer.parseInt(draftBits, 2) / 10.0;
            
            Log.d(TAG, String.format("AIS Aid-to-Navigation: MMSI=%d, type=%d, name='%s', lat=%.6f, lon=%.6f, L=%.1f, W=%.1f, D=%.1f", 
                mmsi, aidType, aidName, latitude, longitude, length, width, draft));
            
            // Создаем или обновляем AIS судно (навигационный знак)
            AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
            vessel.updatePosition(latitude, longitude, 0.0, 0.0);
            vessel.setPositionAccuracy(accuracy == 1);
            vessel.setVesselName(aidName);
            vessel.setVesselType("Aid-to-Navigation");
            vessel.setLength(length);
            vessel.setWidth(width);
            vessel.setDraft(draft);
            vessel.setLastUpdate(java.time.LocalDateTime.now());
            vessel.setVesselClass("Navigation Aid");
            
            // Уведомляем слушателя
            if (listener != null) {
                listener.onAISVesselUpdated(vessel);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Aid-to-Navigation Report: " + e.getMessage(), e);
        }
    }
    
    /**
     * Декодирует AIS сообщение типа 24 (Static Data Report)
     */
    private void decodeStaticDataReport(String payload) {
        try {
            Log.d(TAG, "Декодируем Static Data Report, payload: " + payload + " (длина: " + payload.length() + ")");
            
            // MMSI (30 бит) - начинается с бита 8
            String mmsiBits = decodeAISField(payload, 8, 30);
            int mmsi = Integer.parseInt(mmsiBits, 2);
            Log.d(TAG, "MMSI bits: " + mmsiBits + " = " + mmsi);
            
            // Part Number (2 бита) - бит 38
            String partBits = decodeAISField(payload, 38, 2);
            int partNumber = Integer.parseInt(partBits, 2);
            Log.d(TAG, "Part Number bits: " + partBits + " = " + partNumber);
            
            if (partNumber == 0) {
                // Part A: Vessel Name
                String nameBits = decodeAISField(payload, 40, 120);
                String vesselName = decodeAISString(nameBits);
                Log.d(TAG, "Vessel Name bits: " + nameBits + " = '" + vesselName + "'");
                
                Log.d(TAG, String.format("AIS Static Data Part A: MMSI=%d, name='%s'", mmsi, vesselName));
                
                // Обновляем AIS судно
                AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
                vessel.setVesselName(vesselName);
                vessel.setLastUpdate(java.time.LocalDateTime.now());
                
                if (listener != null) {
                    listener.onAISVesselUpdated(vessel);
                }

                // Логируем статические данные Class B (Part A)
                String vesselInfo = String.format(java.util.Locale.US,
                        "Class B Static A: name='%s'",
                        vesselName);
                LogSender.logShipUpdate(String.valueOf(mmsi), vesselInfo);
                
            } else if (partNumber == 1) {
                // Part B: Vessel Type, Dimensions, etc.
                String typeBits = decodeAISField(payload, 40, 8);
                int vesselTypeCode = Integer.parseInt(typeBits, 2);
                Log.d(TAG, "Vessel Type bits: " + typeBits + " = " + vesselTypeCode);
                
                // Vendor ID (42 бита) - бит 48
                String vendorBits = decodeAISField(payload, 48, 42);
                String vendorId = decodeAISString(vendorBits);
                Log.d(TAG, "Vendor ID bits: " + vendorBits + " = '" + vendorId + "'");
                
                // Call Sign (42 бита) - бит 90
                String callSignBits = decodeAISField(payload, 90, 42);
                String callSign = decodeAISString(callSignBits);
                Log.d(TAG, "Call Sign bits: " + callSignBits + " = '" + callSign + "'");
                
                // Dimension Reference (6 бит каждое) - бит 132
                // Согласно онлайн декодеру, размеры находятся в других позициях
                // Попробуем позиции, которые соответствуют онлайн декодеру
                String dimRefABits = decodeAISField(payload, 132, 9);
                String dimRefBBits = decodeAISField(payload, 141, 9);
                String dimRefCBits = decodeAISField(payload, 150, 6);
                String dimRefDBits = decodeAISField(payload, 156, 6);
                
                int dimRefA = Integer.parseInt(dimRefABits, 2);
                int dimRefB = Integer.parseInt(dimRefBBits, 2);
                int dimRefC = Integer.parseInt(dimRefCBits, 2);
                int dimRefD = Integer.parseInt(dimRefDBits, 2);
                
                Log.d(TAG, "Dimension Reference bits - A: " + dimRefABits + " = " + dimRefA);
                Log.d(TAG, "Dimension Reference bits - B: " + dimRefBBits + " = " + dimRefB);
                Log.d(TAG, "Dimension Reference bits - C: " + dimRefCBits + " = " + dimRefC);
                Log.d(TAG, "Dimension Reference bits - D: " + dimRefDBits + " = " + dimRefD);
                
                // Проверяем, есть ли достаточно битов для размеров
                int totalBits = payload.length() * 6;
                Log.d(TAG, "Static Data Part B - общая длина payload в битах: " + totalBits);
                
                double length = 0.0;
                double width = 0.0;
                double draft = 0.0;
                
                // Для коротких сообщений типа 24 Part B (168 бит) используем Dimension Reference
                // В коротких сообщениях размеры кодируются в Dimension Reference полях
                if (totalBits >= 168) {
                    // В сообщениях типа 24 Part B для Class B судов
                    // размеры кодируются в полях Dimension Reference (биты 132-147)
                    // где каждое поле - 4 бита и представляет размер в метрах
                    // Эти поля уже правильно декодированы выше
                    
                    // Размеры судна рассчитываются как:
                    // Длина = Dim.A + Dim.B (от носа до антенны + от антенны до кормы)
                    // Ширина = Dim.C + Dim.D (от левого борта до антенны + от антенны до правого борта)
                    length = dimRefA + dimRefB;
                    width = dimRefC + dimRefD;
                    
                    Log.d(TAG, "Static Data Part B - используем Dimension Reference:");
                    Log.d(TAG, "  Dim.A (нос-антенна): " + dimRefA + " м");
                    Log.d(TAG, "  Dim.B (антенна-корма): " + dimRefB + " м");
                    Log.d(TAG, "  Dim.C (левый борт-антенна): " + dimRefC + " м");
                    Log.d(TAG, "  Dim.D (антенна-правый борт): " + dimRefD + " м");
                    Log.d(TAG, "Static Data Part B - итоговые размеры: L=" + length + ", W=" + width);
                    
                } else {
                    Log.w(TAG, "Static Data Part B - недостаточно битов для размеров: " + totalBits + " < 168");
                    // Используем нулевые размеры
                    length = 0.0;
                    width = 0.0;
                }
                
                Log.d(TAG, String.format("AIS Static Data Part B: MMSI=%d, type=%d, vendor='%s', callSign='%s', L=%.1f, W=%.1f, D=%.1f", 
                    mmsi, vesselTypeCode, vendorId, callSign, length, width, draft));
                
                // Обновляем AIS судно
                AISVessel vessel = findOrCreateAISVessel(String.valueOf(mmsi));
                vessel.setVesselType(getVesselType(vesselTypeCode));
                vessel.setVendorId(vendorId);
                vessel.setCallSign(callSign);
                vessel.setLength(length);
                vessel.setWidth(width);
                vessel.setDraft(draft);
                vessel.setLastUpdate(java.time.LocalDateTime.now());
                
                if (listener != null) {
                    listener.onAISVesselUpdated(vessel);
                }

                // Логируем статические данные Class B (Part B)
                String vesselInfoB = String.format(java.util.Locale.US,
                        "Class B Static B: name='%s', callSign='%s', type=%s, L=%.1f, W=%.1f, D=%.1f",
                        vessel.getVesselName() != null ? vessel.getVesselName() : "",
                        callSign,
                        getVesselType(vesselTypeCode),
                        length, width, draft);
                LogSender.logShipUpdate(String.valueOf(mmsi), vesselInfoB);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка декодирования Static Data Report: " + e.getMessage(), e);
        }
    }
}
