//! Method inlining for hot methods
//!
//! Inlines small, frequently-called methods directly into their call sites
//! to eliminate call overhead and enable further optimizations.

use crate::class_file::{ClassFile, MethodInfo};
use crate::jit::MethodProfile;
use std::collections::{HashMap, HashSet};

/// Configuration for method inlining
#[derive(Debug, Clone)]
pub struct InliningConfig {
    /// Maximum bytecode size for inlining (bytes)
    pub max_inline_size: usize,
    
    /// Minimum invocation count to consider for inlining
    pub min_invocation_count: u64,
    
    /// Maximum call depth for recursive inlining
    pub max_inline_depth: usize,
    
    /// Whether to inline virtual methods (polymorphic)
    pub inline_virtual: bool,
    
    /// Whether inlining is enabled
    pub enabled: bool,
}

impl Default for InliningConfig {
    fn default() -> Self {
        Self {
            max_inline_size: 35,        // HotSpot default
            min_invocation_count: 250,  // Inline hot methods
            max_inline_depth: 9,        // Prevent excessive inlining
            inline_virtual: true,       // Inline monomorphic virtuals
            enabled: true,
        }
    }
}

/// Inlining decision for a method
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InliningDecision {
    /// Inline this method
    Inline,
    /// Don't inline - too large
    TooLarge,
    /// Don't inline - not hot enough
    NotHot,
    /// Don't inline - recursive
    Recursive,
    /// Don't inline - native method
    Native,
    /// Don't inline - synchronized
    Synchronized,
}

/// Inlining candidate with metadata
#[derive(Debug, Clone)]
pub struct InliningCandidate {
    /// Class name
    pub class_name: String,
    /// Method name
    pub method_name: String,
    /// Method descriptor
    pub descriptor: String,
    /// Bytecode size
    pub bytecode_size: usize,
    /// Invocation count
    pub invocation_count: u64,
    /// Inlining decision
    pub decision: InliningDecision,
}

/// Method inliner - analyzes and performs method inlining
pub struct MethodInliner {
    /// Inlining configuration
    config: InliningConfig,
    
    /// Inlining candidates cache
    candidates: HashMap<String, InliningCandidate>,
    
    /// Methods that have been inlined
    inlined_methods: HashSet<String>,
}

impl MethodInliner {
    /// Create a new method inliner
    pub fn new() -> Self {
        Self {
            config: InliningConfig::default(),
            candidates: HashMap::new(),
            inlined_methods: HashSet::new(),
        }
    }
    
    /// Create a new method inliner with custom config
    pub fn with_config(config: InliningConfig) -> Self {
        Self {
            config,
            candidates: HashMap::new(),
            inlined_methods: HashSet::new(),
        }
    }
    
    /// Analyze a method for inlining eligibility
    pub fn analyze_method(
        &mut self,
        class: &ClassFile,
        method: &MethodInfo,
        profile: &MethodProfile,
    ) -> InliningDecision {
        let method_name = class
            .get_string(method.name_index)
            .unwrap_or_else(|| "unknown".to_string());
        let class_name = class
            .get_class_name()
            .unwrap_or_else(|| "Unknown".to_string());
        let full_name = format!("{}.{}", class_name, method_name);
        
        // Check if already analyzed
        if let Some(candidate) = self.candidates.get(&full_name) {
            return candidate.decision.clone();
        }
        
        // Check if native
        if method.access_flags & 0x0100 != 0 {
            return InliningDecision::Native;
        }
        
        // Check if synchronized
        if method.access_flags & 0x0020 != 0 {
            return InliningDecision::Synchronized;
        }
        
        // Get bytecode size
        let bytecode_size = self.get_bytecode_size(method);
        
        // Check size threshold
        if bytecode_size > self.config.max_inline_size {
            return InliningDecision::TooLarge;
        }
        
        // Check hotness
        if profile.invocation_count < self.config.min_invocation_count {
            return InliningDecision::NotHot;
        }
        
        // Check for recursion (simplified - checks self-recursion only)
        if self.is_recursive(class, method) {
            return InliningDecision::Recursive;
        }
        
        InliningDecision::Inline
    }
    
    /// Get bytecode size of a method
    fn get_bytecode_size(&self, method: &MethodInfo) -> usize {
        // Find Code attribute
        for attr in &method.attributes {
            if attr.info.len() >= 8 {
                let code_len = ((attr.info[4] as usize) << 24)
                    | ((attr.info[5] as usize) << 16)
                    | ((attr.info[6] as usize) << 8)
                    | (attr.info[7] as usize);
                return code_len;
            }
        }
        0
    }
    
