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());
    }
}