# ──────────────────────────────────────────────────────────────
# Project build configuration for ManipulaPy
# ──────────────────────────────────────────────────────────────

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

# ------------------------------------------------------------------
[project]
name = "ManipulaPy"
version = "1.1.3"                      
authors = [
    { name = "Mohamed Aboelnasr", email = "aboelnasr1997@gmail.com" },
]
description = "A comprehensive Python package for robotic manipulator analysis and control"
readme = "README.md"
license = { text = "AGPL-3.0-or-later" }
requires-python = ">=3.8"

classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Topic :: Scientific/Engineering :: Physics",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Operating System :: OS Independent",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
]
keywords = [
    "robotics",
    "kinematics",
    "dynamics",
    "trajectory-planning",
    "simulation",
    "cuda",
    "computer-vision",
    "control-systems",
    "manipulator",
]

# ------------------------------------------------------------------
# Core runtime dependencies
dependencies = [
    "numpy>=1.19.2",
    "scipy>=1.5.2", 
    "matplotlib>=3.3",
    "numba>=0.55",
    "pybullet>=3.0.6",
    "urchin>=0.0.27",
    "trimesh>=3.9.14",
    "torch>=1.8.0",
    "scikit-learn>=1.0",
    "opencv-python>=4.5",
    "ultralytics>=8.0",
    "pillow>=8.0.0",
]

[project.optional-dependencies]
# Core optional dependencies
core-optional = [
    "scikit-learn>=1.0",
]



# Vision dependencies  
vision = [
    "opencv-python>=4.5",
    "ultralytics>=8.0",
    "pillow>=8.0.0",
]

# Vision dependencies for headless environments (CI-friendly)
vision-headless = [
    "opencv-python-headless>=4.5",
    "ultralytics>=8.0",
    "pillow>=8.0.0",
]

# AI/ML dependencies
ml = [
    "torch>=1.8.0",
    "scikit-learn>=1.0",
]

# GPU support
gpu-cuda11 = ["cupy-cuda11x>=10.0.0"]
gpu-cuda12 = ["cupy-cuda12x>=12.0.0"]
gpu-rocm = ["cupy-rocm-4-3>=10.0.0"]
gpu-pycuda = ["pycuda>=2021.1"]
gpu = ["cupy-cuda11x>=10.0.0"]

# Development and testing dependencies
dev = [
    "pytest>=6.0.0",
    "pytest-cov>=3.0.0",
    "pytest-mock>=3.6.0",
    "pytest-xvfb>=2.0.0",
    "pytest-timeout>=2.1.0",
    "pytest-benchmark>=4.0.0",
    "coverage[toml]>=6.0.0",
    "black>=22.0.0",
    "flake8>=4.0.0",
    "isort>=5.10.0",
    "mypy>=0.950",
    "pre-commit>=2.15.0",
    "tox>=4.0.0",
]

# Documentation dependencies
docs = [
    "sphinx>=4.0.0",
    "sphinx-rtd-theme>=1.0.0",
    "myst-parser>=0.15.0",
    "sphinx-autodoc-typehints>=1.12.0",
    "nbsphinx>=0.8.0",
    "jupyter>=1.0.0",
]

# CI-specific dependencies
ci = [
    "pytest>=6.0.0",
    "pytest-cov>=3.0.0",
    "pytest-mock>=3.6.0",
    "pytest-xvfb>=2.0.0",
    "pytest-timeout>=2.1.0",
    "coverage[toml]>=6.0.0",
    "opencv-python-headless>=4.5",
    "tomli>=1.2.0;python_version<'3.11'",
]

# Testing combinations
test-core = ["ManipulaPy[dev]"]
test-simulation = ["ManipulaPy[dev,simulation]"]
test-vision = ["ManipulaPy[dev,vision-headless]"]
test-gpu = ["ManipulaPy[dev,gpu-cuda11]"]
test-full = ["ManipulaPy[dev,simulation,vision-headless,ml]"]
test-ci = ["ManipulaPy[ci]"]

