#!/usr/bin/env python3
# chuk_mcp_math/arithmetic/core/rounding.py
"""
Core Rounding Operations - Async Native

Mathematical rounding functions for various rounding strategies and precision control.

Functions:
- round_number, floor, ceil, truncate
- mround, ceiling_multiple, floor_multiple
"""

import math
import asyncio
from typing import Union
from chuk_mcp_math.mcp_decorator import mcp_function

Number = Union[int, float]


@mcp_function(
    description="Round a number to a specified number of decimal places.",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"x": 3.14159, "decimals": 2},
            "output": 3.14,
            "description": "Round to 2 decimal places",
        },
        {
            "input": {"x": 2.5},
            "output": 2,
            "description": "Round to nearest integer (banker's rounding)",
        },
        {
            "input": {"x": -3.7, "decimals": 0},
            "output": -4,
            "description": "Round negative number to integer",
        },
    ],
)
async def round_number(x: Number, decimals: int = 0) -> Number:
    """Round a number to specified decimal places.

    Args:
        x (int or float): Number to round
        decimals (int): Number of decimal places (default 0)

    Returns:
        Number rounded to specified decimal places

    Example:
        round_number(3.14159, 2) = 3.14
        round_number(2.5) = 2
    """
    return round(x, decimals)


@mcp_function(
    description="Return the floor of a number (largest integer less than or equal to x).",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"x": 3.7}, "output": 3, "description": "Floor of positive number"},
        {"input": {"x": -2.3}, "output": -3, "description": "Floor of negative number"},
        {"input": {"x": 5}, "output": 5, "description": "Floor of integer"},
    ],
)
async def floor(x: Number) -> int:
    """Return the largest integer less than or equal to x.

    Args:
        x (int or float): Number to floor

    Returns:
        Largest integer ≤ x

    Example:
        floor(3.7) = 3
        floor(-2.3) = -3
    """
    return math.floor(x)


@mcp_function(
    description="Return the ceiling of a number (smallest integer greater than or equal to x).",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"x": 3.2}, "output": 4, "description": "Ceiling of positive number"},
        {
            "input": {"x": -2.7},
            "output": -2,
            "description": "Ceiling of negative number",
        },
        {"input": {"x": 5}, "output": 5, "description": "Ceiling of integer"},
    ],
)
async def ceil(x: Number) -> int:
    """Return the smallest integer greater than or equal to x.

    Args:
        x (int or float): Number to ceiling

    Returns:
        Smallest integer ≥ x

    Example:
        ceil(3.2) = 4
        ceil(-2.7) = -2
    """
    return math.ceil(x)


@mcp_function(
    description="Truncate a number towards zero (remove decimal part).",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"x": 3.9}, "output": 3, "description": "Truncate positive number"},
        {"input": {"x": -2.9}, "output": -2, "description": "Truncate negative number"},
        {"input": {"x": 5}, "output": 5, "description": "Truncate integer"},
    ],
)
async def truncate(x: Number) -> int:
    """Remove decimal part, rounding towards zero.

    Args:
        x (int or float): Number to truncate

    Returns:
        Integer part of x

    Example:
        truncate(3.9) = 3
        truncate(-2.9) = -2
    """
    return math.trunc(x)


@mcp_function(
    description="Round a number up to the nearest multiple of significance. Always rounds away from zero.",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"number": 2.5, "significance": 1},
            "output": 3,
            "description": "Round 2.5 up to nearest 1",
        },
        {
            "input": {"number": 6.7, "significance": 2},
            "output": 8,
            "description": "Round 6.7 up to nearest 2",
        },
        {
            "input": {"number": -2.1, "significance": 1},
            "output": -3,
            "description": "Round -2.1 away from zero",
        },
        {
            "input": {"number": 15, "significance": 10},
            "output": 20,
            "description": "Round 15 up to nearest 10",
        },
    ],
)
async def ceiling_multiple(number: Number, significance: Number) -> Number:
    """Round up to nearest multiple of significance (away from zero).

    Args:
        number (int or float): Number to round
        significance (int or float): Multiple to round to (must be positive)

    Returns:
        Number rounded up to nearest multiple

    Raises:
        ValueError: If significance ≤ 0 or infinite

    Example:
        ceiling_multiple(6.7, 2) = 8
        ceiling_multiple(-2.1, 1) = -3
    """
    if significance <= 0:
        raise ValueError("Significance must be positive")

    if not math.isfinite(significance):
        raise ValueError("Significance must be finite")

    if number >= 0:
        return math.ceil(number / significance) * significance
    else:
        return math.floor(number / significance) * significance


