/**
 * MAPHS – Macroscopic Atom-Pattern Hyperfine Sensor
 * C++ implementation for real black hole energy extraction via superradiant interference.
 * 
 * Assumptions:
 * - The device is already within the ergosphere or near the event horizon.
 * - Video captures spin lattice (optical or matter-wave patterns).
 * - Audio captures cavity phonons / horizon modes.
 * - Outputs control signal (0-255) to tune interference mirror position or phase.
 */

#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#include <thread>
#include <chrono>
#include <atomic>
#include <mutex>
#include <cstring>

#include <opencv2/opencv.hpp>
#include <portaudio.h>

// ---------------------------------------------------------------------
// Configuration constants
// ---------------------------------------------------------------------
const int GRID_W = 36;
const int GRID_H = 28;
const int TOTAL_PIXELS = GRID_W * GRID_H;

// Audio / cavity parameters
const int CAVITY_LO_BIN = 5;      // ~117 Hz @ 48kHz, 2048 FFT
const int CAVITY_HI_BIN = 25;     // ~585 Hz
const int FFT_SIZE = 2048;
const int SAMPLE_RATE = 48000;
const float EMA_ALPHA = 0.15f;
const float CONTROL_UPDATE_HZ = 30.0f;

// ---------------------------------------------------------------------
// Global state (thread-safe)
// ---------------------------------------------------------------------
struct SensorMetrics {
    float winding = 0.0f;
    float gainDB = -INFINITY;
    float hyperfine = 0.0f;
    float entropy = 0.0f;
    float accuracy = 0.0f;
    bool isEPR = false;
    bool isViolation = false;
    char statusMsg[256] = "SYSTEM INACTIVE";
};

static std::atomic<bool> running(true);
static std::mutex metricsMutex;
static SensorMetrics latestMetrics;
static float controlOutput = 0.0f;   // 0..1, maps to mirror position / phase

// Audio circular buffer (for FFT in separate thread)
static std::vector<float> audioBuffer(FFT_SIZE);
static std::mutex audioMutex;
static bool newAudioData = false;

// ---------------------------------------------------------------------
// Audio callback (PortAudio)
// ---------------------------------------------------------------------
static int audioCallback(const void *inputBuffer, void *outputBuffer,
                         unsigned long framesPerBuffer,
                         const PaStreamCallbackTimeInfo* timeInfo,
                         PaStreamCallbackFlags statusFlags,
                         void *userData) {
    (void) outputBuffer; (void) timeInfo; (void) statusFlags; (void) userData;
    const float *in = (const float*)inputBuffer;
    if (in == nullptr) return paContinue;

    std::lock_guard<std::mutex> lock(audioMutex);
    // Shift buffer by framesPerBuffer and append new samples
    if (framesPerBuffer <= FFT_SIZE) {
        std::memmove(audioBuffer.data(), audioBuffer.data() + framesPerBuffer,
                     (FFT_SIZE - framesPerBuffer) * sizeof(float));
        std::memcpy(audioBuffer.data() + FFT_SIZE - framesPerBuffer, in,
                    framesPerBuffer * sizeof(float));
        newAudioData = true;
    }
    return paContinue;
}

// ---------------------------------------------------------------------
// Compute frequency spectrum (simple DFT magnitude for bins of interest)
// ---------------------------------------------------------------------
void computeCavityEnergy(const std::vector<float>& buffer, float sampleRate,
                         float& cavityEnergy, int& peakBin, float& peakVal) {
    std::vector<float> magSpectrum(FFT_SIZE/2 + 1, 0.0f);
    // Real FFT using OpenCV's DFT (or we can do simple Goertzel for few bins)
    // For efficiency, only compute bins from CAVITY_LO_BIN to CAVITY_HI_BIN
    peakBin = CAVITY_LO_BIN;
    peakVal = 0.0f;
    cavityEnergy = 0.0f;
    int numBins = CAVITY_HI_BIN - CAVITY_LO_BIN + 1;
    for (int k = CAVITY_LO_BIN; k <= CAVITY_HI_BIN; ++k) {
        float real = 0.0f, imag = 0.0f;
        float angle = 2.0f * M_PI * k / FFT_SIZE;
        for (int n = 0; n < FFT_SIZE; ++n) {
            real += buffer[n] * cosf(angle * n);
            imag -= buffer[n] * sinf(angle * n);
        }
        float mag = sqrtf(real*real + imag*imag) / FFT_SIZE;
        magSpectrum[k] = mag;
        cavityEnergy += mag;
        if (mag > peakVal) {
            peakVal = mag;
            peakBin = k;
        }
    }
    cavityEnergy /= numBins;
}

