# AnalyticsObserver.java

    package com.university.bookstore.observer;

    import java.util.HashMap;
    import java.util.Map;

    /**
     * Observer that collects analytics data from material events.
     * Tracks event counts, material statistics, and system performance metrics.
     *
     * @author Navid Mohaghegh
     * @version 3.0
     * @since 2024-09-15
     */
    public class AnalyticsObserver implements MaterialObserver {

        private final Map<String, Integer> eventCounts;
        private final Map<String, Double> totalValue;
        private final Map<String, Integer> materialTypeCounts;
        private int totalEvents;
        private long firstEventTime;
        private long lastEventTime;

        /**
         * Creates a new analytics observer.
         */
        public AnalyticsObserver() {
            this.eventCounts = new HashMap<>();
            this.totalValue = new HashMap<>();
            this.materialTypeCounts = new HashMap<>();
            this.totalEvents = 0;
            this.firstEventTime = 0;
            this.lastEventTime = 0;
        }

        @Override
        public void onEvent(MaterialEvent event) {
            totalEvents++;
            long currentTime = event.getTimestamp();

            // Track event timing
            if (firstEventTime == 0) {
                firstEventTime = currentTime;
            }
            lastEventTime = currentTime;

            // Track event counts
            String eventType = event.getEventType();
            eventCounts.merge(eventType, 1, Integer::sum);

            // Track material statistics
            String materialId = event.getMaterial().getId();
            String materialType = event.getMaterial().getType().toString();

            if ("MATERIAL_ADDED".equals(eventType)) {
                totalValue.merge(materialId, event.getMaterial().getPrice(), Double::sum);
                materialTypeCounts.merge(materialType, 1, Integer::sum);
            }
        }

        /**
         * Gets event statistics.
         *
         * @return map of event type to count
         */
        public Map<String, Integer> getEventStatistics() {
            return new HashMap<>(eventCounts);
        }

        /**
         * Gets the count for a specific event type.
         *
         * @param eventType the event type
         * @return the event count
         */
        public int getEventCount(String eventType) {
            return eventCounts.getOrDefault(eventType, 0);
        }

        /**
         * Gets the total number of events processed.
         *
         * @return the total event count
         */
        public int getTotalEvents() {
            return totalEvents;
        }

        /**
         * Gets the total inventory value.
         *
         * @return the total value
         */
        public double getTotalInventoryValue() {
            return totalValue.values().stream()
                .mapToDouble(Double::doubleValue)
                .sum();
        }

        /**
         * Gets material type statistics.
         *
         * @return map of material type to count
         */
        public Map<String, Integer> getMaterialTypeStatistics() {
            return new HashMap<>(materialTypeCounts);
        }

        /**
         * Gets the count for a specific material type.
         *
         * @param materialType the material type
         * @return the material count
         */
        public int getMaterialTypeCount(String materialType) {
            return materialTypeCounts.getOrDefault(materialType, 0);
        }

        /**
         * Gets the number of unique materials.
         *
         * @return the unique material count
         */
        public int getUniqueMaterialCount() {
            return totalValue.size();
        }

        /**
         * Gets the time of the first event.
         *
         * @return the first event timestamp
         */
        public long getFirstEventTime() {
            return firstEventTime;
        }

        /**
         * Gets the time of the last event.
         *
         * @return the last event timestamp
         */
        public long getLastEventTime() {
            return lastEventTime;
        }

        /**
         * Gets the time span of events.
         *
         * @return the time span in milliseconds
         */
        public long getEventTimeSpan() {
            if (firstEventTime == 0) {
                return 0;
            }
            return lastEventTime - firstEventTime;
        }

        /**
         * Gets the average events per second.
         *
         * @return the average event rate
         */
        public double getAverageEventRate() {
            long timeSpan = getEventTimeSpan();
            if (timeSpan == 0) {
                return 0.0;
            }
            return (double) totalEvents / (timeSpan / 1000.0);
        }

        /**
         * Gets comprehensive analytics data.
         *
         * @return analytics data object
         */
        public AnalyticsData getAnalyticsData() {
            return new AnalyticsData(
                totalEvents,
                getTotalInventoryValue(),
                getUniqueMaterialCount(),
                getEventStatistics(),
                getMaterialTypeStatistics(),
                firstEventTime,
                lastEventTime,
                getEventTimeSpan(),
                getAverageEventRate()
            );
        }

        /**
         * Clears all analytics data.
         */
        public void clear() {
            eventCounts.clear();
            totalValue.clear();
            materialTypeCounts.clear();
            totalEvents = 0;
            firstEventTime = 0;
            lastEventTime = 0;
        }

        @Override
        public String getObserverName() {
            return "AnalyticsObserver";
        }

        /**
         * Comprehensive analytics data container.
         */
        public static class AnalyticsData {
            private final int totalEvents;
            private final double totalInventoryValue;
            private final int uniqueMaterialCount;
            private final Map<String, Integer> eventStatistics;
            private final Map<String, Integer> materialTypeStatistics;
            private final long firstEventTime;
            private final long lastEventTime;
            private final long eventTimeSpan;
            private final double averageEventRate;

            /**
             * Creates analytics data with comprehensive statistics.
             *
             * @param totalEvents total number of events
             * @param totalInventoryValue total value of inventory
             * @param uniqueMaterialCount number of unique materials
             * @param eventStatistics map of event type to count
             * @param materialTypeStatistics map of material type to count
             * @param firstEventTime timestamp of first event
             * @param lastEventTime timestamp of last event
             * @param eventTimeSpan time span of events
             * @param averageEventRate average rate of events
             */
            public AnalyticsData(int totalEvents, double totalInventoryValue, int uniqueMaterialCount,
                               Map<String, Integer> eventStatistics, Map<String, Integer> materialTypeStatistics,
                               long firstEventTime, long lastEventTime, long eventTimeSpan, double averageEventRate) {
                this.totalEvents = totalEvents;
                this.totalInventoryValue = totalInventoryValue;
                this.uniqueMaterialCount = uniqueMaterialCount;
                this.eventStatistics = new HashMap<>(eventStatistics);
                this.materialTypeStatistics = new HashMap<>(materialTypeStatistics);
                this.firstEventTime = firstEventTime;
                this.lastEventTime = lastEventTime;
                this.eventTimeSpan = eventTimeSpan;
                this.averageEventRate = averageEventRate;
            }

            /** Gets the total number of events. @return total number of events */
            public int getTotalEvents() { return totalEvents; }
            /** Gets the total inventory value. @return total inventory value */
            public double getTotalInventoryValue() { return totalInventoryValue; }
            /** Gets the number of unique materials. @return number of unique materials */
            public int getUniqueMaterialCount() { return uniqueMaterialCount; }
            /** Gets the event statistics map. @return map of event type to count */
            public Map<String, Integer> getEventStatistics() { return new HashMap<>(eventStatistics); }
            /** Gets the material type statistics map. @return map of material type to count */
            public Map<String, Integer> getMaterialTypeStatistics() { return new HashMap<>(materialTypeStatistics); }
            /** Gets the timestamp of first event. @return timestamp of first event */
            public long getFirstEventTime() { return firstEventTime; }
            /** Gets the timestamp of last event. @return timestamp of last event */
            public long getLastEventTime() { return lastEventTime; }
            /** Gets the time span of events. @return time span of events */
            public long getEventTimeSpan() { return eventTimeSpan; }
            /** Gets the average rate of events. @return average rate of events */
            public double getAverageEventRate() { return averageEventRate; }

            @Override
            public String toString() {
                return String.format("AnalyticsData[Events=%d, Value=$%.2f, Materials=%d, Rate=%.2f/s]",
                    totalEvents, totalInventoryValue, uniqueMaterialCount, averageEventRate);
            }
        }

        @Override
        public String toString() {
            return String.format("AnalyticsObserver[Events=%d, Materials=%d, Value=$%.2f]",
                getTotalEvents(), getUniqueMaterialCount(), getTotalInventoryValue());
        }
    }