#!/usr/bin/env python3
"""
Preamble Detection for RF Device Identification

Detects and analyzes preamble patterns in RF signals to improve device identification.
Preambles are repetitive patterns at the start of transmissions used for synchronization.

Common preamble types:
  - Alternating (01010101...) - Manchester, Oregon Scientific
  - Sync word (fixed pattern) - Acurite, LaCrosse
  - Long burst (1111...) - Princeton, PT2262
  - Custom patterns - Protocol-specific
"""

import numpy as np
from dataclasses import dataclass
from typing import List, Optional, Dict, Tuple
from collections import Counter


@dataclass
class PreamblePattern:
    """
    Detected preamble pattern in signal

    Attributes:
        type: Pattern type ('alternating', 'sync_word', 'long_burst', 'custom')
        pattern: Binary pattern as string (e.g., "10101010")
        length: Length in pulses
        repetitions: Number of times pattern repeats
        confidence: Detection confidence (0.0-1.0)
        details: Additional pattern characteristics
    """
    type: str
    pattern: str
    length: int
    repetitions: int
    confidence: float
    details: Dict


@dataclass
class PreambleMatch:
    """
    Match result comparing detected preamble to protocol

    Attributes:
        protocol_name: Name of matched protocol
        match_type: 'exact', 'partial', 'similar', 'none'
        similarity: Similarity score (0.0-1.0)
        details: Match details
    """
    protocol_name: str
    match_type: str
    similarity: float
    details: Dict


