#pragma once

#include "../src/gpio_interface.hpp"
#include <map>
#include <queue>
#include <vector>
#include <chrono>
#include <string>
#include <memory>
#include <cstdlib>

// Mock GPIO interface for testing - closely models Raspberry Pi 5 hardware with gpiod API
class MockGPIOInterface : public IGPIOInterface {
private:
    struct GPIOState {
        bool requested = false;
        bool pull_up = true;
        bool value = false;
        bool has_hardware_pullup = false;
        bool has_hardware_pulldown = false;
    };
    
    std::map<int, GPIOState> gpio_states_;
    uint64_t current_time_ms_ = 0;
    bool chip_open_ = false;
    
    // Event simulation
    struct GPIOEvent {
        uint64_t time_ms;
        int pin;
        bool value;
    };
    std::queue<GPIOEvent> scheduled_events_;
    
    // Call tracking
    std::vector<std::string> call_log_;
    
    // Hardware simulation settings
    bool simulate_hardware_errors_ = false;
    double hardware_error_probability_ = 0.0;
    bool strict_pin_validation_ = false;  // Default to false for backward compatibility
    
    // Hardware-specific pin information for Raspberry Pi 5
    static bool is_valid_gpio_pin(int pin) {
        // Valid GPIO pins on Raspberry Pi 5 (BCM numbering): 2-27
        // GPIO 0,1 are reserved for system use
        // GPIO 28+ don't exist on the 40-pin header
        return (pin >= 2 && pin <= 27);
    }
    
    static bool has_hardware_pullup(int pin) {
        // GPIO 2 and 3 have hardware pull-ups (I2C pins)
        return (pin == 2 || pin == 3);
    }
    
    static bool has_hardware_pulldown(int pin) {
        // Most other GPIO pins have hardware pull-downs by default
        // but GPIO 2,3 have pull-ups
        return !has_hardware_pullup(pin);
    }
    
    static bool get_hardware_default_state(int pin) {
        // Hardware pull-ups make the pin high by default
        // Hardware pull-downs make the pin low by default
        return has_hardware_pullup(pin);
    }
    
