//! Matrix Market file format example
//!
//! This example demonstrates the Matrix Market file format capabilities:
//! - Creating sparse matrices in Matrix Market format
//! - Writing and reading Matrix Market files
//! - Working with different matrix types and symmetries
//! - Converting between different representations

use scirs2_core::ndarray::{Array1, Array2};
use scirs2_io::error::Result;
use scirs2_io::matrix_market::{
    coo_to_sparse, read_dense_matrix, read_sparse_matrix, sparse_to_coo, write_dense_matrix,
    write_sparse_matrix, MMDataType, MMDenseMatrix, MMFormat, MMHeader, MMSparseMatrix, MMSymmetry,
    SparseEntry,
};
use std::fs;

#[allow(dead_code)]
fn main() -> Result<()> {
    println!("=== Matrix Market Example ===");

    // Example 1: Create and write a sparse matrix
    create_sparse_matrix_example()?;

    // Example 2: Create and write a dense matrix
    create_dense_matrix_example()?;

    // Example 3: Read and analyze matrices
    read_and_analyze_matrices()?;

    // Example 4: Demonstrate different matrix types
    demonstrate_matrix_types()?;

    // Example 5: Coordinate format conversions
    demonstrate_coordinate_conversions()?;

    println!("Matrix Market example completed successfully!");
    Ok(())
}

#[allow(dead_code)]
fn create_sparse_matrix_example() -> Result<()> {
    println!("\n1. Creating sparse matrix example...");

    // Create a sparse matrix header
    let header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Coordinate,
        data_type: MMDataType::Real,
        symmetry: MMSymmetry::General,
        comments: vec![
            "Example sparse matrix".to_string(),
            "Generated by scirs2-io Matrix Market example".to_string(),
        ],
    };

    // Create a 5x5 sparse matrix with some non-zero entries
    let mut entries = Vec::new();

    // Diagonal entries
    for i in 0..5 {
        entries.push(SparseEntry {
            row: i,
            col: i,
            value: (i + 1) as f64,
        });
    }

    // Some off-diagonal entries
    entries.push(SparseEntry {
        row: 0,
        col: 2,
        value: 0.5,
    });
    entries.push(SparseEntry {
        row: 1,
        col: 3,
        value: -1.2,
    });
    entries.push(SparseEntry {
        row: 2,
        col: 4,
        value: 2.3,
    });
    entries.push(SparseEntry {
        row: 3,
        col: 0,
        value: -0.8,
    });
    entries.push(SparseEntry {
        row: 4,
        col: 1,
        value: 1.7,
    });

    let sparse_matrix = MMSparseMatrix {
        header,
        rows: 5,
        cols: 5,
        nnz: entries.len(),
        entries,
    };

    // Write the sparse matrix to a file
    write_sparse_matrix("example_sparse.mtx", &sparse_matrix)?;

    println!("  Created sparse matrix:");
    println!("    Size: {}x{}", sparse_matrix.rows, sparse_matrix.cols);
    println!("    Non-zeros: {}", sparse_matrix.nnz);
    println!("    Format: {:?}", sparse_matrix.header.format);
    println!("    Data type: {:?}", sparse_matrix.header.data_type);
    println!("    Symmetry: {:?}", sparse_matrix.header.symmetry);
    println!("    Written to: example_sparse.mtx");

    Ok(())
}

#[allow(dead_code)]
fn create_dense_matrix_example() -> Result<()> {
    println!("\n2. Creating dense matrix example...");

    // Create a dense matrix header
    let header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Array,
        data_type: MMDataType::Real,
        symmetry: MMSymmetry::General,
        comments: vec![
            "Example dense matrix".to_string(),
            "3x3 test matrix with various values".to_string(),
        ],
    };

    // Create a 3x3 dense matrix
    #[rustfmt::skip]
    let data = Array2::from_shape_vec(
        (3, 3),
        vec![
            1.0, 2.0, 3.0,  // First row
            4.0, 5.0, 6.0,  // Second row
            7.0, 8.0, 9.0,  // Third row
        ]
    ).expect("Operation failed");

    let dense_matrix = MMDenseMatrix {
        header,
        rows: 3,
        cols: 3,
        data,
    };

    // Write the dense matrix to a file
    write_dense_matrix("example_dense.mtx", &dense_matrix)?;

    println!("  Created dense matrix:");
    println!("    Size: {}x{}", dense_matrix.rows, dense_matrix.cols);
    println!("    Format: {:?}", dense_matrix.header.format);
    println!("    Data type: {:?}", dense_matrix.header.data_type);
    println!("    Matrix:");
    for row in 0..dense_matrix.rows {
        print!("      [");
        for col in 0..dense_matrix.cols {
            print!("{:6.2}", dense_matrix.data[[row, col]]);
            if col < dense_matrix.cols - 1 {
                print!(", ");
            }
        }
        println!("]");
    }
    println!("    Written to: example_dense.mtx");

    Ok(())
}

