# PolymorphismDemo.java

    package com.university.bookstore.demo;

    import java.util.Arrays;
    import java.util.List;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import com.university.bookstore.api.MaterialStore;
    import com.university.bookstore.impl.MaterialStoreImpl;
    import com.university.bookstore.model.AudioBook;
    import com.university.bookstore.model.Magazine;
    import com.university.bookstore.model.Material;
    import com.university.bookstore.model.Media;
    import com.university.bookstore.model.PrintedBook;
    import com.university.bookstore.model.VideoMaterial;

    /**
     * Demonstration class showing polymorphism and OOP best practices.
     *
     * @author Navid Mohaghegh
     * @version 2.0
     */
    public class PolymorphismDemo {

        private static final Logger LOGGER = LoggerFactory.getLogger(PolymorphismDemo.class);

        public static void main(String[] args) {
            LOGGER.info("=== Material Store Polymorphism Demo ===\n");

            MaterialStore store = createSampleStore();

            demonstratePolymorphicBehavior(store);
            demonstrateInterfaceSegregation(store);
            demonstrateAbstraction(store);
            demonstrateDynamicBinding(store);
            demonstrateSOLIDPrinciples(store);
            demonstrateMediaVersatility(store);
            demonstrateStreamingVsDownload(store);
        }

        private static MaterialStore createSampleStore() {
            MaterialStoreImpl store = new MaterialStoreImpl();

            // Traditional print materials
            PrintedBook book = new PrintedBook(
                "9780134685991", "Effective Java", "Joshua Bloch",
                45.99, 2018, 412, "Addison-Wesley", true
            );

            Magazine magazine = new Magazine(
                "12345678", "National Geographic", "NatGeo Society",
                6.99, 2024, 3, "Monthly", "Science"
            );

            // Audio materials with various qualities
            AudioBook audioBook1 = new AudioBook(
                "9780143038092", "1984", "George Orwell", "Simon Prebble",
                14.99, 2020, 690, "MP3", 850.5,
                Media.MediaQuality.HIGH, "English", true
            );

            AudioBook audioBook2 = new AudioBook(
                "9780547928227", "The Hobbit", "J.R.R. Tolkien", "Rob Inglis",
                24.99, 2022, 660, "M4B", 450.0,
                Media.MediaQuality.STANDARD, "English", false
            );

            AudioBook audiobook3 = new AudioBook(
                "9781250318398", "Atomic Habits", "James Clear", "James Clear",
                19.99, 2023, 320, "FLAC", 1200.0,
                Media.MediaQuality.ULTRA_HD, "English", true
            );

            // Diverse video content
            VideoMaterial documentary = new VideoMaterial(
                "883929665839", "Planet Earth II", "David Attenborough",
                29.99, 2016, 300, "MP4", 4500.0,
                Media.MediaQuality.ULTRA_HD, VideoMaterial.VideoType.DOCUMENTARY,
                "G", Arrays.asList("David Attenborough"), true, "16:9"
            );

            VideoMaterial movie = new VideoMaterial(
                "043396544789", "The Matrix", "The Wachowskis",
                19.99, 1999, 136, "MP4", 2800.0,
                Media.MediaQuality.HD, VideoMaterial.VideoType.MOVIE,
                "R", Arrays.asList("Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss"),
                true, "2.39:1"
            );

            VideoMaterial tutorial = new VideoMaterial(
                "TUT20240001", "Advanced Java Programming", "Tech Education Inc",
                89.99, 2024, 480, "MP4", 3200.0,
                Media.MediaQuality.HD, VideoMaterial.VideoType.TUTORIAL,
                "NR", Arrays.asList("Dr. Sarah Chen"), true, "16:9"
            );

            VideoMaterial tvSeries = new VideoMaterial(
                "883929696789", "Breaking Bad - Season 1", "Vince Gilligan",
                39.99, 2008, 420, "MKV", 8500.0,
                Media.MediaQuality.HD, VideoMaterial.VideoType.TV_SERIES,
                "TV-MA", Arrays.asList("Bryan Cranston", "Aaron Paul", "Anna Gunn"),
                true, "16:9"
            );

            VideoMaterial shortFilm = new VideoMaterial(
                "SHORT2023001", "The Red Balloon", "Albert Lamorisse",
                9.99, 1956, 34, "AVI", 250.0,
                Media.MediaQuality.STANDARD, VideoMaterial.VideoType.SHORT_FILM,
                "G", Arrays.asList("Pascal Lamorisse"), false, "4:3"
            );

            VideoMaterial educationalVideo = new VideoMaterial(
                "EDU2024567", "Quantum Physics Explained", "MIT OpenCourseWare",
                0.00, 2024, 90, "WEBM", 680.0,
                Media.MediaQuality.HIGH, VideoMaterial.VideoType.EDUCATIONAL,
                "NR", Arrays.asList("Prof. Walter Lewin"), true, "16:9"
            );

            // Add all materials to store
            store.addMaterial(book);
            store.addMaterial(magazine);
            store.addMaterial(audioBook1);
            store.addMaterial(audioBook2);
            store.addMaterial(audiobook3);
            store.addMaterial(documentary);
            store.addMaterial(movie);
            store.addMaterial(tutorial);
            store.addMaterial(tvSeries);
            store.addMaterial(shortFilm);
            store.addMaterial(educationalVideo);

            return store;
        }

        private static void demonstratePolymorphicBehavior(MaterialStore store) {
            LOGGER.info("1. POLYMORPHIC BEHAVIOR");
            LOGGER.info("------------------------");

            List<Material> materials = store.getAllMaterials();

            for (Material material : materials) {
                LOGGER.info("\nMaterial Type: {}", material.getType().getDisplayName());
                LOGGER.info("Title: {}", material.getTitle());
                LOGGER.info("Creator: {}", material.getCreator());
                LOGGER.info("Display Info: {}", material.getDisplayInfo());
                LOGGER.info("Original Price: ${}", String.format("%.2f", material.getPrice()));
                LOGGER.info("Discount Rate: {}%", (material.getDiscountRate() * 100));
                LOGGER.info("Discounted Price: ${}", String.format("%.2f", material.getDiscountedPrice()));
            }

            LOGGER.info("\n");
        }

        private static void demonstrateInterfaceSegregation(MaterialStore store) {
            LOGGER.info("2. INTERFACE SEGREGATION (Media Interface)");
            LOGGER.info("-------------------------------------------");

            List<Media> mediaItems = store.getMediaMaterials();

            LOGGER.info("Found {} media items:\n", mediaItems.size());

            for (Media media : mediaItems) {
                LOGGER.info("Media: {}", ((Material)media).getTitle());
                LOGGER.info("  {}", media.getPlaybackInfo());
                LOGGER.info("  Download time at 10 Mbps: {} seconds", media.estimateDownloadTime(10));
                LOGGER.info("  Streaming only: {}", media.isStreamingOnly());
            }

            LOGGER.info("\n");
        }

        private static void demonstrateAbstraction(MaterialStore store) {
            LOGGER.info("3. ABSTRACTION (Abstract Base Class)");
            LOGGER.info("-------------------------------------");

            LOGGER.info("Materials grouped by type:\n");

            for (Material.MaterialType type : Material.MaterialType.values()) {
                List<Material> ofType = store.getMaterialsByType(type);
                if (!ofType.isEmpty()) {
                    LOGGER.info("{}: {} items", type.getDisplayName(), ofType.size());
                    for (Material m : ofType) {
                        LOGGER.info("  - {} (${})", m.getTitle(), String.format("%.2f", m.getPrice()));
                    }
                }
            }

            LOGGER.info("\n");
        }

        private static void demonstrateDynamicBinding(MaterialStore store) {
            LOGGER.info("4. DYNAMIC BINDING");
            LOGGER.info("-------------------");

            Material cheapest = store.filterMaterials(m -> true).stream()
                .min((a, b) -> Double.compare(a.getPrice(), b.getPrice()))
                .orElse(null);

            Material mostExpensive = store.filterMaterials(m -> true).stream()
                .max((a, b) -> Double.compare(a.getPrice(), b.getPrice()))
                .orElse(null);

            LOGGER.info("Cheapest item: {}", cheapest.getDisplayInfo());
            LOGGER.info("Most expensive: {}", mostExpensive.getDisplayInfo());

            LOGGER.info("\nDynamic method dispatch:");
            processPolymorphically(cheapest);
            processPolymorphically(mostExpensive);

            LOGGER.info("\n");
        }

        private static void processPolymorphically(Material material) {
            LOGGER.info("  Processing: {}", material.getTitle());

            if (material instanceof Media) {
                Media media = (Media) material;
                LOGGER.info("    -> This is media with duration: {} minutes", media.getDuration());
            }

            if (material instanceof PrintedBook) {
                PrintedBook book = (PrintedBook) material;
                LOGGER.info("    -> This is a book with {} pages", book.getPages());
            }

            if (material instanceof Magazine) {
                Magazine mag = (Magazine) material;
                LOGGER.info("    -> This is a magazine, issue #{}", mag.getIssueNumber());
            }
        }

        private static void demonstrateSOLIDPrinciples(MaterialStore store) {
            LOGGER.info("5. SOLID PRINCIPLES IN ACTION");
            LOGGER.info("------------------------------");

            MaterialStore.InventoryStats stats = store.getInventoryStats();

            LOGGER.info("Inventory Statistics:");
            LOGGER.info("  Total items: {}", stats.getTotalCount());
            LOGGER.info("  Average price: ${}", String.format("%.2f", stats.getAveragePrice()));
            LOGGER.info("  Median price: ${}", String.format("%.2f", stats.getMedianPrice()));
            LOGGER.info("  Unique types: {}", stats.getUniqueTypes());
            LOGGER.info("  Media items: {}", stats.getMediaCount());
            LOGGER.info("  Print items: {}", stats.getPrintCount());

            LOGGER.info("\nTotal inventory value: ${}", String.format("%.2f", store.getTotalInventoryValue()));
            LOGGER.info("Total with discounts: ${}", String.format("%.2f", store.getTotalDiscountedValue()));

            double savings = store.getTotalInventoryValue() - store.getTotalDiscountedValue();
            LOGGER.info("Total savings: ${}", String.format("%.2f", savings));

            LOGGER.info("\nFiltered Results (Lambda Expression):");
            List<Material> affordable = store.filterMaterials(m -> m.getPrice() < 20);
            LOGGER.info("  Items under $20: {}", affordable.size());
            for (Material m : affordable) {
                LOGGER.info("    - {} (${})", m.getTitle(), String.format("%.2f", m.getPrice()));
            }
        }

        private static void demonstrateMediaVersatility(MaterialStore store) {
            LOGGER.info("\n6. MEDIA VERSATILITY SHOWCASE");
            LOGGER.info("-------------------------------");

            LOGGER.info("\nVideo Content Types:");
            List<Material> videos = store.getMaterialsByType(Material.MaterialType.VIDEO);
            videos.addAll(store.getMaterialsByType(Material.MaterialType.DOCUMENTARY));

            for (Material material : videos) {
                if (material instanceof VideoMaterial) {
                    VideoMaterial video = (VideoMaterial) material;
                    LOGGER.info("\n  {}: {}", video.getVideoType(), video.getTitle());
                    LOGGER.info("    Director: {}", video.getDirector());
                    LOGGER.info("    Duration: {} minutes", video.getDuration());
                    LOGGER.info("    Quality: {}", video.getQuality());
                    LOGGER.info("    Format: {}", video.getFormat());
                    LOGGER.info("    Rating: {}", video.getRating());
                    LOGGER.info("    Aspect Ratio: {}", video.getAspectRatio());
                    LOGGER.info("    Subtitles: {}", (video.hasSubtitles() ? "Yes" : "No"));
                    LOGGER.info("    Streaming Bandwidth: {} Mbps", video.getStreamingBandwidth());

                    if (!video.getCast().isEmpty()) {
                        LOGGER.info("    Cast: {}", String.join(", ",
                            video.getCast().size() > 3 ?
                            video.getCast().subList(0, 3) : video.getCast()));
                    }
                }
            }

            LOGGER.info("\n\nAudio Quality Comparison:");
            List<Material> audioMaterials = store.filterMaterials(m -> m instanceof AudioBook);

            for (Material material : audioMaterials) {
                AudioBook audio = (AudioBook) material;
                LOGGER.info("\n  {}", audio.getTitle());
                LOGGER.info("    Quality: {}", audio.getQuality());
                LOGGER.info("    Format: {}", audio.getFormat());
                LOGGER.info("    File Size: {} MB", audio.getFileSize());
                LOGGER.info("    Duration: {} minutes", audio.getDuration());
                double estimatedBitrate = (audio.getFileSize() * 8 * 1024) / (audio.getDuration() * 60);
                LOGGER.info("    Estimated Bitrate: {} kbps", String.format("%.0f", estimatedBitrate));
                LOGGER.info("    Download Time (10 Mbps): {} seconds", audio.estimateDownloadTime(10));
            }

            LOGGER.info("\n");
        }

        private static void demonstrateStreamingVsDownload(MaterialStore store) {
            LOGGER.info("7. STREAMING VS DOWNLOAD ANALYSIS");
            LOGGER.info("-----------------------------------");

            List<Media> mediaItems = store.getMediaMaterials();

            LOGGER.info("\nStreaming-Only Content (Large Files > 4GB):");
            for (Media media : mediaItems) {
                if (media.isStreamingOnly()) {
                    Material material = (Material) media;
                    LOGGER.info("  - {} ({} MB)", material.getTitle(), media.getFileSize());
                }
            }

            LOGGER.info("\nDownloadable Content:");
            for (Media media : mediaItems) {
                if (!media.isStreamingOnly()) {
                    Material material = (Material) media;
                    LOGGER.info("  - {} ({} MB, Download time @ 50 Mbps: {}s)",
                               material.getTitle(), media.getFileSize(), media.estimateDownloadTime(50));
                }
            }

            LOGGER.info("\nQuality vs Storage Requirements:");
            LOGGER.info("  Format Distribution:");

            int ultraHD = 0, highQ = 0, standard = 0;
            for (Media media : mediaItems) {
                switch (media.getQuality()) {
                    case ULTRA_HD: ultraHD++; break;
                    case HIGH:
                    case HD: highQ++; break;
                    default: standard++; break;
                }
            }

            LOGGER.info("    Ultra HD: {} items", ultraHD);
            LOGGER.info("    High/HD: {} items", highQ);
            LOGGER.info("    Standard/Low: {} items", standard);

            double totalStorage = mediaItems.stream()
                .mapToDouble(Media::getFileSize)
                .sum();

            LOGGER.info("\n  Total Storage Required: {} GB", String.format("%.2f", totalStorage / 1024));

            LOGGER.info("\n  Bandwidth Requirements by Quality:");
            for (Material material : store.getAllMaterials()) {
                if (material instanceof VideoMaterial) {
                    VideoMaterial video = (VideoMaterial) material;
                    if (video.getQuality() == Media.MediaQuality.ULTRA_HD ||
                        video.getQuality() == Media.MediaQuality.HD) {
                        LOGGER.info("    {} ({}): {} Mbps", video.getTitle(), video.getQuality(), video.getStreamingBandwidth());
                    }
                }
            }

            LOGGER.info("\n");
        }
    }