#!/usr/bin/env python3
"""
Enhanced documentation generator for the mrdna package using MkDocs with Material theme.

This script:
1. Scans the mrdna package structure
2. Generates markdown documentation files
3. Creates a navigable documentation website using MkDocs with Material theme
4. Adds support for API reference documentation through mkdocstrings
"""

import os
import sys
import inspect
import importlib
import subprocess
from pathlib import Path
import shutil
import yaml
import re
from textwrap import dedent
from collections import defaultdict

# Configure documentation paths
DOCS_DIR = Path("docs")
SITE_DIR = Path("site")
CONFIG_FILE = Path("mkdocs.yml")

# Core modules to document
MODULES_TO_DOCUMENT = {
    "core": [
        "mrdna.config",
        "mrdna.coords",
        "mrdna.reporting",
        "mrdna.segmentmodel",
        "mrdna.simulate",
        "mrdna.version"
    ],
    "model": [
        "mrdna.model.CanonicalNucleotideAtoms",
        "mrdna.model.dna_sequence",
        "mrdna.model.nbPot",
        "mrdna.model.spring_from_lp"
    ],
    "readers": [
        "mrdna.readers.cadnano_segments",
        "mrdna.readers.polygon_mesh",
        "mrdna.readers.segmentmodel_from_cadnano",
        "mrdna.readers.segmentmodel_from_lists",
        "mrdna.readers.segmentmodel_from_oxdna",
        "mrdna.readers.segmentmodel_from_pdb",
        "mrdna.readers.segmentmodel_from_scadnano"
    ],
    "arbdmodel": [
        "mrdna.arbdmodel.coords",
        "mrdna.arbdmodel.grid",
        "mrdna.arbdmodel.interactions",
        "mrdna.arbdmodel.polymer"
    ]
}