# All optional dependencies
all = ["ManipulaPy[core-optional,simulation,vision,ml,docs]"]

[project.urls]
Homepage = "https://github.com/boelnasr/ManipulaPy"
Documentation = "https://manipulapy.readthedocs.io/"
Repository = "https://github.com/boelnasr/ManipulaPy.git"
"Bug Tracker" = "https://github.com/boelnasr/ManipulaPy/issues"
"Changelog" = "https://github.com/boelnasr/ManipulaPy/blob/main/CHANGELOG.md"
"Funding" = "https://github.com/sponsors/boelnasr"

# ------------------------------------------------------------------
[tool.setuptools]
packages = ["ManipulaPy", "ManipulaPy.ManipulaPy_data", "ManipulaPy.ManipulaPy_data.ur5", "ManipulaPy.ManipulaPy_data.xarm"]
include-package-data = true

[tool.setuptools.dynamic]
# Optional: make version dynamic if you have a _version.py file
# version = {attr = "ManipulaPy._version.__version__"}

[tool.setuptools.package-data]
"ManipulaPy" = [
    "*.yml",
    "*.yaml",
    "*.json",
    "*.urdf",
    "*.stl",
    "*.dae",
    "*.obj",
    "py.typed",  # PEP 561 compliance for type checking
    "ManipulaPy_data/*",
    "ManipulaPy_data/*/*",
    "ManipulaPy_data/*/*/*",
    "ManipulaPy_data/*/*/*/*",
    "ManipulaPy_data/*/*/*/*/*",
    "ManipulaPy_data/ur5/ur5.urdf",
    "ManipulaPy_data/ur5/visual/*.dae",
    "ManipulaPy_data/xarm/xarm6_robot.urdf",
    "ManipulaPy_data/xarm/visual/*.dae"
]
"ManipulaPy.ManipulaPy_data" = ["*"]
"ManipulaPy.ManipulaPy_data.xarm" = [
    "*.urdf",
    "*.stl",
    "*.dae", 
    "*.obj",
    "*.yml",
    "*.yaml",
    "xarm_description/**/*",
    "xarm_gripper/**/*",
]
"ManipulaPy.ManipulaPy_data.ur5" = [
    "*.urdf",
    "*.stl",
    "*.dae",
    "*.obj", 
    "*.yml",
    "*.yaml",
    "collision/**/*",
    "visual/**/*",
]

[tool.setuptools.package-dir]
"" = "."

# ------------------------------------------------------------------
# Entry points (if you have CLI tools)
# [project.scripts]
# manipulapy = "ManipulaPy.cli:main"

# [project.gui-scripts]
# manipulapy-gui = "ManipulaPy.gui:main"

# ==================================================================================
# TESTING CONFIGURATION
# ==================================================================================

[tool.pytest.ini_options]
minversion = "6.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"] 
python_functions = ["test_*"]
addopts = [
    "-ra",
    "--strict-markers",
    "--strict-config",
    "--tb=short",
    "--maxfail=10",
    "--disable-warnings",
    "-v",
    "--durations=10",  # Show 10 slowest tests
]

markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "cuda: marks tests as requiring GPU/CUDA (will skip if not available)",
    "gpu: alias for cuda marker",
    "integration: marks tests as integration tests",
    "unit: marks tests as unit tests",
    "vision: marks tests requiring vision dependencies (OpenCV, YOLO)",
    "simulation: marks tests requiring simulation dependencies (PyBullet, URCHIN)",
    "ml: marks tests requiring machine learning dependencies",
    "network: marks tests requiring network access",
    "benchmark: marks tests as benchmarks",
    "parametrize: marks parametrized tests",
    "functional: marks functional tests",
]

