/**
 *  Pcap capture multiplexor
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *  Note Copy jnetpcap.jar in C:\WINDOWS\Sun\Java\lib\ext, location of jpcap library
 */
package org.etsi.its.pcapdump;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.etsi.common.ByteHelper;
import org.jnetpcap.Pcap;
import org.jnetpcap.PcapBpfProgram;
import org.jnetpcap.PcapDumper;
import org.jnetpcap.PcapIf;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;

public class PcapMultiplexer implements Runnable {
    
    private static String ExpectedIface = "D4BED91269BD";
    
    /**
     * Unique instance of the factory
     */
    private static PcapMultiplexer instance = null;
    
    private StringBuilder errbuf = new StringBuilder();     // For any error msgs  

    private ILayer _client;
    
    private PcapDumper dumper;

    private PcapMultiplexer() {
        
        filter = "";
        
        // Obtain the list of network interfaces
        List<PcapIf> alldevs = new ArrayList<PcapIf>(); // Will be filled with NICs  
        
                          
        int r = Pcap.findAllDevs(alldevs, errbuf);  
        if (r != Pcap.OK || alldevs.isEmpty()) {  
          System.err.printf("Can't read list of devices, error is %s", errbuf.toString());  
          return;  
        }  

        // Find the right interface
        int ifaceIndex = 0;
        String expectedIface = PcapMultiplexer.ExpectedIface; 
        for( ; ifaceIndex < alldevs.size(); ifaceIndex++) {
            try {
                if (expectedIface.equalsIgnoreCase(ByteHelper.byteArrayToString(alldevs.get(ifaceIndex).getHardwareAddress()))) {
                    // Interface found
                    break;
                }
            } catch (IOException e) {
                // ignore
            }
        }
        // Check result
        if (ifaceIndex == alldevs.size()) {
            throw new RuntimeException(String.format("EthernetLayer.register: Network interface %s not found", expectedIface));
        }
       
        device = alldevs.get(ifaceIndex);
        System.out.println("Listening: " + device.getName());
    }
    
    /**
     * Gets the unique factory instance
     * @return PcapMultiplexer instance
     */
    public static PcapMultiplexer getInstance(final String macAddress){
        ExpectedIface = macAddress;
        return (instance = new PcapMultiplexer());
    }
    
    /**
     * Gets the unique factory instance
     * @return PcapMultiplexer instance
     */
    public static PcapMultiplexer getInstance(){
        return instance;
    }
    
    public synchronized void register(ILayer client, short frameType) {
        // Open interface 
        int snaplen = 64 * 1024;            // Capture all packets, no truncation  
        int flags = Pcap.MODE_PROMISCUOUS;  // capture all packets  
        int timeout = 10;                   // 10 millis  
        pcap = Pcap.openLive(device.getName(), snaplen, flags, timeout, errbuf);  
          
        if (pcap == null) {  
          System.err.printf("Error while opening device for capture: " + errbuf.toString());  
          return;  
        }  
        captureThread = new Thread(this);
        captureThread.start();   
        if (frameType != 0) {
            filter = String.format("ether proto 0x%04x", frameType);
            System.out.println("New filter: " + filter);
        
            // Apply filter
            PcapBpfProgram bpfFilter = new PcapBpfProgram();
            int optimize = 0; // 1 means true, 0 means false
            int netmask = 0;            
            int r = pcap.compile(bpfFilter, filter, optimize, netmask);
            if (r != Pcap.OK) {
                System.out.println("Filter error: " + pcap.getErr());
            }
            pcap.setFilter(bpfFilter);
        } // else, no filter
        
        dumper = pcap.dumpOpen(client.getFileName()); 
        
        _client = client;
    }
    
    public synchronized void unregister(ILayer client) {
        pcap.breakloop();
        dumper.close();
        try {
            captureThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pcap.close();
    }
    
    /**
     * Thread function for jpcap capture loop
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        
        PcapPacketHandler<String> dumpHandler = new PcapPacketHandler<String>() {  
            
            private int counter = 0;
            
            @Override
            public void nextPacket(PcapPacket packet, String user) {
                dumper.dump(packet);
                counter += 1;
                if (counter == 500) {
                    System.out.println("nextPacket: Flushing file");
                    dumper.flush();
                    counter = 0;
                }
            }
          };
        
          pcap.loop(-1, dumpHandler, "pcapdump");
    }
        
    /**
     * Jpcap capture device
     */
    private Pcap pcap;
    
    /**
     * Jpcap capture thread instance.
     */
    private Thread captureThread;
    
    PcapIf device;
    private String filter;
}   