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