# BookstoreArrayList.java

    package com.university.bookstore.impl;

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    import com.university.bookstore.api.BookstoreAPI;
    import com.university.bookstore.model.Book;

    /**
     * ArrayList-based implementation of the BookstoreAPI.
     *
     * <p>This implementation uses an ArrayList for storage and enforces
     * ISBN uniqueness. All collection returns are defensive copies to
     * maintain encapsulation.</p>
     *
     * <p>Performance characteristics:</p>
     * <ul>
     *   <li>add: O(n) - due to uniqueness check</li>
     *   <li>removeByIsbn: O(n) - linear search required</li>
     *   <li>findByIsbn: O(n) - linear search required</li>
     *   <li>size: O(1) - ArrayList maintains size</li>
     * </ul>
     *
     * <p>Note: This implementation is NOT thread-safe. For concurrent access,
     * consider using synchronization or ConcurrentHashMap.</p>
     *
     * @author Navid Mohaghegh
     * @version 1.0
     * @since 2024-09-15
     */
    public class BookstoreArrayList implements BookstoreAPI {

        private final List<Book> inventory;

        /**
         * Creates a new empty bookstore.
         */
        public BookstoreArrayList() {
            this.inventory = new ArrayList<>();
        }

        /**
         * Creates a bookstore with initial books.
         *
         * @param initialBooks books to add initially (may be null or empty)
         */
        public BookstoreArrayList(Collection<Book> initialBooks) {
            this.inventory = new ArrayList<>();
            if (initialBooks != null) {
                for (Book book : initialBooks) {
                    add(book);
                }
            }
        }

        @Override
        public boolean add(Book book) {
            if (book == null) {
                return false;
            }

            // Check for duplicate ISBN
            if (findByIsbn(book.getIsbn()) != null) {
                return false;
            }

            return inventory.add(book);
        }

        @Override
        public boolean removeByIsbn(String isbn) {
            if (isbn == null || isbn.trim().isEmpty()) {
                return false;
            }

            return inventory.removeIf(book -> book.getIsbn().equals(isbn));
        }

        @Override
        public Book findByIsbn(String isbn) {
            if (isbn == null || isbn.trim().isEmpty()) {
                return null;
            }

            for (Book book : inventory) {
                if (book.getIsbn().equals(isbn)) {
                    return book;
                }
            }
            return null;
        }

        @Override
        public List<Book> findByTitle(String titleQuery) {
            if (titleQuery == null || titleQuery.trim().isEmpty()) {
                return Collections.emptyList();
            }

            String query = titleQuery.toLowerCase().trim();
            return inventory.stream()
                .filter(book -> book.getTitle().toLowerCase().contains(query))
                .collect(Collectors.toList());
        }

        @Override
        public List<Book> findByAuthor(String authorQuery) {
            if (authorQuery == null || authorQuery.trim().isEmpty()) {
                return Collections.emptyList();
            }

            String query = authorQuery.toLowerCase().trim();
            return inventory.stream()
                .filter(book -> book.getAuthor().toLowerCase().contains(query))
                .collect(Collectors.toList());
        }

        @Override
        public List<Book> findByPriceRange(double minPrice, double maxPrice) {
            if (minPrice < 0 || maxPrice < 0) {
                throw new IllegalArgumentException("Prices cannot be negative");
            }
            if (minPrice > maxPrice) {
                throw new IllegalArgumentException(
                    "Minimum price cannot be greater than maximum price");
            }

            return inventory.stream()
                .filter(book -> book.getPrice() >= minPrice && book.getPrice() <= maxPrice)
                .collect(Collectors.toList());
        }

        @Override
        public List<Book> findByYear(int year) {
            return inventory.stream()
                .filter(book -> book.getYear() == year)
                .collect(Collectors.toList());
        }

        @Override
        public int size() {
            return inventory.size();
        }

        @Override
        public double inventoryValue() {
            return inventory.stream()
                .mapToDouble(Book::getPrice)
                .sum();
        }

        @Override
        public Book getMostExpensive() {
            return inventory.stream()
                .max(Comparator.comparingDouble(Book::getPrice))
                .orElse(null);
        }

        @Override
        public Book getMostRecent() {
            return inventory.stream()
                .max(Comparator.comparingInt(Book::getYear))
                .orElse(null);
        }

        @Override
        public Book[] snapshotArray() {
            return inventory.toArray(new Book[0]);
        }

        @Override
        public List<Book> getAllBooks() {
            return new ArrayList<>(inventory);
        }

        /**
         * Clears all books from the inventory.
         * Useful for testing and bulk operations.
         */
        public void clear() {
            inventory.clear();
        }

        /**
         * Sorts the inventory by title (alphabetical order).
         */
        public void sortByTitle() {
            Collections.sort(inventory);
        }

        /**
         * Sorts the inventory by price (ascending).
         */
        public void sortByPrice() {
            inventory.sort(Comparator.comparingDouble(Book::getPrice));
        }

        /**
         * Sorts the inventory by year (ascending).
         */
        public void sortByYear() {
            inventory.sort(Comparator.comparingInt(Book::getYear));
        }

        /**
         * Gets statistics about the inventory.
         *
         * @return map with statistics (size, total_value, avg_price, etc.)
         */
        public Map<String, Object> getStatistics() {
            Map<String, Object> stats = new HashMap<>();
            stats.put("size", size());
            stats.put("total_value", inventoryValue());
            stats.put("average_price", inventory.isEmpty() ? 0.0 : inventoryValue() / size());

            if (!inventory.isEmpty()) {
                stats.put("min_year", inventory.stream()
                    .mapToInt(Book::getYear).min().orElse(0));
                stats.put("max_year", inventory.stream()
                    .mapToInt(Book::getYear).max().orElse(0));
                stats.put("unique_authors", inventory.stream()
                    .map(Book::getAuthor).distinct().count());
            }

            return stats;
        }

        @Override
        public String toString() {
            return String.format("BookstoreArrayList[size=%d, value=$%.2f]",
                size(), inventoryValue());
        }
    }