class PreambleDetector:
    """
    Detect and analyze preamble patterns in RF signals

    Uses multiple detection strategies:
      1. Autocorrelation for periodic patterns
      2. Long burst detection
      3. Known sync word matching
      4. Pattern repetition analysis
    """

    def __init__(self, preamble_window: int = 50):
        """
        Initialize detector

        Args:
            preamble_window: Number of pulses to analyze for preamble (default: 50)
        """
        self.preamble_window = preamble_window

        # Known sync patterns from common protocols
        self.known_sync_patterns = {
            '10': ['Acurite Tower', 'Princeton'],
            '1000': ['Oregon Scientific v2.1', 'Oregon Scientific v3.0'],
            '1111': ['PT2262', 'PT2260'],
            '0110': ['LaCrosse TX141'],
            '101010': ['Manchester encoding'],
        }

    def detect(self, pulses: List[int], timing_short: int, timing_long: int) -> Optional[PreamblePattern]:
        """
        Detect preamble pattern in signal

        Args:
            pulses: List of pulse durations (positive=HIGH, negative=LOW)
            timing_short: Short pulse duration (microseconds)
            timing_long: Long pulse duration (microseconds)

        Returns:
            PreamblePattern if detected, None otherwise
        """
        if not pulses or len(pulses) < 10:
            return None

        # Analyze first N pulses
        preamble_pulses = pulses[:self.preamble_window]

        # Try detection methods in order of specificity
        methods = [
            self._detect_long_burst,
            self._detect_alternating,
            self._detect_sync_word,
            self._detect_repetition,
        ]

        for method in methods:
            try:
                pattern = method(preamble_pulses, timing_short, timing_long)
                if pattern and pattern.confidence > 0.5:
                    return pattern
            except Exception:
                continue

        return None

    def match_against_protocol(
        self,
        detected: Optional[PreamblePattern],
        protocol_preamble: Optional[str],
        protocol_sync: Optional[str]
    ) -> PreambleMatch:
        """
        Match detected preamble against protocol signature

        Args:
            detected: Detected preamble pattern
            protocol_preamble: Expected preamble pattern from protocol
            protocol_sync: Expected sync word from protocol

        Returns:
            PreambleMatch with similarity score
        """
        if not detected:
            return PreambleMatch(
                protocol_name="",
                match_type='none',
                similarity=0.0,
                details={'reason': 'no_preamble_detected'}
            )

        if not protocol_preamble and not protocol_sync:
            # Protocol has no defined preamble - neutral score
            return PreambleMatch(
                protocol_name="",
                match_type='none',
                similarity=0.5,  # Neutral - doesn't help or hurt
                details={'reason': 'protocol_no_preamble'}
            )

        # Check exact match with protocol preamble
        if protocol_preamble:
            # Clean protocol preamble (handle Python expressions like "10" * 4)
            clean_preamble = protocol_preamble

            # Try to evaluate as Python expression for patterns like '"10" * 4'
            if '*' in clean_preamble:
                try:
                    clean_preamble = str(eval(clean_preamble))
                except Exception:
                    # Fallback: remove quotes and spaces
                    clean_preamble = clean_preamble.replace('"', '').replace("'", '').replace(' * ', '').replace('*', '')
            else:
                # Just remove quotes
                clean_preamble = clean_preamble.replace('"', '').replace("'", '')

            if clean_preamble in detected.pattern:
                return PreambleMatch(
                    protocol_name="",
                    match_type='exact',
                    similarity=1.0,
                    details={'matched': clean_preamble, 'in_pattern': detected.pattern}
                )

        # Check sync word match
        if protocol_sync:
            clean_sync = protocol_sync.replace('"', '').replace("'", '')

            if clean_sync in detected.pattern:
                return PreambleMatch(
                    protocol_name="",
                    match_type='exact',
                    similarity=0.9,
                    details={'matched_sync': clean_sync}
                )

        # Check partial match (pattern type similarity)
        if protocol_preamble:
            # Alternating pattern check
            if ('10' in protocol_preamble or '01' in protocol_preamble) and detected.type == 'alternating':
                return PreambleMatch(
                    protocol_name="",
                    match_type='partial',
                    similarity=0.7,
                    details={'reason': 'both_alternating'}
                )

            # Long burst check
            if '1111' in protocol_preamble and detected.type == 'long_burst':
                return PreambleMatch(
                    protocol_name="",
                    match_type='partial',
                    similarity=0.7,
                    details={'reason': 'both_long_burst'}
                )

        # No match
        return PreambleMatch(
            protocol_name="",
            match_type='none',
            similarity=0.3,  # Penalty for mismatch
            details={'reason': 'no_match'}
        )

    # === Detection Methods ===

    def _detect_long_burst(
        self,
        pulses: List[int],
        timing_short: int,
        timing_long: int
    ) -> Optional[PreamblePattern]:
        """
        Detect long HIGH burst at start (e.g., Princeton: 1111...)

        Args:
            pulses: Pulse data
            timing_short: Short pulse duration
            timing_long: Long pulse duration

        Returns:
            PreamblePattern if detected
        """
        if not pulses or pulses[0] <= 0:
            return None  # Must start with HIGH

        first_pulse = pulses[0]

        # Long burst = first pulse significantly longer than expected long pulse
        # OR first pulse is much longer than remaining pulses
        if first_pulse > timing_long * 2.5:
            # Count how many "long" units this represents
            repetitions = int(first_pulse / timing_long) if timing_long > 0 else 1

            return PreamblePattern(
                type='long_burst',
                pattern='1' * repetitions,
                length=1,
                repetitions=repetitions,
                confidence=0.8,
                details={
                    'duration_us': first_pulse,
                    'expected_long': timing_long,
                    'ratio': first_pulse / timing_long if timing_long > 0 else 0
                }
            )

        return None

    def _detect_alternating(
        self,
        pulses: List[int],
        timing_short: int,
        timing_long: int
    ) -> Optional[PreamblePattern]:
        """
        Detect alternating pattern (01010101...) via autocorrelation

        Common in Manchester encoding, Oregon Scientific

        Args:
            pulses: Pulse data
            timing_short: Short pulse duration
            timing_long: Long pulse duration

        Returns:
            PreamblePattern if detected
        """
        if len(pulses) < 20:
            return None

        # Decode first 30 pulses to binary
        threshold = (timing_short + timing_long) / 2
        bits = []
        for p in pulses[:30]:
            if p > 0:  # Only HIGH pulses
                bits.append('1' if abs(p) > threshold else '0')

        if len(bits) < 10:
            return None

        bit_string = ''.join(bits)

        # Check for alternating pattern (at least 8 alternations)
        alternating_patterns = ['10101010', '01010101']
        for pattern in alternating_patterns:
            if pattern in bit_string[:16]:  # Check first 16 bits
                # Count repetitions
                reps = 0
                for i in range(len(bit_string) - 1):
                    if bit_string[i:i+2] in ['10', '01']:
                        reps += 1
                    else:
                        break

                if reps >= 4:
                    return PreamblePattern(
                        type='alternating',
                        pattern=pattern * (reps // len(pattern)),
                        length=reps * 2,
                        repetitions=reps,
                        confidence=0.9,
                        details={
                            'alternations': reps,
                            'detected_pattern': pattern
                        }
                    )

        return None

    def _detect_sync_word(
        self,
        pulses: List[int],
        timing_short: int,
        timing_long: int
    ) -> Optional[PreamblePattern]:
        """
        Detect known sync words from protocol library

        Args:
            pulses: Pulse data
            timing_short: Short pulse duration
            timing_long: Long pulse duration

        Returns:
            PreamblePattern if detected
        """
        if len(pulses) < 10:
            return None

        # Decode first 20 pulses to binary
        threshold = (timing_short + timing_long) / 2
        bits = []
        for p in pulses[:20]:
            if p > 0:
                bits.append('1' if abs(p) > threshold else '0')

        bit_string = ''.join(bits)

        # Check against known sync patterns (longest first to avoid substring matches)
        sorted_patterns = sorted(self.known_sync_patterns.items(), key=lambda x: len(x[0]), reverse=True)
        for pattern, protocols in sorted_patterns:
            if pattern in bit_string:
                return PreamblePattern(
                    type='sync_word',
                    pattern=pattern,
                    length=len(pattern),
                    repetitions=1,
                    confidence=0.85,
                    details={
                        'sync_word': pattern,
                        'possible_protocols': protocols,
                        'position': bit_string.index(pattern)
                    }
                )

        return None

    def _detect_repetition(
        self,
        pulses: List[int],
        timing_short: int,
        timing_long: int
    ) -> Optional[PreamblePattern]:
        """
        Detect repetitive patterns via autocorrelation

        Args:
            pulses: Pulse data
            timing_short: Short pulse duration
            timing_long: Long pulse duration

        Returns:
            PreamblePattern if detected
        """
        if len(pulses) < 20:
            return None

        # Decode to binary
        threshold = (timing_short + timing_long) / 2
        bits = []
        for p in pulses[:40]:
            if p > 0:
                bits.append('1' if abs(p) > threshold else '0')

        if len(bits) < 12:
            return None

        # Look for repeating 2-8 bit patterns
        for pattern_len in range(2, 9):
            if len(bits) < pattern_len * 3:
                continue

            pattern = bits[:pattern_len]
            pattern_str = ''.join(pattern)

            # Count repetitions
            reps = 1
            for i in range(pattern_len, len(bits) - pattern_len + 1, pattern_len):
                chunk = bits[i:i+pattern_len]
                if chunk == pattern:
                    reps += 1
                else:
                    break

            if reps >= 3:  # At least 3 repetitions
                return PreamblePattern(
                    type='custom',
                    pattern=pattern_str * reps,
                    length=pattern_len * reps,
                    repetitions=reps,
                    confidence=0.75,
                    details={
                        'base_pattern': pattern_str,
                        'pattern_length': pattern_len
                    }
                )

        return None


# Singleton instance
_detector: Optional[PreambleDetector] = None


def get_preamble_detector() -> PreambleDetector:
    """Get singleton preamble detector instance"""
    global _detector
    if _detector is None:
        _detector = PreambleDetector()
    return _detector
