#include "bpf.h"
#include <arpa/inet.h>

// ============================================================================
// WORKING PACKET DETECTION CODE - DO NOT MODIFY THIS SECTION!
// This uses BPF (Berkeley Packet Filter) for accurate packet type detection
// ============================================================================

// UNCOMMENT THIS LINE FOR BPF DEBUG OUTPUT
// #define NETWORK_DEBUG

// BPF-based packet detection for broadcast, multicast, non-IP and IPX
int get_packet_stats_bpf(int *multicast_active, int *broadcast_active, int *nonip_active, int *ipx_active) {
    static int bpf_fd = -1;
    static int first_call = 1;
    char bpf_dev[16];
    struct bpf_hdr *bpf_hdr;
    struct ether_header *eth;
    char bpf_buf[4096];
    int n, i;
    int multicast_detected = 0;
    int broadcast_detected = 0;
    int nonip_detected = 0;
    int ipx_detected = 0;
    
    // Initialize BPF on first call
    if (first_call) {
#ifdef NETWORK_DEBUG
        printf("BPF: Initializing BPF device...\n");
#endif
        
        // Try to find an available BPF device
        for (i = 0; i < 16; i++) {
            snprintf(bpf_dev, sizeof(bpf_dev), "/dev/bpf%d", i);
            bpf_fd = open(bpf_dev, O_RDONLY);
            if (bpf_fd >= 0) {
#ifdef NETWORK_DEBUG
                printf("BPF: Opened %s successfully\n", bpf_dev);
#endif
                break;
            }
        }
        
        if (bpf_fd < 0) {
#ifdef NETWORK_DEBUG
            fprintf(stderr, "BPF: ERROR - Could not open any BPF device\n");
#endif
            *multicast_active = 0;
            *broadcast_active = 0;
            *nonip_active = 0;
            *ipx_active = 0;
            return -1;
        }
        
        // Set up BPF to capture on our interface
        struct ifreq ifr;
        strlcpy(ifr.ifr_name, LAN_INTERFACE_NAME, sizeof(ifr.ifr_name));
        if (ioctl(bpf_fd, BIOCSETIF, &ifr) < 0) {
            perror("BPF: BIOCSETIF failed");
            close(bpf_fd);
            bpf_fd = -1;
            *multicast_active = 0;
            *broadcast_active = 0;
            *nonip_active = 0;
            *ipx_active = 0;
            return -1;
        }
#ifdef NETWORK_DEBUG
        printf("BPF: Bound to interface %s\n", LAN_INTERFACE_NAME);
#endif
        
        // Set immediate mode for quick reads
        int immediate = 1;
        if (ioctl(bpf_fd, BIOCIMMEDIATE, &immediate) < 0) {
            perror("BPF: BIOCIMMEDIATE failed");
        } else {
#ifdef NETWORK_DEBUG
            printf("BPF: Immediate mode enabled\n");
#endif
        }
        
        // Enable promiscuous mode
        if (ioctl(bpf_fd, BIOCPROMISC, NULL) < 0) {
            perror("BPF: BIOCPROMISC failed");
        } else {
#ifdef NETWORK_DEBUG
            printf("BPF: Promiscuous mode enabled\n");
#endif
        }
        
        // Set larger buffer - only if supported
        int buf_size = 65536;
        if (ioctl(bpf_fd, BIOCSBLEN, &buf_size) < 0) {
            // Ignore this error - some systems don't support setting buffer size
#ifdef NETWORK_DEBUG
            printf("BPF: Note: Could not set buffer size (using default)\n");
#endif
        } else {
#ifdef NETWORK_DEBUG
            printf("BPF: Buffer size set to %d\n", buf_size);
#endif
        }
        
        first_call = 0;
#ifdef NETWORK_DEBUG
        printf("BPF: Initialization complete\n");
#endif
    }
    
    if (bpf_fd < 0) {
        *multicast_active = 0;
        *broadcast_active = 0;
        *nonip_active = 0;
        *ipx_active = 0;
        return -1;
    }
    
    // Read available packets (non-blocking)
    n = read(bpf_fd, bpf_buf, sizeof(bpf_buf));
    if (n <= 0) {
        *multicast_active = 0;
        *broadcast_active = 0;
        *nonip_active = 0;
        *ipx_active = 0;
        return 0;
    }
    
#ifdef NETWORK_DEBUG
    printf("BPF: Read %d bytes from BPF\n", n);
#endif
    
    // Process BPF packets
    i = 0;
    int packet_count = 0;
    while (i < n) {
        bpf_hdr = (struct bpf_hdr *)(bpf_buf + i);
        eth = (struct ether_header *)(bpf_buf + i + bpf_hdr->bh_hdrlen);
        
        // Check destination MAC address
        // BROADCAST: FF:FF:FF:FF:FF:FF (all bits set)
        int is_broadcast = 0;
        if (eth->ether_dhost[0] == 0xFF &&
            eth->ether_dhost[1] == 0xFF &&
            eth->ether_dhost[2] == 0xFF &&
            eth->ether_dhost[3] == 0xFF &&
            eth->ether_dhost[4] == 0xFF &&
            eth->ether_dhost[5] == 0xFF) {
            is_broadcast = 1;
        }
        
        // MULTICAST: Check if multicast bit is set (LSB of first byte = 1)
        int is_multicast = ((eth->ether_dhost[0] & 0x01) == 0x01);
        
        // Check EtherType field in Ethernet header
        uint16_t ether_type = ntohs(eth->ether_type);
        
        // Check if this is IPX/SPX - Novell NetWare detection
        int is_ipx = 0;
        
        // FIRST: Check for standard IPX EtherTypes
        if (ether_type == 0x8137 ||    // Novell IPX (old)
            ether_type == 0x8138 ||    // Novell IPX (new)
            ether_type == 0x0BAD) {    // Some older Novell
            is_ipx = 1;
#ifdef NETWORK_DEBUG
            printf("BPF: Standard IPX EtherType detected: 0x%04x\n", ether_type);
#endif
        }
        // SECOND: Check for raw 802.3 frames (Novell NetWare style)
        else if (ether_type <= 0x05DC) {
            // This is an 802.3 raw frame, check for Novell LLC header
            int data_offset = i + bpf_hdr->bh_hdrlen + sizeof(struct ether_header);
            if (data_offset + 2 <= n) {
                unsigned char *llc_data = (unsigned char *)(bpf_buf + data_offset);
                
                // Check for Novell LLC header: DSAP=0xE0, SSAP=0xE0 (IPX/SPX)
                if (llc_data[0] == 0xE0 && llc_data[1] == 0xE0) {
                    is_ipx = 1;
                    
#ifdef NETWORK_DEBUG
                    printf("BPF: >>> NOVELL IPX (802.3 raw) detected! Length: %d <<<\n", ether_type);
                    
                    // Check Control field if available
                    if (data_offset + 3 <= n) {
                        printf("  LLC Control: 0x%02x", llc_data[2]);
                        if (llc_data[2] == 0x03) printf(" (Unnumbered Information)");
                        printf("\n");
                    }
                    
                    printf("  SRC MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
                           eth->ether_shost[0], eth->ether_shost[1], eth->ether_shost[2],
                           eth->ether_shost[3], eth->ether_shost[4], eth->ether_shost[5]);
                    printf("  DST MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
                           eth->ether_dhost[0], eth->ether_dhost[1], eth->ether_dhost[2],
                           eth->ether_dhost[3], eth->ether_dhost[4], eth->ether_dhost[5]);
                    
                    if (is_broadcast) {
                        printf("  NOTE: This is a broadcast IPX packet\n");
                    }
                    if (is_multicast) {
                        printf("  NOTE: This is a multicast IPX packet\n");
                    }
#endif
                }
            }
        }
        // THIRD: Check for other Novell proprietary "EtherTypes"
        else if (ether_type == 0x01e3 ||  // 483 - Novell SAP
                 ether_type == 0x0033 ||  // 51 - Novell RIP/NCP
                 ether_type == 0x0025 ||  // 37 - Novell serialization
                 ether_type == 0x002b ||  // 43 - Novell diagnostics
                 ether_type == 0x0037) {  // 55 - Novell NCP over IPX
            is_ipx = 1;
#ifdef NETWORK_DEBUG
            printf("BPF: Novell proprietary protocol detected: 0x%04x\n", ether_type);
#endif
        }
        
        // Update detection flags with proper precedence
        if (is_ipx) {
            ipx_detected = 1;
        }
        else {
            // Check for NON-IP (excluding standard protocols)
            if (ether_type != 0x0800 &&   // Not IPv4
                ether_type != 0x86dd &&   // Not IPv6
                ether_type != 0x0806 &&   // Not ARP
                ether_type != 0x8035 &&   // Not RARP
                ether_type != 0x8100 &&   // Not VLAN
                ether_type != 0x88cc) {   // Not LLDP
                nonip_detected = 1;
#ifdef NETWORK_DEBUG
                printf("BPF: >>> NON-IP packet detected! EtherType: 0x%04x <<<\n", ether_type);
#endif
            }
            
            // Check for broadcast (non-IPX packets only)
            if (is_broadcast) {
                broadcast_detected = 1;
#ifdef NETWORK_DEBUG
                printf("BPF: >>> BROADCAST packet detected! <<<\n");
#endif
            }
            
            // Check for multicast (non-IPX packets only)
            if (is_multicast && !is_broadcast) {
                multicast_detected = 1;
#ifdef NETWORK_DEBUG
                printf("BPF: >>> MULTICAST packet detected! <<<\n");
#endif
            }
        }
        
        packet_count++;
        i += BPF_WORDALIGN(bpf_hdr->bh_hdrlen + bpf_hdr->bh_caplen);
    }
    
#ifdef NETWORK_DEBUG
    if (packet_count > 0) {
        printf("BPF: Processed %d packets - Multicast: %d, Broadcast: %d, Non-IP: %d, IPX: %d\n",
               packet_count, multicast_detected, broadcast_detected, nonip_detected, ipx_detected);
    }
#endif
    
    // Set LED states based on detection
    *broadcast_active = broadcast_detected;
    *multicast_active = multicast_detected;
    *nonip_active = nonip_detected;
    *ipx_active = ipx_detected;
    
    return 0;
}