// ---------------------------------------------------------------------
// Video processing: spin texture analysis
// ---------------------------------------------------------------------
void processVideoFrame(const cv::Mat& frame,
                       float& winding, float& hyperfine, float& entropy, float& accuracy,
                       bool& isEPR, bool& isViolation,
                       char* statusMsg, size_t msgSize,
                       float cavityEnergy, float avgMotionPrev) {
    // Resize to grid
    cv::Mat small;
    cv::resize(frame, small, cv::Size(GRID_W, GRID_H), 0, 0, cv::INTER_AREA);
    cv::Mat gray;
    cv::cvtColor(small, gray, cv::COLOR_BGR2GRAY);
    gray.convertTo(gray, CV_32F, 1.0/255.0);

    static std::vector<float> prevLuma(TOTAL_PIXELS, 0.0f);
    static std::vector<float> spinTheta(TOTAL_PIXELS, 0.0f);
    static std::vector<float> spinMag(TOTAL_PIXELS, 0.0f);

    float totalDX = 0.0f, totalDY = 0.0f;
    float totalMotion = 0.0f;
    float lumaSum = 0.0f;

    // Spin gradients and motion
    for (int y = 1; y < GRID_H-1; ++y) {
        for (int x = 1; x < GRID_W-1; ++x) {
            int idx = y * GRID_W + x;
            float lum = gray.at<float>(y, x);
            
            // Spatial gradients
            float left  = gray.at<float>(y, x-1);
            float right = gray.at<float>(y, x+1);
            float up    = gray.at<float>(y-1, x);
            float down  = gray.at<float>(y+1, x);
            
            float dx = right - left;
            float dy = down - up;
            float theta = atan2f(dy, dx);
            float mag = sqrtf(dx*dx + dy*dy);
            
            float motion = fabsf(lum - prevLuma[idx]);
            prevLuma[idx] = lum;
            totalMotion += motion;
            lumaSum += lum;
            
            totalDX += cosf(theta) * mag;
            totalDY += sinf(theta) * mag;
            
            spinTheta[idx] = theta;
            spinMag[idx] = mag;
        }
    }
    float avgMotion = totalMotion / TOTAL_PIXELS;
    float meanLuma = lumaSum / TOTAL_PIXELS;
    
    // Winding number (pseudo circulation)
    float circulation = 0.0f;
    for (int y = 2; y < GRID_H-2; ++y) {
        for (int x = 2; x < GRID_W-2; ++x) {
            int idx = y * GRID_W + x;
            float t = spinTheta[idx];
            circulation += (sinf(t) * totalDX - cosf(t) * totalDY);
        }
    }
    float windingClamped = std::min(1.0f, fabsf(circulation) / (TOTAL_PIXELS * 2.0f));
    winding = windingClamped;
    
    // Hyperfine coupling = motion * cavity energy
    float rawCoupling = avgMotion * cavityEnergy * 8.0f;
    static float couplingEMA = 0.0f;
    couplingEMA = (1.0f - EMA_ALPHA) * couplingEMA + EMA_ALPHA * rawCoupling;
    hyperfine = couplingEMA;
    
    // Superradiant gain
    float superGain = couplingEMA * (1.0f + windingClamped);
    static float gainEMA = 0.0f;
    gainEMA = (1.0f - EMA_ALPHA) * gainEMA + EMA_ALPHA * superGain;
    float gainDB = (gainEMA > 1e-6f) ? 10.0f * log10f(gainEMA) : -INFINITY;
    
    // Entropy (Bekenstein-Hawking style)
    float totalMag = 0.0f;
    for (float m : spinMag) totalMag += m + 0.001f;
    float entropyVal = 0.0f;
    for (float m : spinMag) {
        float p = (m + 0.001f) / totalMag;
        if (p > 0.0f) entropyVal -= p * log2f(p);
    }
    float maxEnt = log2f(TOTAL_PIXELS);
    float normEntropy = entropyVal / maxEnt;
    entropy = entropyVal;
    
    // Accuracy
    float accRaw = (1.0f - normEntropy) * couplingEMA * 4.0f;
    static float accEMA = 0.0f;
    accEMA = (1.0f - 0.1f) * accEMA + 0.1f * std::min(1.0f, accRaw);
    accuracy = accEMA;
    
    // EPR bridge and SKISS detection
    float leftMotion = 0.0f, rightMotion = 0.0f;
    for (int y = 0; y < GRID_H; ++y) {
        for (int x = 0; x < GRID_W; ++x) {
            int idx = y * GRID_W + x;
            float delta = fabsf(prevLuma[idx] - meanLuma);
            if (x < GRID_W/2) leftMotion += delta;
            else rightMotion += delta;
        }
    }
    float leftCorr = leftMotion / (TOTAL_PIXELS/2);
    float rightCorr = rightMotion / (TOTAL_PIXELS/2);
    bool eprSymmetric = fabsf(leftCorr - rightCorr) < 0.05f && cavityEnergy > 0.15f && avgMotion > 0.02f;
    
    isEPR = (eprSymmetric && couplingEMA > 0.25f);
    isViolation = false;
    if (avgMotion > 0.08f && cavityEnergy < 0.03f) isViolation = true;
    else if (avgMotion < 0.02f && cavityEnergy > 0.25f) isViolation = true;
    
    if (isEPR) {
        snprintf(statusMsg, msgSize, "EPR BRIDGE DETECTED — Horizon Modes Entangled");
    } else if (isViolation) {
        snprintf(statusMsg, msgSize, "SKISS-VIOLATION: Visual/acoustic mismatch");
    } else if (normEntropy > 0.85f) {
        snprintf(statusMsg, msgSize, "SKISS-INCOMPLETE: Spin texture disordered");
    } else {
        snprintf(statusMsg, msgSize, "SKISS-COMPLETE (Bonded)");
    }
    
    // Store gain for external use
    // (gainEMA is used to compute control output)
}