    /// Check if method is recursive (simplified check)
    fn is_recursive(&self, class: &ClassFile, method: &MethodInfo) -> bool {
        let method_name = class
            .get_string(method.name_index)
            .unwrap_or_else(|| "unknown".to_string());
        
        // Find Code attribute and scan for invokevirtual/invokespecial to self
        for attr in &method.attributes {
            if attr.info.len() >= 8 {
                let code_len = ((attr.info[4] as usize) << 24)
                    | ((attr.info[5] as usize) << 16)
                    | ((attr.info[6] as usize) << 8)
                    | (attr.info[7] as usize);
                
                if let Some(bytecode) = attr.info.get(8..8 + code_len) {
                    let mut pc = 0;
                    while pc < bytecode.len() {
                        let opcode = bytecode[pc];
                        match opcode {
                            0xb6 | 0xb7 => {
                                // invokevirtual or invokespecial
                                if pc + 2 < bytecode.len() {
                                    let method_ref = ((bytecode[pc + 1] as u16) << 8)
                                        | (bytecode[pc + 2] as u16);
                                    // Simplified: assume recursive if any method call
                                    // Full implementation would resolve method_ref
                                    return true;
                                }
                                pc += 3;
                            }
                            _ => pc += 1,
                        }
                    }
                }
            }
        }
        false
    }
    
    /// Get inlining candidates for a compilation unit
    pub fn get_candidates(
        &mut self,
        class: &ClassFile,
        profiles: &HashMap<String, MethodProfile>,
    ) -> Vec<InliningCandidate> {
        let mut candidates = Vec::new();
        
        for method in &class.methods {
            let method_name = class
                .get_string(method.name_index)
                .unwrap_or_else(|| "unknown".to_string());
            let class_name = class
                .get_class_name()
                .unwrap_or_else(|| "Unknown".to_string());
            let full_name = format!("{}.{}", class_name, method_name);
            
            if let Some(profile) = profiles.get(&full_name) {
                let decision = self.analyze_method(class, method, profile);
                let bytecode_size = self.get_bytecode_size(method);
                
                let candidate = InliningCandidate {
                    class_name: class_name.clone(),
                    method_name: method_name.clone(),
                    descriptor: String::new(), // TODO: extract descriptor
                    bytecode_size,
                    invocation_count: profile.invocation_count,
                    decision: decision.clone(),
                };
                
                self.candidates.insert(full_name, candidate.clone());
                
                if decision == InliningDecision::Inline {
                    candidates.push(candidate);
                }
            }
        }
        
        // Sort by invocation count (hottest first)
        candidates.sort_by(|a, b| b.invocation_count.cmp(&a.invocation_count));
        
        candidates
    }
    
    /// Mark a method as inlined
    pub fn mark_inlined(&mut self, class_name: &str, method_name: &str) {
        let full_name = format!("{}.{}", class_name, method_name);
        self.inlined_methods.insert(full_name);
    }
    
    /// Check if a method has been inlined
    pub fn is_inlined(&self, class_name: &str, method_name: &str) -> bool {
        let full_name = format!("{}.{}", class_name, method_name);
        self.inlined_methods.contains(&full_name)
    }
    
    /// Get inlining statistics
    pub fn stats(&self) -> InliningStats {
        let total_candidates = self.candidates.len();
        let inlined_count = self.inlined_methods.len();
        
        let mut too_large = 0;
        let mut not_hot = 0;
        let mut recursive = 0;
        let mut native = 0;
        let mut synchronized = 0;
        
        for candidate in self.candidates.values() {
            match candidate.decision {
                InliningDecision::TooLarge => too_large += 1,
                InliningDecision::NotHot => not_hot += 1,
                InliningDecision::Recursive => recursive += 1,
                InliningDecision::Native => native += 1,
                InliningDecision::Synchronized => synchronized += 1,
                _ => {}
            }
        }
        
        InliningStats {
            total_candidates,
            inlined_count,
            too_large,
            not_hot,
            recursive,
            native,
            synchronized,
        }
    }
}

/// Inlining statistics
#[derive(Debug, Clone)]
pub struct InliningStats {
    pub total_candidates: usize,
    pub inlined_count: usize,
    pub too_large: usize,
    pub not_hot: usize,
    pub recursive: usize,
    pub native: usize,
    pub synchronized: usize,
}

impl Default for MethodInliner {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_inlining_config_default() {
        let config = InliningConfig::default();
        assert!(config.enabled);
        assert_eq!(config.max_inline_size, 35);
        assert_eq!(config.min_invocation_count, 250);
    }
    
    #[test]
    fn test_method_inliner_creation() {
        let inliner = MethodInliner::new();
        assert!(inliner.config.enabled);
    }
}