filterwarnings = [
    "ignore::UserWarning",
    "ignore::DeprecationWarning",
    "ignore::PendingDeprecationWarning",
    "ignore:.*CUDA.*:UserWarning",
    "ignore:.*cupy.*:UserWarning", 
    "ignore:.*numba.*:UserWarning",
    "ignore:.*OpenGL.*:UserWarning",
    "ignore:.*PyBullet.*:UserWarning",
    "ignore:.*torch.*:UserWarning",
    "ignore:.*matplotlib.*:UserWarning",
    "ignore::RuntimeWarning:numba.*",
    "ignore::FutureWarning:torch.*",
    "ignore::DeprecationWarning:pkg_resources.*",
    "ignore:.*pkg_resources.*:DeprecationWarning",
]

timeout = 300
timeout_method = "thread"
log_cli = false
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)8s] %(name)s: %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"

# Pytest collection configuration


# Use norecursedirs instead of collect_ignore for directories
norecursedirs = [
    "build",
    "dist", 
    ".eggs",
    "*.egg-info",
    ".git",
    ".tox",
    ".pytest_cache",
    "__pycache__",
    "Examples*",
    "Benchmark*", 
    "benchmark_results*",
    "paper",
    "figures",
    "docs",
    "venv*",
    "htmlcov",
    ".mypy_cache",
    "node_modules",
]

# ==================================================================================
# COVERAGE CONFIGURATION
# ==================================================================================

[tool.coverage.run]
source = ["ManipulaPy"]
branch = true
parallel = true
context = "${COVERAGE_CONTEXT}"
omit = [
    "*/tests/*",
    "*/test_*", 
    "*/*test*.py",
    "*/Examples/*",
    "*/Examples_old_backup_*/*",
    "*/Benchmark/*",
    "*/benchmark_results/*",
    "*/docs/*",
    "*/__pycache__/*",
    "*/build/*",
    "*/dist/*",
    "*/_version.py",
    "setup.py",
    "*/ManipulaPy_data/*",
    "*/venv*/*",
    "*/env/*",
    "*/.venv/*",
    "*/paper/*",
    "*/figures/*",
    "*/.pytest_cache/*",
    "*/.mypy_cache/*",
]

[tool.coverage.paths]
source = [
    "ManipulaPy/",
    "*/site-packages/ManipulaPy/",
    "*/lib/python*/site-packages/ManipulaPy/",
    "*/lib/python*/dist-packages/ManipulaPy/",
]

[tool.coverage.report]
show_missing = true
precision = 1
skip_covered = false
skip_empty = false
sort = "Cover"

exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "if self\\.debug",
    "if settings.DEBUG",
    "raise AssertionError",
    "raise NotImplementedError", 
    "raise RuntimeError",
    "raise ValueError",
    "if 0:",
    "if False:",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
    "if not CUDA_AVAILABLE:",
    "if not CUPY_AVAILABLE:",
    "except ImportError:",
    "except ModuleNotFoundError:",
    "cuda\\..*",
    "cupy\\..*",
    "CUDA not available",
    "GPU not available",
    "RuntimeError.*CUDA.*",
    "if not OPENCV_AVAILABLE:",
    "if not PYBULLET_AVAILABLE:",
    "if not YOLO_AVAILABLE:",
    "OpenCV not available",
    "PyBullet not available", 
    "YOLO not available",
    "ultralytics.*",
    "if sys\\.platform",
    "if platform\\.system",
    "@abstractmethod",
    "@abc\\.abstractmethod",
    "class .*\\bProtocol\\):",
    "except.*Exception",
    "except.*Error",
    "except.*Warning",
    "def __del__",
    "try:.*finally:",
    "raise NotImplementedError",
    "return NotImplemented",
    "TYPE_CHECKING",
    "\\.\\.\\.",  # Ellipsis in type stubs
]

[tool.coverage.html]
directory = "htmlcov"
title = "ManipulaPy Coverage Report"

[tool.coverage.xml]
output = "coverage.xml"

