package ru.bitel.bgbilling.modules.voice.dyn.mediator.huawei;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ru.bitel.bgbilling.apps.voice.accounting.mediation.AbstractMediator;
import ru.bitel.bgbilling.apps.voice.accounting.mediation.VoiceRecord;
import ru.bitel.bgbilling.apps.voice.accounting.mediation.VoiceRecordProcessor;
import ru.bitel.bgbilling.kernel.base.server.logger.BGLogger;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;

public class CC08Mediator
    extends AbstractMediator
{
    @Override
    public void readHourDataLog( final VoiceRecordProcessor processor, Date hour )
        throws Exception
    {
        LocalDateTime time = TimeUtils.convertDateToLocalDateTime( hour );
        // предобработка логов
        // берем все новые файлы логов и делим по часам + копии перемещаем в папки по часам
        // исходный файл перемещаем в папку обработаные

        preloadLocal( time );
        
        Path logPath = Paths.get( device.getLogPath() );
        getLogger().debug( "logPath => " + logPath.toString() );
        if ( Files.notExists( logPath ) )
        {
            Files.createDirectories( logPath );
        }
        
        // день + час за который надо обработать логи
        String prefix = time.format( DateTimeFormatter.ofPattern( "yyyyMMddHH" ) );
        
        Path dayLogPath = Paths.get( logPath.toString(), prefix.substring( 0, 4 ), prefix.substring( 4, 6 ), prefix.substring( 6, 8 ) );
        getLogger().debug( "dayLogPath => " + dayLogPath.toString() );
        if ( Files.notExists( dayLogPath ) )
        {
            Files.createDirectories( dayLogPath );
        }

        Path hourLogPath = Paths.get( dayLogPath.toString(), prefix.substring( 8 ) );
        getLogger().debug( "hourLogPath => " + hourLogPath.toString() );
        if ( Files.exists( hourLogPath ) )
        {
            Files.lines( hourLogPath ).forEach( line ->
            {
                try 
                {
                    processLine( processor, line.split( "\t" ) );    
                }
                catch( Exception ex )
                {
                    logError( ex );
                }
            } );
        }
    }

    protected VoiceRecord processLine( final VoiceRecordProcessor processor, final String[] params )
        throws InterruptedException
    {
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "processLine => " + params[0] + " " + params[1] + " " + params[2] + " " + params[3] + " " + params[4] + " " + params[5]  );
        }
        
        VoiceRecord record = processor.next();
        record.sessionStart = TimeUtils.convertLocalDateTimeToDate( LocalDateTime.parse( params[0] ) );
        record.callingStationId = params[1].replaceAll( "[^0-9]*", "" );
        record.e164CallingStationId = callingStationIdToE164( record.callingStationId );
        record.calledStationId = params[2].replaceAll( "[^0-9]*", "" );
        record.e164CalledStationId = calledStationIdToE164( record.calledStationId );
        record.duration = record.connectionDuration = Utils.parseInt( params[3], 0 );
        record.trunkIncoming = params[4];
        record.trunkOutgoing = params[5];
        record.category = 0;
        
        return record;
    }
    
    private void preloadLocal( final LocalDateTime hour )
    {
        try
        {
            Path logPath = Paths.get( device.getLogPath() );
            getLogger().debug( "logPath => " + logPath.toString() );
            if ( Files.notExists( logPath ) )
            {
                Files.createDirectories( logPath );
            }
            
            Path inboxLogDir = Paths.get( logPath.toString(), "inbox" );
            getLogger().debug( "inboxLogDir => " + inboxLogDir.toString() );
            Files.createDirectories( inboxLogDir );
    
            Path processedDir = Paths.get( logPath.toString(), "processed" );
            getLogger().debug( "processedDir => " + processedDir.toString() );
            Files.createDirectories( processedDir );
            
            Files.walk( inboxLogDir ).forEach( file ->
            {
                getLogger().debug( "file => " + file.getFileName() );
                
                if ( Files.isDirectory( file ) )
                {
                    return;
                }

                if ( file.getFileName().startsWith( "." ) )
                {
                    return;
                }

                Map<String, List<String>> cdrs = new HashMap<>();
                // читаем файл и раскладываем записи по часовым файлам
                parseBil( file, cdrs );
                
                try
                {
                    for ( String key : cdrs.keySet() )
                    {
                        Path dayPath = Paths.get( logPath.toString(), key.substring( 0, 4 ), key.substring( 4, 6 ), key.substring( 6, 8 ) );
                        Files.createDirectories( dayPath );
                        Path cdrPath = Paths.get( dayPath.toString(), key.substring( 8 ) );
                        Set<String> cdrSet = new LinkedHashSet<>();
                        if ( Files.exists( cdrPath ) )
                        {
                            for ( String cdr : Files.readAllLines( cdrPath ) )
                            {
                                cdrSet.add( cdr );
                            }
                        }
                        cdrSet.addAll( cdrs.get( key ) );
                        //
                        StringBuffer buffer = new StringBuffer();
                        for ( String s : cdrSet )
                        {
                            buffer.append( s ).append( "\n" );
                        }
                        Files.writeString( cdrPath, buffer.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE );
                    }

                    // перемещаем исходный файл в папку обработанные
                    // 2023030312.bil
                    String fileName = file.getFileName().toString();
                    Path outboxPath = Paths.get( processedDir.toString(), fileName.substring( 0, 4 ), fileName.substring( 4, 6 ), fileName.substring( 6, 8 ) );
                    Files.createDirectories( outboxPath );
                    Files.move( file, Paths.get( outboxPath.toString(), fileName ) );
                }
                catch( IOException ex )
                {
                    logError( ex );
                }
            } );
        }
        catch( IOException ex )
        {
            logError( ex );
        }
    }
    
    private void parseBil( Path path, Map<String, List<String>> cdrs )
    {
        if ( Files.exists( path ) )
        {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHH");
            
            try
            {
                ByteBuffer buffer = ByteBuffer.wrap( Files.readAllBytes( path ) );
                byte[] bilData = new byte[154];
                while( buffer.position() < buffer.capacity() )
                {
                    buffer.get( bilData );
                    ByteBuffer billBuffer = ByteBuffer.wrap( bilData );
                    billBuffer.order( ByteOrder.LITTLE_ENDIAN );

                    CDR cdr = new CDR();

                    // 1 Порядковый номер (4)
                    // long f1 = billBuffer.getInt( 0 ) & 0xFFFFFFFFL;

                    // 2 Тип счета (1)
                    //    0x01: Подробный счет R002MML
                    //    0x02: Запись вызова DBO 
                    //    0x03: Запись вызова IN 
                    //    0x05: Запись вызова TAX
                    //    0x11: Подробный счет R002MML128
                    //    0xF0: Счет таблицы счетчика           
                    //    0xF1: Статистика таблицы счетчика
                    //    0xF2 (242): Статистический счет по длительности занятия соединительной линии
                    //    0xF3 (243): Статистика бесплатных вызовов
                    //    0xF4: Счет таблицы счетчика SCCP 
                    //    0xFF: Неверный счет
                    //    0x55: Счет неуспешного вызова (Счет вызова, рассматриваемого как незавершённый, счет неполной записи)
                    int f2 = billBuffer.get( 4 ) & 0xFF;
                    if ( f2 != 1 ) continue;
                    // if ( f2 != 1 ) System.out.println( f2 );
                    
                    // 3 Контрольная сумма (1)
                    // int f3 = billBuffer.get() & 0xFF;
//                    billBuffer.position( billBuffer.position() + 1 );
                    
                    // 4 Индикатор неполной записи (0.25) 0: Отдельная запись 1: Неполная (частичная) запись
                    // 5 Флаг сдвига времени (0.125) 0: Да; 1: Нет
                    // 6 Флаг бесплатного счета (0.125) 0: Бесплатный; 1: С оплатой
                    // 7 Достоверность (0.125) 0: Действительный; 1: Недействительный
                    // 8 Флаг попытки вызова (0.125) 0: Попытка вызова бесплатна; 1: Попытка вызова оплачивается (по умолчанию)
                    // 9 Флаг жалобы (0.125) 0: Нет жалобы; 1: Жалоба
                    // 10 Флаг централизованной тарификации (0.125) 0: децентрализованный учёт стоимости; 1: централизованный учёт стоимости
                    // int f4_10 = billBuffer.get() & 0xFF;
//                    billBuffer.position( billBuffer.position() + 1 );
                    
                    // 11 Флаг PPS (0.125) 0: Вызов не PPS; 1: Вызов PPS
                    // 12 Метод тарификации (0.25) 0: Таблица счетчиков; 1: Подробный счет
                    // 13 Флаг вызова NP (0.125) 0: Вызов не NP; 1: Вызов NP
                    // 14 Плательщик (0.5) 
                    //    1: оплата вызывающим абонентом
                    //    2: оплата вызываемым абонентом
                    //    3: оплата номером пункта назначения (для IN)
                    //    4: оплата третьей стороной, классифицируется соответственно на случаи: 11, 12, 13, и 14
                    //    9: оплата входящей СЛ
                    //    10: оплата исходящей СЛ
                    //    11: оплата вызывающей стороной (платит третья сторона)
                    //    12: оплата вызываемой стороной (платит третья сторона)
                    //    13: оплата входящей СЛ (платит третья сторона)
                    //    14: оплата исходящей СЛ (платит третья сторона)
                    // int f11_14 = billBuffer.get() & 0xFF;
                    billBuffer.position( 8 );                    

                    // 15 Время окончания разговора (6) Формат: ГГММДДЧЧММСС
                    int f15_yy = billBuffer.get() & 0xFF;
                    int f15_mm = billBuffer.get() & 0xFF;
                    int f15_dd = billBuffer.get() & 0xFF;
                    int f15_hh = billBuffer.get() & 0xFF;
                    int f15_MM = billBuffer.get() & 0xFF;
                    int f15_ss = billBuffer.get() & 0xFF;
                    cdr.start = LocalDateTime.of( 2000 + f15_yy, f15_mm, f15_dd, f15_hh, f15_MM, f15_ss );

                    // 16 Длительность разговора (4) длинное целое, Единица измерения: 10 мс
                    long f16 = billBuffer.getInt() & 0xFFFFFFFFL;
                    cdr.amount = BigDecimal.valueOf( f16 ).divide( BigDecimal.valueOf( 100 ) ).setScale( 0, RoundingMode.HALF_UP ).intValue();
                    cdr.start = cdr.start.minusSeconds( cdr.amount );
                    
                    // 17 Длительность занятия вызывающего абонента (4) длинное целое, Единица измерения: 10 мс
                    // 18 Длительность занятия вызываемого абонента (4) длинное целое, Единица измерения: 10 мс
                    billBuffer.position( billBuffer.position() + 8 );
                    // 19 Контроль незавершённого вызова (0.25) 0: не контролируется; 1: контроль вызывающего абонента; 2: контроль вызываемого абонента; 3: контроль вызывающего и вызываемого абонентов
                    // 20 Доступ ISDN вызывающего абонента (0.125) 0: Доступ терминала не является ISDN; 2: Доступ терминала - ISDN
                    // 21 Доступ ISDN вызываемого абонента (0.125) 0: Доступ терминала не является ISDN; 2: Доступ терминала - ISDN
                    // 22 Индикатор ISUP (0.125) 1: Постоянно ISUP; 0: ISUP непостоянно 
                    // 23 Резервное поле (0.375)
                    billBuffer.position( billBuffer.position() + 1 );
                    // 24 Тип адреса оплачивающего номера (0.5)
                    //    0: абонентский номер
                    //    1: зарезервировано
                    //    2: значащий национальный номер (оплачивающий номер = код зоны + местный номер)
                    //    3: международный номер (оплачивающий номер = код государства + код зоны + местный номер)
                    //    4: расчётная карта типа А
                    //    5: расчётная карта типа B
                    //    6: расчётная карта типа C
                    //    7: расчётная карта типа D
                    //    8: расчётная карта VISA
                    //    9: номер группы CTX
                    //    10: номер внутреннего аппарата CTX
                    //    11: Банк 1
                    //    12: Банк 2
                    //    13: Банк 3
                    //    14: Банк 4
                    //    15: зарезервировано
                    // 25 Тип адреса номера вызывающего абонента (0.5) 
                    //    0: Абонентский номер (Номер вызывающего абонента = местный номер)
                    //    2: Национальный значащий номер (Номер вызывающего абонента = код зоны + местный номер)
                    //    3: Международный номер (Номер вызывающего абонента = код государства + код зоны + местный номер)
                    // 26 Тип адреса подключенного номера (0.5)
                    //    0: Абонентский номер (Номер вызывающего абонента = местный номер)
                    //    2: Национальный значащий номер (Номер вызывающего абонента = код зоны + местный номер)
                    //    3: Международный номер (Номер вызывающего абонента = код государства + код зоны + местный номер)                    
                    // 27 Тип адреса вызываемого номера (0.5)
                    //    0: Абонентский номер (Номер вызывающего абонента = местный номер)
                    //    2: Национальный значащий номер (Номер вызывающего абонента = код зоны + местный номер)
                    //    3: Международный номер (Номер вызывающего абонента = код государства + код зоны + местный номер)
                    billBuffer.position( billBuffer.position() + 2 );
                    // 28 DnSet оплачивающего номера (1)
                    // 29 Оплачивающий номер (10)
                    billBuffer.position( billBuffer.position() + 11 );
                    // 30 DnSet номера вызывающего абонента (1)

                    billBuffer.position( 41 );
                    // 31 Номер вызывающего абонента (c 41 - 10 байт) тип данных: запакованный BCD, свободное пространство заполняется комбинацией "0xF"
                    cdr.numberA = getNumber( billBuffer, 10 );
                    if ( getLogger().isDebugEnabled() )
                    {
                        getLogger().debug( String.format( "Номер A = %s", cdr.numberA ) );
                    }
                    // 32 DnSet номера подключённого абонента (1)
                    // 33 Номер подключённого абонента (10)
                    billBuffer.position( billBuffer.position() + 11 );
                    // 34 DnSet номера вызываемого абонента (1)

                    billBuffer.position( 63 );
                    // 35 Номер вызываемого абонента (10) тип данных: запакованный BCD, свободное пространство заполняется комбинацией "0xF"
                    cdr.numberB = getNumber( billBuffer, 10 );
                    if ( getLogger().isDebugEnabled() )
                    {
                        getLogger().debug( String.format( "Номер B = %s", cdr.numberB ) );
                    }
                    if ( cdr.numberB.isEmpty() ) continue;
                    
                    // 36 Набранный номер (12) тип данных: запакованный BCD, свободное пространство заполняется комбинацией "0xF"
                    cdr.origNumberA = getNumber( billBuffer, 12 );
                    if ( getLogger().isDebugEnabled() )
                    {
                        getLogger().debug( String.format( "Номер A (orig) = %s", cdr.origNumberA ) );
                    }
                    // 37 Номер абонента группы CENTREX (2)
                    // 38 Сокращённый номер вызывающего абонента CENTREX (4)
                    // 39 Сокращённый номер вызываемого абонента CENTREX (4)
                    // 40 Номер модуля вызывающего абонента (1)
                    // 41 Номер модуля вызываемого абонента (1)
                    billBuffer.position( billBuffer.position() + 12 );

                    // 42 Номер группы входящих СЛ (2)
                    cdr.trunkIncoming = String.valueOf( billBuffer.getShort() & 0xFFFF );
                    // 43 Номер группы исходящих СЛ (2)
                    cdr.trunkOutgoing = String.valueOf( billBuffer.getShort() & 0xFFFF );

                    // 44 Номер входящего субмаршрута (2)
                    // 45 Номер исходящего субмаршрута (2)
                    billBuffer.position( billBuffer.position() + 4 );
                    // 46 Тип терминала вызывающего абонента (1)
                    // 47 Тип терминала вызываемого абонента (1)
                    // 48 Номер порта вызывающего абонента (2)
                    // 49 Номер порта вызываемого абонента (2)
                    // 50 Категория вызывающей стороны (1)
                    // 51 Категория вызываемой стороны (1)
                    billBuffer.position( billBuffer.position() + 8 );
                    // 52 Тип вызова (0.5) 
                    //    1: внутристанционный
                    //    2: входящий на станцию
                    //    3: исходящий из станции
                    //    4: транзитный
                    //    5: местный на интернациональный
                    //    6: интернациональный на местный
                    //    7: интернациональный на интернациональный
                    //    8: дополнительная услуга
                    // 53 Тип услуги (0.5) 
                    //    0: внутристанционный
                    //    1: услуга местной связи
                    //    2: услуга внутризоновой связи
                    //    3: услуга междугородной связи
                    //    4: услуга международной связи
                    //    5: дополнительная услуга
                    //    6: услуга вызова к абоненту УПАТС
                    //    14: местная услуга CENTREX
                    billBuffer.position( billBuffer.position() + 1 );
                    // 53 Тип дополнительной услуги (1) 
                    //    0: Выход за пределы группы CENTREX
                    //    1: Регистрация сокращённого набора
                    //    2: Использование сокращённого набора
                    //    3: Отмена сокращённого набора
                    //    4: Полная отмена сокращённого набора
                    //    5: Контроль сокращённого набора
                    //    6: Активизация прямой связи (горячая линия)
                    //    7: Использование прямой связи
                    //    8: Отмены прямой связи
                    //    9: Контроль прямой связи
                    //    10: Активизация запрета исходящих вызовов
                    //    11: Использование запрета исходящих вызовов
                    //    12: Отмена запрета исходящих вызовов
                    //    13: Контроль запрета исходящих вызовов
                    //    14: Активизация вызова-побудки
                    //    15: Использование вызова-побудки
                    //    16: Отмена вызова-побудки
                    //    17: Контроль вызова-побудки
                    //    18: Активизация нескольких вызовов побудки
                    //    19: Отмена нескольких вызовов побудки
                    //    20: Контроль нескольких вызовов побудки
                    //    21: Активизация услуги «абонент отсутствует»
                    //    22: Использование услуги «абонент отсутствует»
                    //    23: Отмена услуги «абонент отсутствует»
                    //    24: Контроль услуги «абонент отсутствует»
                    //    25: Активизация услуги «не беспокоить»
                    //    26: Использование услуги «не беспокоить»
                    //    27: Отмена услуги «не беспокоить»
                    //    28: Контроль услуги «не беспокоить»
                    //    29: Прослеживание злонамеренного вызова
                    //    30: Активизация услуги заказного вызова
                    //    31: Использование услуги заказного вызова
                    //    32: Отмена услуги заказного вызова
                    //    33: Контроль услуги заказного вызова
                    //    34: Активизация услуги безусловной переадресации вызова (CFU; Call Forwarding Unconditionally)
                    //    35: Использование услуги безусловной переадресации вызова (CFU)
                    //    36: Отмена услуги безусловной переадресации вызова (CFU)
                    //    37: Контроль услуги безусловной переадресации вызова (CFU)
                    //    38: Активизация услуги переадресации вызова при занятости (CFB; Call Forwarding on Busy )
                    //    39: Использование услуги переадресации вызова при занятости (CFB)
                    //    40: Отмена услуги переадресации вызова при занятости (CFB)
                    //    41: Контроль услуги переадресации вызова при занятости (CFB)
                    //    42: Активизация услуги переадресации вызова при неответе (CFNR)
                    //    43: Использование услуги переадресации вызова при неответе (CFNR; Call Forwarding on No Reply)
                    //    44: Отмена услуги переадресации вызова при неответе (CFNR)
                    //    45: Контроль услуги переадресации вызова при неответе (CFNR)
                    //    46: Активизация услуги вызова на ожидании
                    //    47: Использование услуги вызова на ожидании
                    //    48: Отмена услуги вызова на ожидании
                    //    49: Контроль услуги вызова на ожидании
                    //    50: Активизация услуги автоматического обратного вызова при занятости
                    //    51: Использование услуги автоматического обратного вызова при занятости
                    //    52: Отмена услуги автоматического обратного вызова при занятости
                    //    53: Контроль услуги автоматического обратного вызова при занятости
                    //    54: Использование услуги трёхсторонней связи
                    //    55: Использование услуги конференц-связи
                    //    56: Использование услуги ответа на вызов к определённому абоненту
                    //    57: Использование услуги ответа на любые вызовы
                    //    58: Использование услуги разъединения со стороны вызывающего абонента
                    //    59: Использование услуги разъединения со стороны вызываемого абонента
                    //    60: Активизация услуги CLIR
                    //    61: Отмена услуги CLIR
                    //    62: Контроль услуги CLIR
                    //    63: Активизация услуги COLR
                    //    64: Отмена услуги COLR
                    //    65: Контроль услуги COLR
                    //    66: Использование услуги CLIP
                    //    67: Временная регистрация услуги CLIP
                    //    68: Временная регистрация услуги CLIR
                    //    69: Активизация услуги запрета вызова данного номера
                    //    70: Использование услуги запрета вызова данного номера
                    //    71: Отмена услуги запрета вызова данного номера
                    //    72: Отмена услуг запрета вызовов всех номеров
                    //    73: Контроль услуги запрета вызова данного номера
                    //    74: Активизация функции телефонного аппарата секретаря
                    //    75: Использование функции телефонного аппарата секретаря
                    //    76: Деактивизация функции телефонного аппарата секретаря
                    //    77: Контроль функции телефонного аппарата секретаря
                    //    78: Активизация услуги «секретарь»
                    //    79: Использования услуги «секретарь»
                    //    80: Отмена услуги «секретарь»
                    //    81: Контроль услуги «секретарь»
                    //    82: Активизация срочной прямой связи
                    //    83: Использование срочной прямой связи
                    //    84: Отмена срочной прямой связи
                    //    85: Активизация таблицы переадресации вызовов
                    //    86: Деактивизация таблицы переадресации вызовов
                    //    87: Контроль таблицы переадресации вызовов
                    //    88: Дистанционная активизация CFU
                    //    89: Дистанционная отмена CFU
                    //    90: Дистанционная активизация CFB
                    //    91: Дистанционная отмена CFB
                    //    92: Дистанционная активизация CFNR
                    //    93: Дистанционная отмена CFNR
                    //    94: Замена пароля
                    //    95: Отключение участника конференцсвязи
                    //    96: Принятие входящего вызова при конференцсвязи
                    //    97: Отклонение входящего вызова при конференцсвязи
                    //    98: Регистрация списка участников конференцсвязи
                    //    99: Использование списка участников конференцсвязи
                    //    100: Очистка списка участников конференцсвязи
                    //    101: Очистка всех списков участников конференцсвязи
                    //    102: Контроль списка участников конференцсвязи
                    //    103: Отмена всех услуг
                    //    104: Использование услуги вызова по паролю
                    //    105: Использование услуги меню подсказки при исходящем вызове
                    //    106: Активизация услуги предварительного заказа (бронирование) вызова
                    //    107: Использование услуги предварительного заказа (бронирования) вызова
                    //    108: Отмена предварительно заказанного (забронированного) вызова
                    //    109: Контроль услуги предварительного заказа (бронирования) вызова
                    //    110: Переадресация вызова при занятости (CFB) на речевую почту
                    //    111: Переадресация вызова при неответе (CFNR) на речевую почту
                    //    112: Добавление напряжения MWN
                    //    113: Активизация услуги информации о стоимости при завершении соединения (AOCE)
                    //    114: Использование услуги информации о стоимости при завершении соединения (AOCE)
                    //    115: Отмена услуги информации о стоимости при завершении соединения (AOCE)
                    //    254: Действие для всех дополнительных услуг
                    //    255: Резерв для введения новых услуг
                    billBuffer.position( billBuffer.position() + 1 );
                    // 55 Charging Case (2)
                    // 56 Тариф (2)
                    // 57 Тарификационный импульс (4)
                    // 58 Стоимость (4)
                    // 59 Баланс (4)
                    // 60 Услуги доставки информации (1)
                    // 61 Услуга предоставления связи (0.5)
                    // 62 Причина завершения разговора (0.375)
                    //    0: отбой со стороны вызывающего абонента;
                    //    1: отбой со стороны вызываемого абонента;
                    //    2: Внутренний  отбой
                    //    3: Отбой станции вызывающего абонента
                    //    4: Отбой станции вызываемого абонента
                    // 63 Указатель освобождения (Release index) (1.125)
                    // 64 Значение причины освобождения (Cause value) (1)
                    // 65 Счётчик услуги UUS1 (1)
                    // 66 Счётчик услуги UUS2 (1)
                    // 67 Счётчик услуги UUS3 (1)
                    // 68 OPC (4)
                    // 69 DPC (4)
                    // 70 B_num (0.625) 
                    // 71 Зарезервировано (0.375) Заполняется до одного байта
                    // 72 Зарезервировано (78) Заполняется до 154 байтов

                    String date = cdr.start.format( formatter );
                    List<String> list = cdrs.get( date );
                    if ( list == null )
                    {
                        list = new ArrayList<>();
                        cdrs.put( date, list );
                    }
                    list.add( cdr.toString() );
                    // System.out.println( cdr.toString() );
                }
            }
            catch ( Exception ex )
            {
                BGLogger.error( ex );
            }
        }
    }
    
    protected String getNumber( ByteBuffer byteBuffer, int len )
    {
        StringBuffer buf = new StringBuffer();
        int i = 0;
        while( i < len )
        {
            int b = byteBuffer.get() & 0xFF;
            int b1 = (b & 0xF0) >> 4;
            int b2 = b & 0x0F;
            buf.append( b1 == 15 ? "" : b1 ).append( b2 == 15 ? "" : b2 );
            i++;
        }
        return buf.toString();
    }
    
    public static void main( String[] arg )
    {
        CC08Mediator cc08Mediator = new CC08Mediator();
        Map<String, List<String>> cdrs = new HashMap<>();
        cc08Mediator.parseBil( Paths.get( "2023020100.BIL" ), cdrs );
    }
}
