import os
import numpy as np

class AcousticCollapseTheoryV2:
    def __init__(self, num_frequencies=512, resonance_threshold=0.05, max_ode_steps=10):
        self.NUM_FREQS = num_frequencies
        self.RESONANCE_THRESHOLD = resonance_threshold # How much the wave shape can change before we stop
        self.MAX_ODE_STEPS = max_ode_steps
        self.kb = []
        print(f"[ACT v2] Initialized Phase-locked Acoustic Cortex.")

    def _text_to_freq(self, bigram):
        """Hashes a 2-word pair to a frequency band."""
        hash_val = 5381
        for char in bigram.lower():
            hash_val = ((hash_val << 5) + hash_val) + ord(char)
        return hash_val % self.NUM_FREQS

    def encode_to_signal(self, text):
        """Encodes text into a 1D wave where Bigrams=Frequency and Word Order=Phase."""
        signal = np.zeros(self.NUM_FREQS, dtype=np.complex128)
        words = text.lower().split()
        
        if len(words) < 2: return signal
        
        # Create bigrams
        bigrams = [f"{words[i]} {words[i+1]}" for i in range(len(words) - 1)]
        num_bigrams = len(bigrams)
        
        for i, bigram in enumerate(bigrams):
            freq = self._text_to_freq(bigram)
            # PHASE MAPPING: Word order maps to a rotation around the unit circle.
            # If sentence is 10 words long, phase advances by 36 degrees per bigram.
            phase = (i / num_bigrams) * 2 * np.pi 
            
            # Add to wave: Amplitude of 1.0, Phase determined by syntax order
            signal[freq] += 1.0 * np.exp(1j * phase)
            
        return signal

    def calculate_wave_stability(self, sig1, sig2):
        """
        Measures how much the wave shape changed between ODE steps.
        Uses complex dot product. 1.0 = identical shape, 0.0 = completely different.
        """
        mag1 = np.abs(sig1)
        mag2 = np.abs(sig2)
        
        # Only compare the parts of the wave that actually have energy
        active_bins = (mag1 > 0.1) | (mag2 > 0.1)
        if not np.any(active_bins): return 1.0
        
        s1_active = sig1[active_bins]
        s2_active = sig2[active_bins]
        
        # Complex dot product gives us Phase-Aware similarity
        dot = np.vdot(s1_active, s2_active)
        norm = np.linalg.norm(s1_active) * np.linalg.norm(s2_active)
        
        if norm == 0: return 1.0
        return abs(dot / norm)

    def apply_ode_dynamics(self, signal, time_step):
        """
        Phase-Locked Loop (PLL) ODE Simulation.
        Simulates a resonant cavity. Frequencies that are strong get sharper 
        (higher Q-factor), weak frequencies decay (damping).
        """
        magnitudes = np.abs(signal)
        phases = np.angle(signal)
        
        # 1. Non-linear sharpening (Acoustic Resonance)
        # Makes strong peaks sharper, kills weak random noise
        sharpened = np.power(magnitudes, 1.8) 
        
        # 2. Phase Drift (Simulating wave propagation in a medium)
        # Higher frequencies drift faster (Physics: v = f * wavelength)
        phase_drift = (0.15 * time_step * np.arange(self.NUM_FREQS)) / self.NUM_FREQS
        new_phases = phases + phase_drift
        
        return sharpened * np.exp(1j * new_phases)

    def signal_similarity(self, sig1, sig2):
        """Phase-aware acoustic matching."""
        active_bins = (np.abs(sig1) > 0.1) | (np.abs(sig2) > 0.1)
        if not np.any(active_bins): return 0.0
        
        s1 = sig1[active_bins]
        s2 = sig2[active_bins]
        
        dot = np.vdot(s1, s2)
        norm = np.linalg.norm(s1) * np.linalg.norm(s2)
        return abs(dot / norm) if norm > 0 else 0.0

    def train(self, books_dir="books"):
        if not os.path.exists(books_dir):
            print(f"[ERROR] '{books_dir}' not found."); return

        print(f"[TRAIN] Scanning '{books_dir}'...")
        for filename in os.listdir(books_dir):
            if filename.endswith(".txt") or filename.endswith(".md"):
                with open(os.path.join(books_dir, filename), 'r', encoding='utf-8', errors='ignore') as f:
                    lines = [line.strip() for line in f if len(line.strip()) > 15] # Slightly longer sentences for better bigrams
                    for i in range(len(lines) - 1):
                        trigger_sig = self.encode_to_signal(lines[i])
                        response_sig = self.encode_to_signal(lines[i+1])
                        self.kb.append((trigger_sig, response_sig, lines[i+1]))
        print(f"[TRAIN] Complete. {len(self.kb)} phase-locked associations built.\n")

    def query(self, prompt_text):
        if not self.kb: return "Knowledge base empty."

        print(f"Input:  {prompt_text}")
        current_signal = self.encode_to_signal(prompt_text)
        prev_signal = np.zeros_like(current_signal)
        
        # The ODE "Thinking" Loop
        collapsed = False
        for t in range(1, self.MAX_ODE_STEPS + 1):
            # Check if the wave has reached a stable resonant state (Collapse)
            stability = self.calculate_wave_stability(current_signal, prev_signal)
            change = 1.0 - stability
            
            if change < self.RESONANCE_THRESHOLD and t > 1:
                print(f"[ODE-CCT] WAVE RESONANCE COLLAPSED at t={t}. (Shape change: {change:.4f})")
                collapsed = True
                break
            
            # Propagate ODE
            prev_signal = current_signal.copy()
            current_signal = self.apply_ode_dynamics(current_signal, t)
            print(f"[ODE-CCT] t={t}: Resonating... (Wave shape change: {change:.4f})")

        if not collapsed:
            print(f"[ODE-CCT] Max time reached. Forcing evaluation.")

        # Find best acoustic match in KB
        best_score = 0.0
        best_response = "... (Destructive Interference - No match)"

        for trigger_sig, response_sig, response_text in self.kb:
            score = self.signal_similarity(current_signal, trigger_sig)
            if score > best_score:
                best_score = score
                best_response = response_text

        print(f"[RESULT] Phase-Aligned Confidence: {best_score:.3f}")
        print(f"Output:  {best_response}\n" + "-"*60)
        return best_response

if __name__ == "__main__":
    ai = AcousticCollapseTheoryV2(num_frequencies=512, resonance_threshold=0.02, max_ode_steps=8)
    ai.train("books")
    
    print("=== STARTING EVALUATION ===")
    test_prompts = [
        "What is the main concept?",
        "Explain the theory of relativity",
        "The quantum state collapses",
        "Poirot looked at the murder scene", # Try something explicitly in the books
        "Random words apple bicycle gravity"
    ]
    
    for prompt in test_prompts:
        ai.query(prompt)
