/**
 * @file _gconcord.cpp
 * @brief Pybind11 bindings for the GCONCORD C++ library
 * 
 * This file provides Python bindings for the unified CONCORD algorithms,
 * maintaining backward compatibility while exposing the full C++ API.
 */

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>

#include "concord_unified.hpp"

namespace py = pybind11;
using namespace concord;

PYBIND11_MODULE(_gconcord, m) {
    m.doc() = "GCONCORD: Unified CONCORD Package for Sparse Precision Matrix Estimation";
    
    // Bind Algorithm enum
    py::enum_<Algorithm>(m, "Algorithm", "Available CONCORD algorithms")
        .value("COORDINATE_DESCENT", Algorithm::COORDINATE_DESCENT, "Standard coordinate descent (concordC)")
        .value("COORDINATE_HIGH_DIM", Algorithm::COORDINATE_HIGH_DIM, "High-dimensional coordinate descent (concordC2)")
        .value("COORDINATE_BLAS", Algorithm::COORDINATE_BLAS, "BLAS-optimized coordinate descent (concordC3)")
        .value("ISTA", Algorithm::ISTA, "Matrix-based ISTA algorithm")
        .value("SYMLASSO", Algorithm::SYMLASSO, "Symmetric Lasso (symlassoC)")
        .value("SYMLASSO_ITERATES", Algorithm::SYMLASSO_ITERATES, "Symmetric Lasso with iterate saving (symlassoC2)")
        .value("AUTO", Algorithm::AUTO, "Automatic algorithm selection")
        .export_values();
    
    // Bind ConcordOptions class
    py::class_<ConcordOptions>(m, "ConcordOptions", "Configuration options for CONCORD algorithms")
        .def(py::init<>())
        .def_readwrite("algorithm", &ConcordOptions::algorithm, "Algorithm to use")
        .def_readwrite("tolerance", &ConcordOptions::tolerance, "Convergence tolerance")
        .def_readwrite("max_iterations", &ConcordOptions::max_iterations, "Maximum number of iterations")
        .def_readwrite("save_iterates", &ConcordOptions::save_iterates, "Save iteration history")
        .def_readwrite("penalize_diagonal", &ConcordOptions::penalize_diagonal, "Penalize diagonal elements")
        .def_readwrite("lambda_scale", &ConcordOptions::lambda_scale, "Lambda scaling factor")
        .def_readwrite("verbose", &ConcordOptions::verbose, "Verbose output");
    
    // Bind ConcordResult class
    py::class_<ConcordResult>(m, "ConcordResult", "Result structure containing solution and diagnostics")
        .def(py::init<>())
        .def_readonly("omega", &ConcordResult::omega, "Solution sparse matrix")
        .def_readonly("objective_values", &ConcordResult::objective_values, "Objective function values")
        .def_readonly("convergence_metrics", &ConcordResult::convergence_metrics, "Convergence metrics per iteration")
        .def_readonly("inner_iterations", &ConcordResult::inner_iterations, "Inner iteration counts")
        .def_readonly("total_iterations", &ConcordResult::total_iterations, "Total number of iterations")
        .def_readonly("algorithm_used", &ConcordResult::algorithm_used, "Algorithm that was used")
        .def_readonly("converged", &ConcordResult::converged, "Whether algorithm converged")
        .def_readonly("final_tolerance", &ConcordResult::final_tolerance, "Final convergence metric")
        .def_readonly("convergence_reason", &ConcordResult::convergence_reason, "Reason for termination")
        .def_readonly("iterates", &ConcordResult::iterates, "Full solution path (if requested)");
    
    // Bind ConcordSolver class
    py::class_<ConcordSolver>(m, "ConcordSolver", "Main solver class for CONCORD algorithms")
        .def(py::init<const ConcordOptions&>(), py::arg("options") = ConcordOptions())
        .def("solve", static_cast<ConcordResult (ConcordSolver::*)(const Eigen::MatrixXd&, double)>(&ConcordSolver::solve),
             "Solve with scalar lambda", py::arg("S"), py::arg("lambda_scalar"))
        .def("solve", static_cast<ConcordResult (ConcordSolver::*)(const Eigen::MatrixXd&, const Eigen::MatrixXd&)>(&ConcordSolver::solve),
             "Solve with matrix lambda", py::arg("S"), py::arg("Lambda"))
        .def("solve_from_data", &ConcordSolver::solve_from_data,
             "Solve from raw data matrix", py::arg("data"), py::arg("lambda_scalar"));
    
    // Bind convenience functions
    m.def("concord", static_cast<ConcordResult (*)(const Eigen::MatrixXd&, double, const ConcordOptions&)>(&concord::concord),
          "Convenience function for solving CONCORD problems",
          py::arg("S"), py::arg("lambda_scalar"), py::arg("options") = ConcordOptions());
    
    m.def("concord_from_data", &concord::concord_from_data,
          "Convenience function for solving from raw data",
          py::arg("data"), py::arg("lambda_scalar"), py::arg("options") = ConcordOptions());
    
    // Bind utility functions
    py::module_ utils = m.def_submodule("utils", "Utility functions");
    utils.def("soft_threshold", &utils::soft_threshold, "Soft thresholding operator", py::arg("x"), py::arg("threshold"));
    utils.def("linear_index", &utils::linear_index, "Linear index for symmetric matrix storage", py::arg("i"), py::arg("j"), py::arg("p"));
    
    // Bind sparse matrix conversion helpers
    m.def("to_sparse_matrix", [](const Eigen::MatrixXd& dense) {
        return Eigen::SparseMatrix<double>(dense.sparseView());
    }, "Convert dense matrix to sparse matrix");
    
    m.def("to_dense_matrix", [](const Eigen::SparseMatrix<double>& sparse) {
        return Eigen::MatrixXd(sparse);
    }, "Convert sparse matrix to dense matrix");
    
    // Version information
    m.attr("__version__") = "1.0.0";
}