#[allow(dead_code)]
fn read_and_analyze_matrices() -> Result<()> {
    println!("\n3. Reading and analyzing matrices...");

    // Read the sparse matrix
    println!("  Reading sparse matrix...");
    let sparse_matrix = read_sparse_matrix("example_sparse.mtx")?;

    println!("    Loaded sparse matrix:");
    println!("      Size: {}x{}", sparse_matrix.rows, sparse_matrix.cols);
    println!("      Non-zeros: {}", sparse_matrix.nnz);
    println!("      Comments: {:?}", sparse_matrix.header.comments);

    // Analyze sparsity
    let total_elements = sparse_matrix.rows * sparse_matrix.cols;
    let sparsity = 1.0 - (sparse_matrix.nnz as f64 / total_elements as f64);
    println!(
        "      Sparsity: {:.2}% ({} zeros out of {} total elements)",
        sparsity * 100.0,
        total_elements - sparse_matrix.nnz,
        total_elements
    );

    // Show some entries
    println!("      First 5 non-zero entries:");
    for entry in sparse_matrix.entries.iter().take(5) {
        println!(
            "        ({}, {}) = {}",
            entry.row + 1,
            entry.col + 1,
            entry.value
        );
    }

    // Read the dense matrix
    println!("  Reading dense matrix...");
    let dense_matrix = read_dense_matrix("example_dense.mtx")?;

    println!("    Loaded dense matrix:");
    println!("      Size: {}x{}", dense_matrix.rows, dense_matrix.cols);
    println!("      Comments: {:?}", dense_matrix.header.comments);
    println!("      Data shape: {:?}", dense_matrix.data.shape());

    // Calculate some statistics
    let sum = dense_matrix.data.sum();
    let mean = sum / (dense_matrix.rows * dense_matrix.cols) as f64;
    let min = dense_matrix
        .data
        .iter()
        .fold(f64::INFINITY, |a, &b| a.min(b));
    let max = dense_matrix
        .data
        .iter()
        .fold(f64::NEG_INFINITY, |a, &b| a.max(b));

    println!("      Statistics:");
    println!("        Sum: {:.2}", sum);
    println!("        Mean: {:.2}", mean);
    println!("        Min: {:.2}", min);
    println!("        Max: {:.2}", max);

    Ok(())
}

#[allow(dead_code)]
fn demonstrate_matrix_types() -> Result<()> {
    println!("\n4. Demonstrating different matrix types...");

    // Create a symmetric matrix
    println!("  Creating symmetric matrix...");
    let sym_header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Coordinate,
        data_type: MMDataType::Real,
        symmetry: MMSymmetry::Symmetric,
        comments: vec!["Symmetric matrix example".to_string()],
    };

    // For symmetric matrices, we only need to store the lower triangle
    let sym_entries = vec![
        SparseEntry {
            row: 0,
            col: 0,
            value: 2.0,
        }, // Diagonal
        SparseEntry {
            row: 1,
            col: 0,
            value: 1.0,
        }, // Lower triangle only
        SparseEntry {
            row: 1,
            col: 1,
            value: 3.0,
        }, // Diagonal
        SparseEntry {
            row: 2,
            col: 0,
            value: 0.5,
        }, // Lower triangle only
        SparseEntry {
            row: 2,
            col: 1,
            value: -0.8,
        }, // Lower triangle only
        SparseEntry {
            row: 2,
            col: 2,
            value: 4.0,
        }, // Diagonal
    ];

    let sym_matrix = MMSparseMatrix {
        header: sym_header,
        rows: 3,
        cols: 3,
        nnz: sym_entries.len(),
        entries: sym_entries,
    };

    write_sparse_matrix("symmetric_matrix.mtx", &sym_matrix)?;
    println!(
        "    Written symmetric matrix (3x3) with {} stored entries",
        sym_matrix.nnz
    );

    // Create a pattern matrix (no values, just structure)
    println!("  Creating pattern matrix...");
    let pattern_header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Coordinate,
        data_type: MMDataType::Pattern,
        symmetry: MMSymmetry::General,
        comments: vec!["Pattern matrix - structure only".to_string()],
    };

    // Pattern matrices don't store values, just positions
    let pattern_entries = vec![
        SparseEntry {
            row: 0,
            col: 0,
            value: 1.0,
        }, // Value ignored for pattern
        SparseEntry {
            row: 0,
            col: 3,
            value: 1.0,
        },
        SparseEntry {
            row: 1,
            col: 1,
            value: 1.0,
        },
        SparseEntry {
            row: 1,
            col: 4,
            value: 1.0,
        },
        SparseEntry {
            row: 2,
            col: 2,
            value: 1.0,
        },
        SparseEntry {
            row: 3,
            col: 0,
            value: 1.0,
        },
        SparseEntry {
            row: 3,
            col: 3,
            value: 1.0,
        },
        SparseEntry {
            row: 4,
            col: 1,
            value: 1.0,
        },
        SparseEntry {
            row: 4,
            col: 4,
            value: 1.0,
        },
    ];

    let pattern_matrix = MMSparseMatrix {
        header: pattern_header,
        rows: 5,
        cols: 5,
        nnz: pattern_entries.len(),
        entries: pattern_entries,
    };

    write_sparse_matrix("pattern_matrix.mtx", &pattern_matrix)?;
    println!(
        "    Written pattern matrix (5x5) with {} non-zero positions",
        pattern_matrix.nnz
    );

    // Create an integer matrix
    println!("  Creating integer matrix...");
    let int_header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Coordinate,
        data_type: MMDataType::Integer,
        symmetry: MMSymmetry::General,
        comments: vec!["Integer matrix example".to_string()],
    };

    let int_entries = vec![
        SparseEntry {
            row: 0,
            col: 0,
            value: 10.0,
        },
        SparseEntry {
            row: 1,
            col: 1,
            value: 20.0,
        },
        SparseEntry {
            row: 2,
            col: 2,
            value: 30.0,
        },
        SparseEntry {
            row: 0,
            col: 2,
            value: -5.0,
        },
        SparseEntry {
            row: 2,
            col: 0,
            value: 15.0,
        },
    ];

    let int_matrix = MMSparseMatrix {
        header: int_header,
        rows: 3,
        cols: 3,
        nnz: int_entries.len(),
        entries: int_entries,
    };

    write_sparse_matrix("integer_matrix.mtx", &int_matrix)?;
    println!(
        "    Written integer matrix (3x3) with {} entries",
        int_matrix.nnz
    );

    Ok(())
}