// ---------------------------------------------------------------------
// Control algorithm: adjusts interference mirror/phase to maximize
// superradiant energy extraction.
// ---------------------------------------------------------------------
float computeControlSignal(float gainDB, float hyperfine, float winding, float accuracy) {
    // Simple PID-like: we want to maximize gain. In real system, you'd dither.
    // Here we simulate a hill-climbing: increase control if gain is rising.
    static float lastGain = -INFINITY;
    static float control = 0.5f;
    float currentGain = gainDB;
    if (lastGain > -INFINITY && currentGain > lastGain) {
        // Continue same direction
        control += 0.01f;
    } else if (lastGain > -INFINITY && currentGain < lastGain) {
        control -= 0.01f;
    }
    control = std::min(1.0f, std::max(0.0f, control));
    lastGain = currentGain;
    return control;
}

// ---------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------
int main() {
    std::cout << "MAPHS C++ – Black Hole Energy Extraction System" << std::endl;
    std::cout << "Initializing sensors (camera + microphone)..." << std::endl;
    
    // Initialize PortAudio
    PaError err = Pa_Initialize();
    if (err != paNoError) {
        std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
        return 1;
    }
    
    // Open default input stream
    PaStream *stream;
    err = Pa_OpenDefaultStream(&stream, 1, 0, paFloat32, SAMPLE_RATE,
                               paFramesPerBufferUnspecified, audioCallback, nullptr);
    if (err != paNoError) {
        std::cerr << "Failed to open audio stream: " << Pa_GetErrorText(err) << std::endl;
        Pa_Terminate();
        return 1;
    }
    err = Pa_StartStream(stream);
    if (err != paNoError) {
        std::cerr << "Failed to start audio stream: " << Pa_GetErrorText(err) << std::endl;
        Pa_CloseStream(stream);
        Pa_Terminate();
        return 1;
    }
    
    // Open video capture (default camera)
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) {
        std::cerr << "Cannot open camera" << std::endl;
        Pa_StopStream(stream);
        Pa_CloseStream(stream);
        Pa_Terminate();
        return 1;
    }
    
    std::cout << "Sensors online. Beginning real-time extraction control loop." << std::endl;
    
    // Control loop rate
    const auto loopInterval = std::chrono::milliseconds(static_cast<long>(1000.0f / CONTROL_UPDATE_HZ));
    auto nextFrameTime = std::chrono::steady_clock::now();
    
    cv::Mat frame;
    float cavityEnergy = 0.0f;
    int peakBin = CAVITY_LO_BIN;
    float peakVal = 0.0f;
    float avgMotionPrev = 0.0f;
    
    while (running) {
        // Capture video frame
        cap >> frame;
        if (frame.empty()) break;
        
        // Get latest audio buffer
        std::vector<float> audioCopy;
        {
            std::lock_guard<std::mutex> lock(audioMutex);
            if (newAudioData) {
                audioCopy = audioBuffer;
                newAudioData = false;
            }
        }
        
        if (!audioCopy.empty()) {
            computeCavityEnergy(audioCopy, SAMPLE_RATE, cavityEnergy, peakBin, peakVal);
        }
        
        // Process video and compute metrics
        float winding, hyperfine, entropy, accuracy;
        bool isEPR, isViolation;
        char statusMsg[256];
        processVideoFrame(frame, winding, hyperfine, entropy, accuracy,
                          isEPR, isViolation, statusMsg, sizeof(statusMsg),
                          cavityEnergy, avgMotionPrev);
        
        // Compute control signal for interference mirror
        float gainDB = (hyperfine > 0.001f) ? 10.0f * log10f(hyperfine) : -INFINITY;
        float control = computeControlSignal(gainDB, hyperfine, winding, accuracy);
        controlOutput = control;
        
        // Update latest metrics for external display/interface
        {
            std::lock_guard<std::mutex> lock(metricsMutex);
            latestMetrics.winding = winding;
            latestMetrics.gainDB = gainDB;
            latestMetrics.hyperfine = hyperfine;
            latestMetrics.entropy = entropy;
            latestMetrics.accuracy = accuracy;
            latestMetrics.isEPR = isEPR;
            latestMetrics.isViolation = isViolation;
            strncpy(latestMetrics.statusMsg, statusMsg, sizeof(latestMetrics.statusMsg)-1);
        }
        
        // --- Output control signal to actuator (example: serial or just print) ---
        // In a real system you would write to /dev/ttyUSB0 or use a DAC.
        static int printCounter = 0;
        if (++printCounter % 30 == 0) {
            std::cout << "\r[CTRL] mirror = " << controlOutput
                      << " | Gain = " << gainDB << " dB"
                      << " | Winding = " << winding
                      << " | " << statusMsg << "          \n" << std::flush;
        }
        
        // Optional: draw spin vectors overlay on frame (like original JS)
        // (omitted for brevity, but could be added using OpenCV drawing functions)
        
        // Maintain control loop rate
        std::this_thread::sleep_until(nextFrameTime);
        nextFrameTime += loopInterval;
    }
    
    // Cleanup
    cap.release();
    Pa_StopStream(stream);
    Pa_CloseStream(stream);
    Pa_Terminate();
    std::cout << "\nMAPHS shutdown." << std::endl;
    return 0;
}