@mcp_function(
    description="Round a number down to the nearest multiple of significance. Always rounds toward zero.",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"number": 2.9, "significance": 1},
            "output": 2,
            "description": "Round 2.9 down to nearest 1",
        },
        {
            "input": {"number": 7.8, "significance": 2},
            "output": 6,
            "description": "Round 7.8 down to nearest 2",
        },
        {
            "input": {"number": -2.9, "significance": 1},
            "output": -2,
            "description": "Round -2.9 toward zero",
        },
        {
            "input": {"number": 23, "significance": 10},
            "output": 20,
            "description": "Round 23 down to nearest 10",
        },
    ],
)
async def floor_multiple(number: Number, significance: Number) -> Number:
    """Round down to nearest multiple of significance (toward zero).

    Args:
        number (int or float): Number to round
        significance (int or float): Multiple to round to (must be positive)

    Returns:
        Number rounded down to nearest multiple

    Raises:
        ValueError: If significance ≤ 0 or infinite

    Example:
        floor_multiple(7.8, 2) = 6
        floor_multiple(-2.9, 1) = -2
    """
    if significance <= 0:
        raise ValueError("Significance must be positive")

    if not math.isfinite(significance):
        raise ValueError("Significance must be finite")

    if number >= 0:
        return math.floor(number / significance) * significance
    else:
        return math.ceil(number / significance) * significance


@mcp_function(
    description="Round a number to the nearest multiple of significance. Uses standard rounding rules.",
    namespace="arithmetic",
    category="core",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"number": 2.4, "significance": 1},
            "output": 2,
            "description": "Round 2.4 to nearest 1",
        },
        {
            "input": {"number": 2.6, "significance": 1},
            "output": 3,
            "description": "Round 2.6 to nearest 1",
        },
        {
            "input": {"number": 7.3, "significance": 2},
            "output": 8,
            "description": "Round 7.3 to nearest 2",
        },
        {
            "input": {"number": 15, "significance": 10},
            "output": 20,
            "description": "Round 15 to nearest 10",
        },
    ],
)
async def mround(number: Number, significance: Number) -> Number:
    """Round to nearest multiple of significance (standard rounding).

    Args:
        number (int or float): Number to round
        significance (int or float): Multiple to round to (must be positive)

    Returns:
        Number rounded to nearest multiple

    Raises:
        ValueError: If significance ≤ 0 or infinite

    Example:
        mround(2.6, 1) = 3
        mround(7.3, 2) = 8
    """
    if significance <= 0:
        raise ValueError("Significance must be positive")

    if not math.isfinite(significance):
        raise ValueError("Significance must be finite")

    return round(number / significance) * significance


# Export all rounding functions
__all__ = [
    "round_number",
    "floor",
    "ceil",
    "truncate",
    "ceiling_multiple",
    "floor_multiple",
    "mround",
]

if __name__ == "__main__":
    import asyncio

    async def test_rounding_operations():
        """Test all rounding operations."""
        print("🔢 Rounding Operations Test")
        print("=" * 30)

        # Test basic rounding
        print(f"round_number(3.14159, 2) = {await round_number(3.14159, 2)}")
        print(f"floor(3.7) = {await floor(3.7)}")
        print(f"ceil(3.2) = {await ceil(3.2)}")
        print(f"truncate(3.9) = {await truncate(3.9)}")

        # Test multiple rounding
        print(f"ceiling_multiple(6.7, 2) = {await ceiling_multiple(6.7, 2)}")
        print(f"floor_multiple(7.8, 2) = {await floor_multiple(7.8, 2)}")
        print(f"mround(7.3, 2) = {await mround(7.3, 2)}")

        print("\n✅ All rounding operations working!")

    asyncio.run(test_rounding_operations())