#[allow(dead_code)]
fn demonstrate_coordinate_conversions() -> Result<()> {
    println!("\n5. Demonstrating coordinate format conversions...");

    // Create some test data in coordinate format
    let rows = Array1::from(vec![0, 1, 2, 0, 2]);
    let cols = Array1::from(vec![0, 1, 2, 2, 0]);
    let values = Array1::from(vec![1.0, 2.0, 3.0, 0.5, -1.5]);

    println!("  Original coordinate arrays:");
    println!(
        "    Rows:   {:?}",
        rows.as_slice().expect("Operation failed")
    );
    println!(
        "    Cols:   {:?}",
        cols.as_slice().expect("Operation failed")
    );
    println!(
        "    Values: {:?}",
        values.as_slice().expect("Operation failed")
    );

    // Convert to Matrix Market sparse matrix
    let header = MMHeader {
        object: "matrix".to_string(),
        format: MMFormat::Coordinate,
        data_type: MMDataType::Real,
        symmetry: MMSymmetry::General,
        comments: vec!["Converted from coordinate arrays".to_string()],
    };

    let mm_matrix = coo_to_sparse(&rows, &cols, &values, (3, 3), header);

    println!("  Converted to Matrix Market format:");
    println!("    Size: {}x{}", mm_matrix.rows, mm_matrix.cols);
    println!("    Non-zeros: {}", mm_matrix.nnz);

    // Write to file
    write_sparse_matrix("converted_matrix.mtx", &mm_matrix)?;

    // Read it back and convert back to coordinate format
    let read_matrix = read_sparse_matrix("converted_matrix.mtx")?;
    let (back_rows, back_cols, back_values) = sparse_to_coo(&read_matrix);

    println!("  Converted back to coordinate arrays:");
    println!(
        "    Rows:   {:?}",
        back_rows.as_slice().expect("Operation failed")
    );
    println!(
        "    Cols:   {:?}",
        back_cols.as_slice().expect("Operation failed")
    );
    println!(
        "    Values: {:?}",
        back_values.as_slice().expect("Operation failed")
    );

    // Verify the round-trip conversion
    let rows_match = rows == back_rows;
    let cols_match = cols == back_cols;
    let values_match = values
        .iter()
        .zip(back_values.iter())
        .all(|(&a, &b)| (a - b).abs() < 1e-10);

    println!("  Round-trip conversion verification:");
    println!("    Rows match: {}", rows_match);
    println!("    Cols match: {}", cols_match);
    println!("    Values match: {}", values_match);

    if rows_match && cols_match && values_match {
        println!("    ✓ Round-trip conversion successful!");
    } else {
        println!("    ✗ Round-trip conversion failed!");
    }

    Ok(())
}

/// Clean up example files
#[allow(dead_code)]
fn cleanup_files() {
    let files = [
        "example_sparse.mtx",
        "example_dense.mtx",
        "symmetric_matrix.mtx",
        "pattern_matrix.mtx",
        "integer_matrix.mtx",
        "converted_matrix.mtx",
    ];

    for file in &files {
        let _ = fs::remove_file(file);
    }
}