    bool should_simulate_error() {
        if (!simulate_hardware_errors_) return false;
        return (static_cast<double>(rand()) / RAND_MAX) < hardware_error_probability_;
    }
    
public:
    // IGPIOInterface implementation (gpiod-style API)
    bool open_gpio_chip(int chip_num = 0) override {
        call_log_.push_back("open_gpio_chip(" + std::to_string(chip_num) + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        chip_open_ = true;
        return true;
    }
    
    void close_gpio_chip() override {
        call_log_.push_back("close_gpio_chip()");
        chip_open_ = false;
        
        // Release all requested lines
        for (auto& [pin, state] : gpio_states_) {
            state.requested = false;
        }
    }
    
    bool request_gpio_line(int pin, bool pull_up = true) override {
        call_log_.push_back("request_gpio_line(" + std::to_string(pin) + ", " + (pull_up ? "true" : "false") + ")");
        
        // Simulate hardware errors
        if (should_simulate_error()) {
            return false;
        }
        
        // Check if chip is open
        if (!chip_open_) {
            return false;
        }
        
        // Validate pin number (if strict validation enabled)
        if (strict_pin_validation_ && !is_valid_gpio_pin(pin)) {
            return false;  // Invalid pin number
        }
        
        // Check if already requested
        if (gpio_states_.find(pin) != gpio_states_.end() && gpio_states_[pin].requested) {
            return false;  // Already requested
        }
        
        // Check if pin already exists before creating the entry
        bool had_manual_value = (gpio_states_.find(pin) != gpio_states_.end());
        
        // Initialize GPIO state
        GPIOState& state = gpio_states_[pin];
        
        state.requested = true;
        state.pull_up = pull_up;
        state.has_hardware_pullup = has_hardware_pullup(pin);
        state.has_hardware_pulldown = has_hardware_pulldown(pin);
        
        // Set initial value based on hardware defaults (only if not manually set)
        if (!had_manual_value) {
            state.value = get_hardware_default_state(pin);
        }
        
        return true;
    }
    
    void release_gpio_line(int pin) override {
        call_log_.push_back("release_gpio_line(" + std::to_string(pin) + ")");
        
        if (gpio_states_.find(pin) != gpio_states_.end()) {
            gpio_states_[pin].requested = false;
        }
    }
    
    bool read_pin_value(int pin) override {
        call_log_.push_back("read_pin_value(" + std::to_string(pin) + ")");
        
        // Process any scheduled events up to current time
        process_scheduled_events();
        
        // Check if pin is requested
        if (gpio_states_.find(pin) == gpio_states_.end() || !gpio_states_[pin].requested) {
            return false;  // Pin not requested
        }
        
        return gpio_states_[pin].value;
    }
    
    bool wait_for_edge(int pin, int timeout_ms) override {
        call_log_.push_back("wait_for_edge(" + std::to_string(pin) + ", " + std::to_string(timeout_ms) + ")");
        
        // For testing, we'll simulate edge detection by checking if there are scheduled events
        uint64_t start_time = current_time_ms_;
        uint64_t end_time = start_time + timeout_ms;
        
        // Process events until timeout or edge detected
        while (current_time_ms_ < end_time) {
            process_scheduled_events();
            
            // Check if there was an edge on this pin
            if (!scheduled_events_.empty() && 
                scheduled_events_.front().pin == pin && 
                scheduled_events_.front().time_ms <= current_time_ms_) {
                return true;  // Edge detected
            }
            
            // Simulate time passing
            current_time_ms_ += 10;  // 10ms increments
        }
        
        return false;  // Timeout
    }
    
    // Time operations
    void sleep_ms(int milliseconds) override {
        current_time_ms_ += milliseconds;
        process_scheduled_events();
    }
    
    uint64_t get_time_ms() override {
        return current_time_ms_;
    }
    
    // Test helper methods
    void set_pin_value(int pin, bool value) {
        // Create pin state if it doesn't exist (allows pre-setup)
        gpio_states_[pin].value = value;
    }
    
    void schedule_pin_change(int pin, bool value, uint64_t delay_ms) {
        GPIOEvent event;
        event.time_ms = current_time_ms_ + delay_ms;
        event.pin = pin;
        event.value = value;
        scheduled_events_.push(event);
    }
    
    void set_time_ms(uint64_t time_ms) {
        current_time_ms_ = time_ms;
    }
    
    std::vector<std::string> get_call_log() const {
        return call_log_;
    }
    
    void clear_call_log() {
        call_log_.clear();
    }
    
    // Hardware error simulation
    void enable_hardware_error_simulation(double probability = 0.1) {
        simulate_hardware_errors_ = true;
        hardware_error_probability_ = probability;
    }
    
    void disable_hardware_error_simulation() {
        simulate_hardware_errors_ = false;
        hardware_error_probability_ = 0.0;
    }
    
    // Pin validation control
    void enable_strict_pin_validation() {
        strict_pin_validation_ = true;
    }
    
    void disable_strict_pin_validation() {
        strict_pin_validation_ = false;
    }
    
    // Hardware information methods
    std::vector<int> get_i2c_pins() const {
        return {2, 3};  // GPIO 2 (SDA) and GPIO 3 (SCL)
    }
    
    std::vector<int> get_spi_pins() const {
        return {7, 8, 9, 10, 11};  // SPI0 pins on Raspberry Pi
    }
    
    std::vector<int> get_uart_pins() const {
        return {14, 15};  // UART pins (TXD, RXD)
    }
    
    bool is_hardware_pullup_pin(int pin) const {
        return has_hardware_pullup(pin);
    }
    
private:
    void process_scheduled_events() {
        while (!scheduled_events_.empty() && scheduled_events_.front().time_ms <= current_time_ms_) {
            GPIOEvent event = scheduled_events_.front();
            scheduled_events_.pop();
            
            // Apply the event
            if (gpio_states_.find(event.pin) != gpio_states_.end()) {
                gpio_states_[event.pin].value = event.value;
            }
        }
    }
};