[tool.coverage.json]
output = "coverage.json"

# ==================================================================================
# CODE QUALITY TOOLS
# ==================================================================================

[tool.black]
line-length = 88
target-version = ["py38", "py39", "py310", "py311"]
include = "\\.pyi?$"
extend-exclude = '''
/(
    \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
  | __pycache__
  | ManipulaPy_data
  | Examples
  | Examples_old_backup_.*
  | Benchmark
  | benchmark_results
  | paper
  | figures
)/
'''

[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
known_first_party = ["ManipulaPy"]
known_third_party = [
    "numpy",
    "scipy",
    "matplotlib",
    "cv2",
    "pybullet",
    "torch",
    "numba", 
    "cupy",
    "ultralytics",
    "sklearn",
    "urchin",
    "trimesh",
    "PIL",
    "pytest",
]
skip_glob = [
    "ManipulaPy_data/*",
    "build/*",
    "dist/*",
    "Examples/*",
    "Examples_old_backup_*/*",
    "Benchmark/*",
    "benchmark_results/*",
    "paper/*",
    "figures/*",
]
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]

[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
disallow_incomplete_defs = false
check_untyped_defs = true
disallow_untyped_decorators = false
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
show_error_codes = true
pretty = true
show_column_numbers = true
show_error_context = true

[[tool.mypy.overrides]]
module = [
    "pybullet.*",
    "urchin.*",
    "ultralytics.*",
    "cupy.*",
    "numba.*",
    "cv2.*",
    "matplotlib.*",
    "scipy.*",
    "trimesh.*",
    "pycuda.*",
    "PIL.*",
    "sklearn.*",
    "torch.*",
]
ignore_missing_imports = true

[tool.ruff]
target-version = "py38"
line-length = 88
select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "B",  # flake8-bugbear
    "C4", # flake8-comprehensions
    "UP", # pyupgrade
    "SIM", # flake8-simplify
]
ignore = [
    "E501",  # line too long, handled by black
    "B008",  # do not perform function calls in argument defaults
    "C901",  # too complex
    "B904",  # raise ... from err
]
unfixable = [
    "B",  # Avoid fixing flake8-bugbear violations
]

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401", "F403"]
"tests/*" = ["B011", "S101"]
"**/conftest.py" = ["F401", "F403"]

[tool.ruff.mccabe]
max-complexity = 15

[tool.bandit]
exclude_dirs = [
    "tests",
    "Examples", 
    "Examples_old_backup_*",
    "Benchmark",
    "benchmark_results",
]
skips = ["B101", "B601", "B602"]

[tool.bandit.assert_used]
skips = ["**/test_*.py", "**/tests/*.py"]

# ==================================================================================
# ADDITIONAL TOOLS
# ==================================================================================

[tool.vulture]
min_confidence = 80
paths = ["ManipulaPy"]
exclude = ["ManipulaPy_data/"]

[tool.docformatter]
make-summary-multi-line = false
close-quotes-on-newline = true
wrap-summaries = 88
wrap-descriptions = 88

# ==================================================================================
# TOX CONFIGURATION (for multi-environment testing)
# ==================================================================================

[tool.tox]
legacy_tox_ini = """
[tox]
isolated_build = true
envlist = py38,py39,py310,py311,coverage,docs,lint

[testenv]
extras = test-ci
commands = pytest {posargs}
setenv =
    SKIP_CUDA_TESTS = true
    SKIP_VISION_TESTS = true  
    SKIP_SIMULATION_TESTS = true

[testenv:coverage]
extras = test-ci
commands = 
    pytest --cov=ManipulaPy --cov-report=html --cov-report=xml

[testenv:docs]
extras = docs
commands = sphinx-build -b html docs docs/_build/html

[testenv:lint]
extras = dev
commands = 
    black --check ManipulaPy/
    isort --check-only ManipulaPy/
    flake8 ManipulaPy/
    mypy ManipulaPy/
"""