def check_command_exists(command):
    """Check if a command exists in the system path."""
    try:
        subprocess.run([command, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
        return True
    except FileNotFoundError:
        return False

def extract_module_info(module_name):
    """Extract useful information from a module."""
    try:
        module = importlib.import_module(module_name)
        docstring = module.__doc__ or ""
        
        # If the module has a __version__ attribute, include it
        version = getattr(module, "__version__", None)
        
        # Extract classes
        classes = {}
        for name, obj in inspect.getmembers(module, inspect.isclass):
            if obj.__module__ == module_name:
                classes[name] = {
                    "docstring": obj.__doc__ or "",
                    "methods": {}
                }
                
                # Extract methods
                for method_name, method in inspect.getmembers(obj, inspect.isfunction):
                    if not method_name.startswith("_") or method_name == "__init__":
                        classes[name]["methods"][method_name] = {
                            "docstring": method.__doc__ or "",
                            "signature": str(inspect.signature(method))
                        }
        
        # Extract functions
        functions = {}
        for name, obj in inspect.getmembers(module, inspect.isfunction):
            if obj.__module__ == module_name and not name.startswith("_"):
                functions[name] = {
                    "docstring": obj.__doc__ or "",
                    "signature": str(inspect.signature(obj))
                }
        
        return {
            "docstring": docstring,
            "version": version,
            "classes": classes,
            "functions": functions,
            "module_name": module_name
        }
    except (ImportError, AttributeError) as e:
        print(f"Error extracting info from {module_name}: {e}")
        return {
            "docstring": f"Module documentation for {module_name}. (Error: {e})",
            "version": None,
            "classes": {},
            "functions": {},
            "module_name": module_name
        }

def create_module_md(module_name, docs_dir):
    """Create Markdown documentation for a Python module."""
    try:
        # Extract module name and subpackage for organization
        parts = module_name.split('.')
        simple_name = parts[-1]
        subpackage = parts[1] if len(parts) > 2 else 'core'
        
        # Get module information
        module_info = extract_module_info(module_name)
        
        # Start building the Markdown content
        content = [
            f"# {simple_name}",
            "",
        ]
        
        # Add module docstring
        if module_info["docstring"]:
            content.append(module_info["docstring"].strip())
            content.append("")
        else:
            content.append(f"Module documentation for {simple_name}.")
            content.append("")
        
        # Add version if available
        if module_info["version"]:
            content.append(f"**Version:** {module_info['version']}")
            content.append("")
        
        # Add mkdocstrings directive for API docs
        content.extend([
            "## API Reference",
            "",
            f"::: {module_name}",
            "    handler: python",
            "    selection:",
            "      docstring_style: google",
            "    rendering:",
            "      show_source: true",
            ""
        ])
        
        # Create directory for module documentation
        output_dir = docs_dir / "api" / subpackage
        output_dir.mkdir(exist_ok=True, parents=True)
        
        # Write the Markdown file
        output_path = output_dir / f"{simple_name}.md"
        with open(output_path, 'w') as f:
            f.write('\n'.join(content))
            
        print(f"Created module documentation: {output_path}")
        return output_path.relative_to(docs_dir), simple_name
        
    except Exception as e:
        print(f"Error processing module {module_name}: {e}")
        return None, None

def create_subpackage_index(subpackage, module_names, docs_dir):
    """Create an index Markdown file for a subpackage."""
    # Create index content
    subpackage_title = subpackage.capitalize()
    content = [
        f"# {subpackage_title} Modules",
        "",
        f"This section covers the modules in the {subpackage} subpackage.",
        "",
        "## Modules",
        "",
    ]
    
    # Process each module in the subpackage
    module_info = []
    for module_name in sorted(module_names):
        rel_path, simple_name = create_module_md(module_name, docs_dir)
        if rel_path and simple_name:
            full_module_name = module_name
            brief_desc = ""
            try:
                mod = importlib.import_module(module_name)
                if mod.__doc__:
                    brief_desc = mod.__doc__.strip().split('\n')[0]
            except:
                pass
            module_info.append((rel_path, simple_name, full_module_name, brief_desc))
    
    # If no modules were successfully processed, skip creating the index
    if not module_info:
        print(f"No modules were successfully processed for {subpackage}, skipping index")
        return None
    
    # Add module links to index with descriptions
    for rel_path, simple_name, full_module_name, brief_desc in sorted(module_info, key=lambda x: x[1]):
        # Fix the relative path to ensure it's correct from the packages directory
        # This ensures links are properly resolved by MkDocs
        fixed_path = f"../{rel_path}"
        content.append(f"- [{simple_name}]({fixed_path}): {brief_desc}")
    
    # Create directory for subpackage documentation
    output_dir = docs_dir / "packages"
    output_dir.mkdir(exist_ok=True, parents=True)
    
    # Write the index file
    index_path = output_dir / f"{subpackage}.md"
    with open(index_path, 'w') as f:
        f.write('\n'.join(content))
        
    print(f"Created subpackage index: {index_path}")
    return index_path.relative_to(docs_dir)

def create_main_index(subpackages, docs_dir):
    """Create the main index.md file."""
    content = [
        "# mrDNA Documentation",
        "",
        "Welcome to the documentation for mrDNA!",
        "",
        "mrDNA is a molecular dynamics package for DNA modeling and simulation.",
        "",
        "## Package Structure",
        "",
        "The package is organized into the following subpackages:",
        "",
    ]
    
    # Add each subpackage to the list
    for subpackage in sorted(subpackages):
        subpackage_title = subpackage.capitalize()
        content.append(f"- [{subpackage_title}](packages/{subpackage}.md)")
    
    # Write the index file
    index_path = docs_dir / "index.md"
    with open(index_path, 'w') as f:
        f.write('\n'.join(content))
        
    print(f"Created main index: {index_path}")

def create_installation_page(docs_dir):
    """Create the installation page."""
    with open(Path("README.md"), 'r') as f:
        readme_content = f.read()
    
    # Extract installation instructions from README
    installation_match = re.search(r'## Installation(.*?)(?:##|\Z)', readme_content, re.DOTALL)
    if installation_match:
        installation_content = installation_match.group(1).strip()
    else:
        installation_content = """
        Please refer to the README file for installation instructions.
        """
    
    # Create a more detailed installation guide
    content = [
        "# Installation",
        "",
        "## Requirements",
        "",
        "mrDNA requires the following dependencies:",
        "",
        "- Python 3.6+",
        "- NumPy",
        "- SciPy",
        "- MDAnalysis (for certain functionality)",
        "- CUDA Toolkit (for GPU acceleration)",
        "- ARBD simulation engine",
        "",
        "## Installation Instructions",
        "",
        installation_content,
        "",
        "## Verifying Installation",
        "",
        "After installation, you can verify that mrDNA is working correctly by running a simple test:",
        "",
        "```python",
        "import mrdna",
        "print(mrdna.__version__)",
        "```",
        "",
        "This should print the version number of the installed mrDNA package.",
    ]
    
    # Write the installation file
    install_path = docs_dir / "installation.md"
    with open(install_path, 'w') as f:
        f.write('\n'.join(content))
        
    print(f"Created installation page: {install_path}")

def create_getting_started(docs_dir):
    """Create the getting started page."""
    content = [
        "# Getting Started",
        "",
        "This guide will help you get started with using mrDNA for DNA molecular modeling.",
        "",
        "## Basic Usage",
        "",
        "Here's a simple example that creates a DNA structure:",
        "",
        "```python",
        "# Import necessary modules",
        "from mrdna.segmentmodel import SegmentModel",
        "from mrdna.model.dna_sequence import DNASequence",
        "",
        "# Create a segment model",
        "model = SegmentModel()",
        "",
        "# Add a DNA segment with a specific sequence",
        "sequence = \"GATTACA\"",
        "model.add_segment(sequence)",
        "",
        "# Save the model to a PDB file",
        "model.to_pdb(\"example.pdb\")",
        "```",
        "",
        "## Working with Existing Structures",
        "",
        "mrDNA can also read structures from various file formats:",
        "",
        "```python",
        "# Import the appropriate reader",
        "from mrdna.readers import read_cadnano",
        "",
        "# Load a structure from a cadnano file",
        "model = read_cadnano(\"my_structure.json\")",
        "",
        "# Analyze the structure",
        "num_nucleotides = model.count_nucleotides()",
        "print(f\"Structure contains {num_nucleotides} nucleotides\")",
        "```",
        "",
        "## Running a Simulation",
        "",
        "To run a simulation with mrDNA:",
        "",
        "```python",
        "from mrdna.simulate import multiresolution_simulation",
        "",
        "# Set up simulation parameters",
        "output_name = \"my_simulation\"",
        "gpu = 0  # GPU device to use",
        "",
        "# Run the simulation",
        "result_directory = multiresolution_simulation(",
        "    model,",
        "    output_name=output_name,",
        "    gpu=gpu,",
        "    coarse_steps=1e7,  # Number of coarse-grained steps",
        "    fine_steps=1e7,    # Number of fine-grained steps",
        ")",
        "```",
        "",
        "## Next Steps",
        "",
        "Explore the API reference documentation for more detailed information on each module and function.",
    ]
    
    # Write the getting started file
    getting_started_path = docs_dir / "getting_started.md"
    with open(getting_started_path, 'w') as f:
        f.write('\n'.join(content))
        
    print(f"Created getting started page: {getting_started_path}")

def create_tutorials(docs_dir):
    """Create tutorials directory with example files."""
    tutorials_dir = docs_dir / "tutorials"
    tutorials_dir.mkdir(exist_ok=True)
    
    # Create a basic tutorial
    basic_tutorial = [
        "# Basic DNA Origami Tutorial",
        "",
        "This tutorial walks through the process of creating a simple DNA origami structure using mrDNA.",
        "",
        "## Loading a design from cadnano",
        "",
        "```python",
        "from mrdna.readers import read_cadnano",
        "",
        "# Load a cadnano design",
        "model = read_cadnano('6hb.json')",
        "```",
        "",
        "## Visualizing the structure",
        "",
        "You can generate PDB files to visualize the structure in molecular viewers like VMD or PyMOL:",
        "",
        "```python",
        "# Create a coarse-grained model",
        "model.generate_bead_model(max_basepairs_per_bead=5, max_nucleotides_per_bead=5)",
        "",
        "# Output PDB files",
        "model.write_pdb('6hb_cg.pdb')",
        "```",
        "",
        "## Running a simulation",
        "",
        "```python",
        "from mrdna.simulate import multiresolution_simulation",
        "",
        "# Run a multi-resolution simulation",
        "result_dir = multiresolution_simulation(",
        "    model, ",
        "    output_name='6hb',",
        "    gpu=0,",
        "    coarse_steps=5e7,",
        "    fine_steps=5e7",
        ")",
        "```",
    ]
    
    with open(tutorials_dir / "basic_origami.md", 'w') as f:
        f.write('\n'.join(basic_tutorial))
    
    # Create a tutorial index
    tutorial_index = [
        "# Tutorials",
        "",
        "This section contains tutorials for using mrDNA for different applications.",
        "",
        "## Available Tutorials",
        "",
        "- [Basic DNA Origami](basic_origami.md): Create and simulate a simple DNA origami structure",
    ]
    
    with open(tutorials_dir / "index.md", 'w') as f:
        f.write('\n'.join(tutorial_index))
    
    print(f"Created tutorials directory: {tutorials_dir}")

def create_mkdocs_config(subpackages):
    """Create the MkDocs configuration file."""
    nav = [
        {"Home": "index.md"},
        {"Installation": "installation.md"},
        {"Getting Started": "getting_started.md"},
        {"Tutorials": "tutorials/index.md"},
    ]
    
    # Add API reference to navigation
    api_nav = []
    for subpackage in sorted(subpackages):
        subpackage_title = subpackage.capitalize()
        api_nav.append({subpackage_title: f"packages/{subpackage}.md"})
    
    nav.append({"API Reference": api_nav})
    
    # Create the configuration
    config = {
        "site_name": "mrDNA Documentation",
        "site_description": "Documentation for the mrDNA DNA molecular modeling package",
        "site_author": "Chris Maffeo",
        "repo_url": "https://gitlab.engr.illinois.edu/tbgl/tools/mrdna",
        "theme": {
            "name": "material",
            "palette": {
                "primary": "blue grey",
                "accent": "light blue"
            },
            "features": [
                "navigation.instant",
                "navigation.tracking",
                "navigation.expand",
                "navigation.indexes",
                "navigation.top",
                "search.highlight",
                "search.share",
                "content.code.copy"
            ],
            "icon": {
                "repo": "fontawesome/brands/gitlab"
            }
        },
        "markdown_extensions": [
            "pymdownx.highlight",
            "pymdownx.superfences",
            "pymdownx.tabbed",
            "pymdownx.arithmatex",
            "admonition",
            "footnotes",
            "attr_list",
            "pymdownx.details",
            "pymdownx.emoji",
            "toc"
        ],
        "plugins": [
            "search",
            "mkdocstrings",
            {
                "autorefs": {}
            }
        ],
        "nav": nav,
        "extra": {
            "social": [
                {
                    "icon": "fontawesome/brands/gitlab",
                    "link": "https://gitlab.engr.illinois.edu/tbgl/tools/mrdna",
                    "name": "mrDNA on GitLab"
                }
            ]
        }
    }
    
    # Write the configuration file
    with open(CONFIG_FILE, 'w') as f:
        yaml.dump(config, f, default_flow_style=False, sort_keys=False)
        
    print(f"Created MkDocs configuration file: {CONFIG_FILE}")

def build_docs():
    """Build the documentation using MkDocs."""
    try:
        print("\nBuilding documentation...")
        subprocess.run(['mkdocs', 'build'], check=True)
        print(f"\nDocumentation successfully built. You can view it at: {SITE_DIR / 'index.html'}")
        return True
    except subprocess.CalledProcessError as e:
        print(f"Error building documentation: {e}")
        return False
    except FileNotFoundError:
        print("Could not find mkdocs. Make sure MkDocs is installed and in your PATH.")
        return False

def check_dependencies():
    """Check if required dependencies are installed."""
    missing_deps = []
    
    if not check_command_exists("mkdocs"):
        missing_deps.append("mkdocs")
    
    # Check for mkdocstrings - try multiple ways
    try:
        import mkdocstrings
    except ImportError:
        try:
            # Try alternative import path
            subprocess.run([sys.executable, "-c", "import mkdocstrings"], 
                          check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except:
            missing_deps.append("mkdocstrings")
    
    # Check for mkdocs-material
    try:
        # First try direct import
        import material
    except ImportError:
        try:
            # Alternative check: try to import the theme module
            subprocess.run([sys.executable, "-c", "import mkdocs.themes.material"], 
                          check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except:
            # Another way to check if a theme is installed
            try:
                # Check if mkdocs recognizes the theme
                result = subprocess.run(["mkdocs", "build", "--theme", "material", "--dry-run"],
                                      check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                if result.returncode != 0:
                    missing_deps.append("mkdocs-material")
            except:
                missing_deps.append("mkdocs-material")
        
    if missing_deps:
        print("Missing dependencies: " + ", ".join(missing_deps))
        print("\nPlease install the required dependencies with:")
        print(f"pip install {' '.join(missing_deps)}")
        print("\nFor better API documentation, also install:")
        print("pip install \"mkdocstrings[python]\"")
        
        response = input("\nDo you want to continue anyway? (y/n): ")
        if response.lower() in ('y', 'yes'):
            return True
        return False
        
    return True

def main():
    """Generate documentation for the mrdna package using MkDocs."""
    # Check dependencies
    if not check_dependencies():
        print("\nProceeding with a warning: Some dependencies may be missing.")
        print("The documentation generation might not complete successfully.")
    
    # Create or clean docs directory
    if DOCS_DIR.exists():
        shutil.rmtree(DOCS_DIR)
    DOCS_DIR.mkdir()
    
    # Generate documentation for each subpackage in the predefined list
    for subpackage, module_list in MODULES_TO_DOCUMENT.items():
        create_subpackage_index(subpackage, module_list, DOCS_DIR)
    
    # Create main index and additional pages
    create_main_index(MODULES_TO_DOCUMENT.keys(), DOCS_DIR)
    create_installation_page(DOCS_DIR)
    create_getting_started(DOCS_DIR)
    create_tutorials(DOCS_DIR)
    
    # Create MkDocs configuration
    create_mkdocs_config(MODULES_TO_DOCUMENT.keys())
    
    print(f"\nDocumentation files generated in {DOCS_DIR}")
    
    # Build the documentation
    response = input("\nWould you like to build the HTML documentation now? (y/n): ")
    if response.lower() in ('y', 'yes'):
        success = build_docs()
        if success:
            print("\nTo preview the documentation with a local server, run:")
            print("mkdocs serve")
    else:
        print("\nTo build the documentation later, run:")
        print("mkdocs build")
        print("\nTo preview the documentation with a local server, run:")
        print("mkdocs serve")

if __name__ == "__main__":
    main()