# SPDX-License-Identifier: Apache-2.0
# Copyright 2025 Bubble Sort Implementation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Bubble Sort Algorithm Implementation.

This module provides an implementation of the bubble sort algorithm,
a simple comparison-based sorting algorithm that repeatedly steps through
the list, compares adjacent elements and swaps them if they are in the
wrong order.

Typical usage example:
    numbers = [64, 34, 25, 12, 22, 11, 90]
    sorted_numbers = bubble_sort(numbers)
    print(sorted_numbers)  # [11, 12, 22, 25, 34, 64, 90]
"""

from typing import List, TypeVar

# Type variable for generic sorting
T = TypeVar('T')


def bubble_sort(arr: List[T]) -> List[T]:
    """Sort a list using the bubble sort algorithm.
    
    Bubble sort is a simple sorting algorithm that repeatedly steps through
    the list, compares adjacent elements and swaps them if they are in the
    wrong order. The pass through the list is repeated until the list is sorted.
    
    Time Complexity: O(n²) in worst and average cases, O(n) in best case
    Space Complexity: O(1) - sorts in place
    
    Args:
        arr: List of comparable elements to be sorted.
        
    Returns:
        A new list containing the sorted elements in ascending order.
        
    Raises:
        TypeError: If elements in the list are not comparable.
        
    Example:
        >>> bubble_sort([64, 34, 25, 12, 22, 11, 90])
        [11, 12, 22, 25, 34, 64, 90]
        >>> bubble_sort(['banana', 'apple', 'cherry'])
        ['apple', 'banana', 'cherry']
    """
    if not arr:
        return []
    
    # Create a copy to avoid modifying the original list
    sorted_arr = arr.copy()
    n = len(sorted_arr)
    
    # Traverse through all array elements
    for i in range(n):
        # Flag to optimize by detecting if any swaps occurred
        swapped = False
        
        # Last i elements are already in place
        for j in range(0, n - i - 1):
            # Traverse the array from 0 to n-i-1
            # Swap if the element found is greater than the next element
            if sorted_arr[j] > sorted_arr[j + 1]:
                sorted_arr[j], sorted_arr[j + 1] = sorted_arr[j + 1], sorted_arr[j]
                swapped = True
        
        # If no swapping occurred, the array is already sorted
        if not swapped:
            break
    
    return sorted_arr


def bubble_sort_in_place(arr: List[T]) -> None:
    """Sort a list in place using the bubble sort algorithm.
    
    This version modifies the original list instead of creating a new one,
    which saves memory but changes the input list.
    
    Args:
        arr: List of comparable elements to be sorted in place.
        
    Raises:
        TypeError: If elements in the list are not comparable.
        
    Example:
        >>> numbers = [64, 34, 25, 12, 22, 11, 90]
        >>> bubble_sort_in_place(numbers)
        >>> print(numbers)
        [11, 12, 22, 25, 34, 64, 90]
    """
    if not arr:
        return
    
    n = len(arr)
    
    # Traverse through all array elements
    for i in range(n):
        # Flag to optimize by detecting if any swaps occurred
        swapped = False
        
        # Last i elements are already in place
        for j in range(0, n - i - 1):
            # Traverse the array from 0 to n-i-1
            # Swap if the element found is greater than the next element
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        
        # If no swapping occurred, the array is already sorted
        if not swapped:
            break


def demonstrate_bubble_sort() -> None:
    """Demonstrate the bubble sort algorithm with various examples."""
    print("Bubble Sort Algorithm Demonstration")
    print("=" * 40)
    
    # Example 1: Integer array
    numbers = [64, 34, 25, 12, 22, 11, 90]
    print(f"Original array: {numbers}")
    sorted_numbers = bubble_sort(numbers)
    print(f"Sorted array:   {sorted_numbers}")
    print()
    
    # Example 2: String array
    fruits = ['banana', 'apple', 'cherry', 'date']
    print(f"Original fruits: {fruits}")
    sorted_fruits = bubble_sort(fruits)
    print(f"Sorted fruits:   {sorted_fruits}")
    print()
    
    # Example 3: Already sorted array
    sorted_array = [1, 2, 3, 4, 5]
    print(f"Already sorted: {sorted_array}")
    result = bubble_sort(sorted_array)
    print(f"Result:         {result}")
    print()
    
    # Example 4: Reverse sorted array
    reverse_array = [5, 4, 3, 2, 1]
    print(f"Reverse sorted: {reverse_array}")
    result = bubble_sort(reverse_array)
    print(f"Result:         {result}")
    print()
    
    # Example 5: In-place sorting
    in_place_array = [3, 1, 4, 1, 5, 9, 2, 6]
    print(f"Before in-place sort: {in_place_array}")
    bubble_sort_in_place(in_place_array)
    print(f"After in-place sort:  {in_place_array}")


if __name__ == '__main__':
    demonstrate_bubble_sort()