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

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

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.modules.voice.common.bean.VoiceDevice;
import ru.bitel.bgbilling.modules.voice.common.bean.VoiceDeviceType;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Preferences;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;

public class SI2000Mediator
    extends AbstractMediator
{
    private boolean isFtp;
    private String ftpUrl;
    private String ftpLogin;
    private String ftpPswd;
    
    @Override
    public Object init( Setup setup, int moduleId, VoiceDevice device, VoiceDeviceType deviceType, ParameterMap config )
        throws Exception
    {
        Object result = super.init( setup, moduleId, device, deviceType, config );
        
        Preferences devConfig = new Preferences( device.getConfig(), "\n" );
        // настройки ftp
        isFtp = devConfig.getBoolean( "cdr.ftp", false );
        getLogger().debug( "cdr.ftp=>" + isFtp );
        if ( isFtp )
        {
            ftpUrl = devConfig.get( "cdr.ftp.url", null );
            if ( ftpUrl == null )
            {
                throw new Exception( "cdr.ftp.url is  NULL" );
            }
            ftpLogin = devConfig.get( "cdr.ftp.login", "" );
            ftpPswd = devConfig.get( "cdr.ftp.pswd", "" );
        }
        
        return result;
    }
    
    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] );
        }
        
        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;
    }
    
    protected String getNumber( ByteBuffer byteBuffer, int index, int len )
    {
        StringBuffer buf = new StringBuffer();
        int i = 0;
        while( i < len )
        {
        	buf.append( ( byteBuffer.get( index + ( i / 2 ) ) & ( i % 2 == 0 ? 0xF0 : 0x0F ) ) >> ( i % 2 == 0 ? 4 : 0 ) );
        	i++;
        }
        return buf.toString();
    }
    
    protected void preload()
    {
        if ( isFtp )
        {
            preloadFTP();
        }
        super.preload();
    }
    
    /**
     * проверяем есть новые файлы на FTP если есть загружаем, конвертируем и удаляем файл с FTP
     * @param hour
     */
    private void preloadFTP()
    {
        getLogger().debug( "Try connect to ftp..." );
        getLogger().debug( "Update from " + ftpUrl );
        
        URI uri = URI.create( ftpUrl );
        FTPClient ftp = new FTPClient();
        if ( uri.getPort() > 0 )
        {
            getLogger().info( "port:" + uri.getPort() );
            ftp.setDefaultPort( uri.getPort() );
        }
        getLogger().debug( "FTP connect" );
        try
        {
            getLogger().debug( "uri.getHost() = " + uri.getHost() );
            ftp.setDataTimeout( 1000 );
            ftp.setControlKeepAliveTimeout( 1000 );
            ftp.connect( uri.getHost() );
            int reply = ftp.getReplyCode();
            getLogger().debug( "reply code = " + reply );
            if ( !FTPReply.isPositiveCompletion( reply ) )
            {
                ftp.disconnect();
                throw new IOException( "FTP server refused connection." );
            }
            getLogger().debug( "login = " + ftpLogin );
            // логин
            if ( !ftp.login( ftpLogin, ftpPswd ) )
            {
                ftp.disconnect();
                throw new IOException( "FTP server login attempt failed." );
            }
            getLogger().debug( "path = " + uri.getPath() );
            // чанге дир
            if ( !ftp.changeWorkingDirectory( uri.getPath() ) )
            {
                String rs = ftp.getReplyString().trim();
                ftp.logout();
                ftp.disconnect();
                throw new IOException( "FTP server change working directory '" + uri.getPath() + "' failed (" + rs + ")." );
            }
            ftp.setFileType( FTP.BINARY_FILE_TYPE );
            
            Path logPath = Paths.get( device.getLogPath() );
            if ( Files.notExists( logPath ) )
            {
                Files.createDirectories( logPath );
            }
            
            Path inboxLogPath = Paths.get( logPath.toString(), "inbox" );
            if ( Files.notExists( inboxLogPath ) )
            {
                Files.createDirectories( inboxLogPath );
            }
            
            for ( FTPFile ftpFile : ftp.listFiles() )
            {
            	String fileName = ftpFile.getName();
            	getLogger().debug( "cdr file: name = " + fileName + "; size = " + ftpFile.getSize() );
                try ( InputStream inputStream = ftp.retrieveFileStream( fileName ) )
                {
                    getLogger().debug( "inputStream = " + inputStream );
                    Files.copy( inputStream, Paths.get( inboxLogPath.toString(), ftpFile.getName() ), StandardCopyOption.REPLACE_EXISTING );

                    boolean completePendingCommand = ftp.completePendingCommand();
                    getLogger().debug( "completePendingCommand = " + completePendingCommand );
                    if ( !completePendingCommand )
                    {
                        throw new Exception( "ftp.completePendingCommand() = false" );
                    }
                }
                getLogger().debug( "deleteFile( " + fileName +  " ) = " + ftp.deleteFile( fileName ) );
            }
        }
        catch( Exception ex )
        {
            logError( ex );
        }
        finally
        {
            getLogger().debug( "FTP disconnect" );
            
            try
            {
                ftp.logout();
            }
            catch( IOException ioe )
            {
                // do nothing
            }
            try
            {
                ftp.disconnect();
            }
            catch( IOException ioe )
            {
                // do nothing
            }
        }
    }
    
    protected void parseLog( Path path, Map<String, List<String>> cdrs )
    {
        if ( Files.exists( path ) )
        {
            byte[] fixed3 = new byte[3];
            try (InputStream inputStream = Files.newInputStream( path ))
            {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHH");
                
                while ( true )
                {
                    int l = inputStream.read( fixed3 );
                    if ( l == -1 )
                    {
                        break;
                    }
                    else if ( l != 3 )
                    {
                        getLogger().error( "fixed3 != 3" );
                        break;
                    }
                    ByteBuffer byteBuffer = ByteBuffer.wrap( fixed3 );
                    int len = byteBuffer.getShort( 1 ) & 0xFFFF;
                    byte[] recordBytes = new byte[ len ];
                    if ( inputStream.read( recordBytes, 3, len - 3 ) != len - 3 )
                    {
                        getLogger().error( "recordBytes != " + len );
                        break;
                    }
                    recordBytes[0] = fixed3[0];
                    recordBytes[1] = fixed3[1];
                    recordBytes[2] = fixed3[2];
                    byteBuffer = ByteBuffer.wrap( recordBytes );
                    if ( getLogger().isTraceEnabled() )
                    {
                        getLogger().trace( String.format( "CDR: type = %d; length = %d; index = %d; call id = %d", 
                                       byteBuffer.get() & 0xFF, byteBuffer.getShort() & 0xFFFF, byteBuffer.getInt(), byteBuffer.getInt() ) );
                    }
                    
                    CDR cdr = new CDR();
                    
                    int a = ( ( byteBuffer.get( 15 ) & 0b11100000 ) >> 5 );
                    int b = ( byteBuffer.get( 15 ) & 0b00011111 );
                    cdr.numberA = getNumber( byteBuffer, 16, a + b );
                    if ( getLogger().isTraceEnabled() )
                    {
                        getLogger().debug( String.format( "Номер A = %s (%d + %d)", cdr.numberA, a, b ) );
                    }
                    
                    int index = 16 + ( ( a + b ) / 2 + ( a + b ) % 2 );
                    while ( index < byteBuffer.limit() )
                    {
                        int elemId = byteBuffer.get( index++ ) & 0xFF;
                        if ( elemId == 100 )
                        {
                            int elemLen = byteBuffer.get( index++ ) & 0xFF;
                            cdr.numberB = getNumber( byteBuffer, index, elemLen );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Номер B = %s (%d)", elemId, cdr.numberB, elemLen ) );
                            }
                            index = index + elemLen / 2 + elemLen % 2;
                        }
                        // Дата и время начала (Start date and time) (102)
                        else if ( elemId == 102 )
                        {
                            int year = 2000 + ( byteBuffer.get( index ) & 0xFF );
                            int month = byteBuffer.get( index + 1 ) & 0xFF;
                            int dayOfMonth = byteBuffer.get( index + 2 ) & 0xFF;
                            int hour = byteBuffer.get( index + 3 ) & 0xFF;
                            int minute = byteBuffer.get( index + 4 ) & 0xFF;
                            int second = byteBuffer.get( index + 5 ) & 0xFF;
                            cdr.start = LocalDateTime.of( year, month, dayOfMonth, hour, minute, second );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Начало = %tF %tT", elemId, cdr.start, cdr.start ) );
                            }
                            index += 8;
                        }
                        // Дата и время завершения вызова (End date and time) (103)
                        else if ( elemId == 103 )
                        {
                            int year = 2000 + ( byteBuffer.get( index ) & 0xFF );
                            int month = byteBuffer.get( index + 1 ) & 0xFF;
                            int dayOfMonth = byteBuffer.get( index + 2 ) & 0xFF;
                            int hour = byteBuffer.get( index + 3 ) & 0xFF;
                            int minute = byteBuffer.get( index + 4 ) & 0xFF;
                            int second = byteBuffer.get( index + 5 ) & 0xFF;
                            LocalDateTime stop = LocalDateTime.of( year, month, dayOfMonth, hour, minute, second );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Окончание = %tF %tT", elemId, stop, stop ) );
                            }
                            index += 8;
                        }
                        // Количество тарифных импульсов (Number of charging units) (104)
                        else if ( elemId == 104 )
                        {
                            int count = (byteBuffer.get( index ) & 0xFF ) << 16 + (byteBuffer.get( index + 1 ) & 0xFF ) << 8 + byteBuffer.get( index + 2 ) & 0xFF; 
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Количество тарифных импульсов = %d", elemId, count ) );
                            }
                            index += 3;
                        }
                        // Базовая услуга (Basic service) (105)
                        else if ( elemId == 105 )
                        {
                            int b1 = byteBuffer.get( index ) & 0xFF;
                            int b2 = byteBuffer.get( index + 1 ) & 0xFF;
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Базовая услуга = %d; Телеслужбы = %d", elemId, b1, b2 ) );
                            }
                            index += 2;
                        }
                        // Исходящая категория (Origin category) (110)
                        else if ( elemId == 110 )
                        {
                            int b1 = byteBuffer.get( index ) & 0xFF;
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Исходящая категория = %d", elemId, b1 ) );
                            }
                            index += 1;
                        }
                        // Тарифное направление (111)
                        else if ( elemId == 111 )
                        {
                            int b1 = byteBuffer.get( index ) & 0xFF;
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Тарифное направление = %d", elemId, b1 ) );
                            }
                            index += 1;
                        }
                        // Идентификация входящей соединительной линии (Incoming trunk data) (113)
                        else if ( elemId == 113 )
                        {
                            int b1 = byteBuffer.getShort( index ) & 0xFFFF;
                            cdr.trunkIncoming = String.valueOf( b1 );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Идентификация входящей соединительной линии = %d", elemId, b1 ) );
                            }
                            index += + 8;
                        }
                        // Идентификация исходящей соединительной линии (Outgoing trunk data) (114)
                        else if ( elemId == 114 )
                        {
                            int b1 = byteBuffer.getShort( index ) & 0xFFFF;
                            cdr.trunkOutgoing = String.valueOf( b1 );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Идентификация исходящей соединительной линии = %d", elemId, b1 ) );
                            }
                            index += 8;
                        }
                        // Длительность вызова или использования дополнительной услуги (Call / Service duration) (115)
                        else if ( elemId == 115 )
                        {
                            cdr.amount = byteBuffer.getInt( index ) / 1000;
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Длительность вызова = %d", elemId, cdr.amount ) );
                            }
                            index += 4;
                        }
                        // Контрольная сумма (Checksum) (116)
                        else if ( elemId == 116 )
                        {
                            int b2 = byteBuffer.get( index + 1 ) & 0xFF;
                            int b3 = byteBuffer.get( index + 2 ) & 0xFF;
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Контрольная сумма = %d, %d", elemId, b2, b3 ) );
                            }
                            index += 3;
                        }
                        // Оригинальный номер вызывающего абонента (Original calling party number) (119)
                        else if ( elemId == 119 )
                        {
                            int b2 = byteBuffer.get( index + 1 ) & 0xFF;
                            cdr.origNumberA = getNumber( byteBuffer, index + 2, b2 );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Оригинальный номер вызывающего абонента = %s (%d)", elemId, cdr.origNumberA, b2 ) );
                            }
                            index += 2 + b2 / 2 + b2 % 2;
                        }                        
                        // Причина разъединения вызова (Call release cause) (121)
                        else if ( elemId == 121 )
                        {
                            int b1 = byteBuffer.getInt( index );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format("elemId = %d; Причина разъединения вызова = %d", elemId, b1 ) );
                            }
                            index += 4;
                        }
                        // Время до ответа (Durations Before Answer) (124)
                        else if ( elemId == 124 )
                        {
                            int b1 = byteBuffer.get( index ) & 0xFF;
                            int b2 = byteBuffer.getInt( index + 1 );
                            int b3 = byteBuffer.getInt( index + 5 );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format("elemId = %d; Длина = %d; Время до ответа = %d/%d", elemId, b1, b2, b3 ) );
                            }
                            index += 9;
                        }
                        // IP–адреса (IP Addresses) (127)
                        else if ( elemId == 127 )
                        {
                            int b1 = byteBuffer.getInt( index );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; IP–адреса = %d", elemId, b1 ) );
                            }
                            index += byteBuffer.get( index ) - 1;
                        }
                        // VoIP–информация (VoIP Info) (128)
                        else if ( elemId == 128 )
                        {
                            int b1 = byteBuffer.getInt( index );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; VoIP–информация = %d", elemId, b1 ) );
                            }
                            index += + 12;
                        }
                        // Объем передаваемых данных (Amount of Transferred Data) (129)
                        else if ( elemId == 129 )
                        {
                            int b1 = byteBuffer.getInt( index );
                            if ( getLogger().isTraceEnabled() )
                            {
                                getLogger().trace( String.format( "elemId = %d; Объем передаваемых данных = %d", elemId, b1 ) );
                            }
                            index += + 24;
                        }
                        else
                        {
                        	if ( getLogger().isTraceEnabled() )
                            {
                        	    getLogger().trace( String.format( "elemId = " + elemId ) );
                            }
                        }
                    }
                    String date = cdr.start.format( formatter );
                    List<String> buffer = cdrs.get( date );
                    if ( buffer == null )
                    {
                        buffer = new ArrayList<>();
                        cdrs.put( date, buffer );
                    }
                    buffer.add( cdr.toString() );
                    if ( getLogger().isDebugEnabled() )
                    {
                        getLogger().debug( "cdr => {}", cdr.toString() );
                    }
                }
            }
            catch( IOException ex )
            {
                logError( ex );
            }
        }
    }
    
    protected Path getOutboxPath( Path processedDir, Path file )
    {
        String fileName = file.getFileName().toString();
        return Paths.get( processedDir.toString(), "20" + fileName.substring( 7, 9 ), fileName.substring( 9, 11 ) );
    }    
}
