001    package market.statistics;
002    
003    import java.util.Calendar;
004    import java.util.Iterator;
005    import java.util.LinkedList;
006    import java.util.List;
007    
008    import market.Conversions;
009    import market.MarketCalendar;
010    import market.SMarket;
011    
012    /**
013     * Does calculation on statistics. While {@link Statistics#getArticleStats(String, int, int, int, int)}
014     * sums up all prices and concatenates all history lists, this class {@link #cleanUpPriceHistory() cleans up}
015     * the lists and provides methods to evaluate the statistics.
016     */
017    public class EvaluateStatistics {
018    
019        private CISalesStats ciss;
020        private List cleanedPriceHistory;
021        
022    /*  Unused atm...
023        
024        private int averagePrice;
025        private double averageOrder;
026        private static final int MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;*/
027    
028        /**
029         * @param ciss the history item to be evaluated.
030         */
031        public EvaluateStatistics(CISalesStats ciss) {
032            this.ciss = ciss;
033            cleanedPriceHistory = cleanUpPriceHistory();
034        }
035    
036        /**
037         * Removes "duplicate" items from the {@link CISalesStats#priceHistory priceHistory}.<br>
038         * As every price history is initialized with the current price on creation
039         * (see {@link CSalesStats#initPriceHistory}), there might be multiple items in a row that contain
040         * the same price.<br>
041         * Another possibility to create consecutive items with the same price would be to set a new price in
042         * the evening (after market closed), switch to the next day, set the price again to its original
043         * value and change the day again (or open the market).
044         * This method removes all list items that contain the same price as their predecessor.
045         *
046         * @return the cleaned up price history.
047         */
048        private List cleanUpPriceHistory() {
049            List<HistoryEntry> cleaned = new LinkedList<HistoryEntry>();
050            int lastValue = -1;
051            List<HistoryEntry> l = ciss.getPriceHistory();
052            Iterator<HistoryEntry> it = l.iterator();
053            while (it.hasNext()) {
054                HistoryEntry he = it.next();
055                if (he.getValue() != lastValue) {
056                    cleaned.add(he);
057                    lastValue = he.getValue();
058                }
059            }
060            return cleaned;
061        }
062    
063        /**
064         * @return the date of the very first entry in the {@link #cleanUpPriceHistory cleaned up price history}.
065         */
066        private Calendar firstDate() {
067            return ((HistoryEntry)cleanedPriceHistory.get(0)).getDate();
068        }
069    
070        /**
071         * @return the date of the very last entry in the {@link #cleanUpPriceHistory cleaned up price history}.
072         */
073        private Calendar lastRecordedDate() {
074            return ((HistoryEntry)ciss.getPriceHistory().get(ciss.getPriceHistory().size() - 1)).getDate();
075        }
076    
077    
078        /**
079         * Finds the last date of the time range for which the statistics should be computed (e.g. 31.01.2005).<br>
080         * As every month has at least one item in the <b>original</b> price history, the last date can be
081         * computed from that list.
082         * @return the very last date of the statistic's time range.
083         */
084        private Calendar veryLastDate() {
085            Calendar lastRecordedDate = lastRecordedDate();
086            Calendar c = new MarketCalendar(lastRecordedDate.get(Calendar.YEAR),
087                    lastRecordedDate.get(Calendar.MONTH),
088                    lastRecordedDate.getActualMaximum(Calendar.DAY_OF_MONTH));
089            //do not extrapolate to future, if last month is current month stop computation at day before current date
090            if (!c.before(SMarket.getTime())) {
091                c = new MarketCalendar(SMarket.getYear(), SMarket.getMonth(),
092                        SMarket.getTime().get(Calendar.DATE) - 1);
093            }
094            return c;
095        }
096    
097        /**
098         * If market has just closed (i.e. day-end closing has taken place), take current day into account,
099         * but if the market is open OR just the day has changed and the market hasn't
100         * opened yet, ignore the current day.
101         *
102         * @return <code>true</code> when ignoring the current day for statistics, otherwise <code>false</code>.
103         */
104        private boolean ignoreCurrentDay() {
105            return SMarket.isOpen() || SMarket.hasTimeAdvanced();
106        }
107    
108        /**
109         * @return the number of days covered by the statistics to be evaluated.
110         */
111        private int range() {
112            int difference = Conversions.dayDifference(firstDate(), veryLastDate()) + 1;
113            if (!ignoreCurrentDay()) {
114                difference++;
115            }
116            return difference;
117        }
118    
119    
120        public List getPriceHistory() {
121            return cleanedPriceHistory;
122        }
123    
124        public List getOrderHistory() {
125            return ciss.getOrderHistory();
126        }
127    
128        /**
129         * @return the average price of the article.
130         */
131        public int getAveragePrice() {
132            int range = range();
133            Iterator it = cleanedPriceHistory.iterator();
134            HistoryEntry he = (HistoryEntry)it.next();
135            int previousPrice = he.getValue();
136            Calendar previousDate = he.getDate();
137            int sum = 0;
138            while (it.hasNext()) {
139                he = (HistoryEntry)it.next();
140                sum += previousPrice * Conversions.dayDifference(previousDate, he.getDate());
141                previousDate = he.getDate();
142                previousPrice = he.getValue();
143            }
144            sum += previousPrice * (Conversions.dayDifference(previousDate, veryLastDate()) +
145                    (ignoreCurrentDay() ? 1 : 2));
146            //catch division by 0 if there are no days to be displayed yet (should only happen on date of opening)
147            return (range == 0) ? SMarket.getArticleCatalog().get(ciss.getArticleID()).getBid() : sum/range;
148        }
149    
150        public int getRevenue() {
151            return ciss.getRevenue();
152        }
153    
154        public int getAmount() {
155            return ciss.getAmount();
156        }
157    
158        /**
159         * @return the average of sold items per day.
160         */
161        public double getAverageItemsSold() {
162            int range = range();
163            return range == 0 ? 0 : new Integer(getAmount()).doubleValue()/range;
164        }
165    
166        /**
167         * @return the average of items ordered per order.
168         */
169        public double getAverageOrderAmount() {
170            double avg = 0;
171            Iterator it = ciss.getOrderHistory().iterator();
172            while (it.hasNext()) {
173                avg += ((HistoryEntry)it.next()).getValue();
174            }
175            return ciss.getOrderHistory().size() == 0 ? 0 : avg/ciss.getOrderHistory().size();
176        }
177    
178        /**
179         * @return the average days between two orders of the appropriate item.
180         */
181        public double getAverageDaysBetweenOrders() {
182            double avg = 0;
183            Calendar prevOrder = null;
184            Calendar nextOrder = null;
185            Iterator it = ciss.getOrderHistory().iterator();
186            if (it.hasNext()) {
187                prevOrder = ((HistoryEntry)it.next()).getDate();
188            }
189            while (it.hasNext()) {
190                nextOrder = ((HistoryEntry)it.next()).getDate();
191                avg += Conversions.dayDifference(prevOrder, nextOrder);
192                prevOrder = nextOrder;
193            }
194            if (prevOrder != null) {
195                avg += Conversions.dayDifference(prevOrder, SMarket.getTime());
196            }
197            return prevOrder == null ? 0 : avg/ciss.getOrderHistory().size();
198        }
199    }