
//! # Ockham - OR toolkit
//!
//! comprehensive OR toolkit for LP, optimization & math modeling in rust
//!
//! ## Features
//!
//! - **Linear Programming**: simplex solver w/ 2-phase method
//! - **Problem Modeling**: builder pattern for easy problem construction  
//! - **Variable Types**: continuous, int, binary vars
//! - **Error Handling**: comprehensive error types & validation
//! - **Numerical Stability**: scaling, presolve, validation utils
//! - **Solver Framework**: extensible solver trait for diff algorithms
//! - **Diagnostics**: detailed solver stats & solution info
//!
//! ## Quick Start
//!
//! ```rust
//! use ockham::prelude::*;
//!
//! // simple LP: max 3x + 2y s.t. x + y <= 10, x,y >= 0
//! let problem = ProblemBuilder::new()
//!     .add_non_negative_variable("x")?
//!     .add_non_negative_variable("y")?
//!     .add_constraint_by_name(
//!         &[("x", 1.0), ("y", 1.0)],
//!         ConstraintType::LessEqual,
//!         10.0,
//!     )?
//!     .objective_by_name(
//!         &[("x", 3.0), ("y", 2.0)],
//!         ObjectiveType::Maximize,
//!     )?
//!     .build()?;
//!
//! // solve it
//! let solution = solve(&problem)?;
//!
//! if solution.is_optimal() {
//!     println!("found optimal solution!");
//!     println!("x = {:.2}", solution.variables[0]);
//!     println!("y = {:.2}", solution.variables[1]);
//!     println!("obj = {:.2}", solution.objective_value);
//! }
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ## modules
//!
//! - [`problem`]: problem modeling, vars, constraints & objectives
//! - [`solvers`]: solver implementations & framework
//! - [`utils`]: presolve, scaling, validation stuff
//! - [`errors`]: error types & handling

pub mod errors;
pub mod problem;
pub mod solvers;
pub mod utils;

// legacy support - re-export old names for backwards compat
pub mod simplex {
    pub use crate::solvers::simplex::*;
    pub use crate::problem::{Problem as LPProblem};
    pub use crate::solvers::simplex::SimplexTableau;
    
    // re-export conversion fn w/ old name
    pub fn to_standard_form(problem: &crate::problem::Problem) -> crate::errors::ProblemResult<(crate::problem::Problem, Vec<usize>)> {
        let (standard, basic_vars, _) = problem.to_standard_form()?;
        Ok((standard, basic_vars))
    }
}

// re-export old types for backwards compat
pub use problem::{
    ConstraintType::{LessEqual as Le, GreaterEqual as Ge, Equal as Eq},
    Problem as LPProblem,
};

/// convenient prelude module
pub mod prelude {
    pub use crate::problem::*;
    pub use crate::solvers::*;
    pub use crate::errors::*;
    pub use crate::utils::*;
}

// re-export commonly used items at crate root
pub use errors::{OptimizationError, ProblemError, Result};
pub use problem::{Problem, ProblemBuilder, Variable, Constraint, Objective, 
                  ConstraintType, ObjectiveType, VariableType};
pub use solvers::{solve, solve_with_config, Solver, Solution, SolutionStatus, 
                  SolverConfig, SimplexSolver};
pub use utils::{Presolve, Scaling, Validator};

#[cfg(test)]
mod tests {
    use super::*;
    use approx::assert_abs_diff_eq;

    #[test]
    fn test_simple_problem_solve() {
        let problem = ProblemBuilder::new()
            .add_non_negative_variable("x").unwrap()
            .add_non_negative_variable("y").unwrap()
            .add_constraint_by_name(
                &[("x", 1.0), ("y", 1.0)],
                ConstraintType::LessEqual,
                10.0,
            ).unwrap()
            .objective_by_name(
                &[("x", 3.0), ("y", 2.0)],
                ObjectiveType::Maximize,
            ).unwrap()
            .build()
            .unwrap();

        let solution = solve(&problem).unwrap();
        
        assert!(solution.is_optimal());
        assert_eq!(solution.variables.len(), 2);
        
        // should find corner solution at (10, 0) w/ obj val 30
        assert_abs_diff_eq!(solution.variables[0], 10.0, epsilon = 1e-6);
        assert_abs_diff_eq!(solution.variables[1], 0.0, epsilon = 1e-6);
        assert_abs_diff_eq!(solution.objective_value, 30.0, epsilon = 1e-6);
    }

    #[test]
    fn test_backward_compatibility() {
        // test that old API still works
        let var1 = Variable::new_non_negative("x1");
        let var2 = Variable::new_non_negative("x2");
        let vars = vec![var1, var2];

        let cons1 = Constraint::new(vec![2.0, 3.0], ConstraintType::LessEqual, 10.0).unwrap();
        let cons2 = Constraint::new(vec![1.0, 1.0], ConstraintType::GreaterEqual, 2.0).unwrap();
        let constraints = vec![cons1, cons2];

        let obj = Objective::new(vec![5.0, 7.0], ObjectiveType::Maximize).unwrap();

        let lp_problem = LPProblem::new(vars.clone(), constraints.clone(), obj.clone()).unwrap();

        assert_eq!(lp_problem.variables.len(), 2);
        assert_eq!(lp_problem.constraints.len(), 2);
        assert_eq!(lp_problem.objective.coefficients, vec![5.0, 7.0]);
    }

    #[test]
    fn test_builder_pattern() {
        let problem = ProblemBuilder::new()
            .name("Test Problem")
            .add_variable("x1", 0.0, 100.0).unwrap()
            .add_binary_variable("b1").unwrap()
            .add_integer_variable("i1", 0.0, 10.0).unwrap()
            .add_constraint(vec![1.0, 2.0, 3.0], ConstraintType::LessEqual, 50.0).unwrap()
            .objective(vec![10.0, 5.0, 1.0], ObjectiveType::Maximize).unwrap()
            .build()
            .unwrap();

        assert_eq!(problem.name, Some("Test Problem".to_string()));
        assert_eq!(problem.num_variables(), 3);
        assert_eq!(problem.variables[0].var_type, VariableType::Continuous);
        assert_eq!(problem.variables[1].var_type, VariableType::Binary);
        assert_eq!(problem.variables[2].var_type, VariableType::Integer);
    }

    #[test]
    fn test_solver_configuration() {
        let config = SolverConfig::new()
            .with_max_iterations(1000)
            .with_optimality_tolerance(1e-9)
            .verbose()
            .with_option("pivot_rule", "steepest_edge");

        assert_eq!(config.max_iterations, 1000);
        assert_eq!(config.optimality_tolerance, 1e-9);
        assert!(config.verbose);
        assert_eq!(config.get_option("pivot_rule"), Some("steepest_edge"));
    }

    #[test]
    fn test_presolve_and_scaling() {
        let problem = ProblemBuilder::new()
            .add_variable("x", 5.0, 5.0).unwrap() // Fixed variable
            .add_non_negative_variable("y").unwrap()
            .add_constraint_by_name(
                &[("x", 1000.0), ("y", 0.001)], // Poorly scaled
                ConstraintType::LessEqual,
                10.0,
            ).unwrap()
            .objective_by_name(
                &[("x", 1.0), ("y", 2.0)],
                ObjectiveType::Maximize,
            ).unwrap()
            .build()
            .unwrap();

        // Test presolve
        let presolve = Presolve::new();
        let presolve_result = presolve.apply(&problem).unwrap();
        assert!(!presolve_result.fixed_variables.is_empty());
        
        // Test scaling
        let scaling = Scaling::new(utils::ScalingMethod::Equilibration);
        let scaling_result = scaling.apply(&problem).unwrap();
        assert!(scaling_result.statistics.condition_number_after < 
                scaling_result.statistics.condition_number_before);

        // Test validation
        let validator = Validator::new();
        let validation_result = validator.validate(&problem);
        assert!(validation_result.is_valid);
        assert!(!validation_result.warnings.is_empty()); // Should warn about scaling
    }
}
