/*****************************************************************************************
* Copyright (c) 2011 Norbert Nagold <norbert.nagold@gmail. *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.service;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ichi2.anki.db.AnkiDatabaseManager;
import com.ichi2.anki.model.Deck;
import com.ichi2.themes.Themes;
import android.content.Context;
import android.content.res.Resources;
public class Statistics {
public static Logger log = LoggerFactory.getLogger(Statistics.class);
public static String[] axisLabels = { "", "" };
public static String sTitle;
public static String[] Titles;
public static double[] xAxisData;
public static double[][] sSeriesList;
private static Deck sDeck;
private static Map<String, Object> sDeckSummaryValues;
public static int sType;
public static int sZoom = 0;
/**
* Types
*/
public static final int TYPE_DUE = 0;
public static final int TYPE_CUMULATIVE_DUE = 1;
public static final int TYPE_INTERVALS = 2;
public static final int TYPE_REVIEWS = 3;
public static final int TYPE_REVIEWING_TIME = 4;
public static final int TYPE_DECK_SUMMARY = 5;
public static void initVariables(Context context, int type, int period, String title) {
Resources res = context.getResources();
if (type == TYPE_DECK_SUMMARY) {
sDeckSummaryValues = new HashMap<String, Object>();
return;
}
sType = type;
sTitle = title;
axisLabels[0] = res.getString(R.string.statistics_period_x_axis);
axisLabels[1] = res.getString(R.string.statistics_period_y_axis);
if (type <= TYPE_CUMULATIVE_DUE) {
Titles = new String[3];
Titles[0] = res.getString(R.string.statistics_young_cards);
Titles[1] = res.getString(R.string.statistics_mature_cards);
Titles[2] = res.getString(R.string.statistics_failed_cards);
sSeriesList = new double[3][period];
xAxisData = xAxisData(period, false);
} else if (type == TYPE_REVIEWS) {
Titles = new String[3];
Titles[0] = res.getString(R.string.statistics_new_cards);
Titles[1] = res.getString(R.string.statistics_young_cards);
Titles[2] = res.getString(R.string.statistics_mature_cards);
sSeriesList = new double[3][period];
xAxisData = xAxisData(period, true);
} else {
Titles = new String[1];
sSeriesList = new double[1][period];
switch (type) {
case TYPE_INTERVALS:
xAxisData = xAxisData(period, false);
break;
case TYPE_REVIEWING_TIME:
xAxisData = xAxisData(period, true);
axisLabels[1] = context.getResources().getString(R.string.statistics_period_x_axis_minutes);
break;
}
}
sZoom = 0;
}
public static boolean refreshDeckStatistics(Context context, Deck deck, int type, int period, String title) {
initVariables(context, type, period, title);
sDeck = deck;
if (type == TYPE_DECK_SUMMARY) {
sDeckSummaryValues = sDeck.getDeckSummary();
return sDeckSummaryValues != null ? true : false;
} else {
sSeriesList = getSeriesList(context, type, period);
return sSeriesList != null ? true : false;
}
}
public static boolean refreshAllDeckStatistics(Context context, String[] deckPaths, int type, int period, String title) {
initVariables(context, type, period, title);
for (String dp : deckPaths) {
sDeck = DeckManager.getDeck(dp, DeckManager.REQUESTING_ACTIVITY_STATISTICS);
if (sDeck == null) {
continue;
}
if (type == TYPE_DECK_SUMMARY) {
sDeckSummaryValues.put("title", context.getResources().getString(R.string.deck_summary_all_decks));
Map<String, Object> values = sDeck.getDeckSummary();
if (values == null) {
continue;
}
for (Entry<String, Object> entry : values.entrySet()) {
if (entry.getKey().equals("deckAge")) {
if (sDeckSummaryValues.containsKey(entry.getKey())) {
sDeckSummaryValues.put(entry.getKey(), Math.max(sDeckSummaryValues.getAsInteger(entry.getKey()), (Integer) entry.getValue()));
} else {
sDeckSummaryValues.put(entry.getKey(), (Integer) entry.getValue());
}
} else {
if (sDeckSummaryValues.containsKey(entry.getKey())) {
sDeckSummaryValues.put(entry.getKey(), sDeckSummaryValues.getAsInteger(entry.getKey()) + (Integer) entry.getValue());
} else {
sDeckSummaryValues.put(entry.getKey(), (Integer) entry.getValue());
}
}
}
} else {
double[][] seriesList;
seriesList = getSeriesList(context, type, period);
for (int i = 0; i < sSeriesList.length; i++) {
for (int j = 0; j < period; j++) {
sSeriesList[i][j] += seriesList[i][j];
}
}
}
DeckManager.closeDeck(dp, DeckManager.REQUESTING_ACTIVITY_STATISTICS, false);
}
if (type == TYPE_DECK_SUMMARY) {
return (sDeckSummaryValues != null && sDeckSummaryValues.size() > 0) ? true : false;
} else {
return sSeriesList != null ? true : false;
}
}
public static double[][] getSeriesList(Context context, int type, int period) {
double[][] seriesList;
AnkiDb ankiDB = AnkiDatabaseManager.getDatabase(sDeck.getDeckPath());
ankiDB.getDatabase().beginTransaction();
try {
switch (type) {
case TYPE_DUE:
seriesList = new double[3][period];
seriesList[0] = getCardsByDue(period, false);
seriesList[1] = getMatureCardsByDue(period, false);
seriesList[2] = getFailedCardsByDue(period, false);
seriesList[0][1] += seriesList[2][1];
seriesList[1][0] += seriesList[2][0];
seriesList[1][1] += seriesList[2][1];
break;
case TYPE_CUMULATIVE_DUE:
seriesList = new double[3][period];
seriesList[0] = getCardsByDue(period, true);
seriesList[1] = getMatureCardsByDue(period, true);
seriesList[2] = getFailedCardsByDue(period, true);
seriesList[1][0] += seriesList[2][0];
for (int i = 1; i < period; i++) {
seriesList[0][i] += seriesList[2][i];
seriesList[1][i] += seriesList[2][i];
}
break;
case TYPE_INTERVALS:
seriesList = new double[1][period];
seriesList[0] = getCardsByInterval(period);
break;
case TYPE_REVIEWS:
seriesList = getReviews(period);
break;
case TYPE_REVIEWING_TIME:
seriesList = new double[1][period];
seriesList[0] = getReviewTime(period);
break;
default:
seriesList = null;
}
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
return seriesList;
}
public static double[] xAxisData(int length, boolean backwards) {
double series[] = new double[length];
if (backwards) {
for (int i = 0; i < length; i++) {
series[i] = i - length + 1;
}
} else {
for (int i = 0; i < length; i++) {
series[i] = i;
}
}
return series;
}
public static double[] getCardsByDue(int length, boolean cumulative) {
double series[] = new double[length];
series[0] = sDeck.getDueCount();
for (int i = 1; i < length; i++) {
int count = 0;
count = sDeck.getNextDueCards(i);
if (cumulative) {
series[i] = count + series[i - 1];
} else {
series[i] = count;
}
}
return series;
}
public static double[] getMatureCardsByDue(int length, boolean cumulative) {
double series[] = new double[length];
for (int i = 0; i < length; i++) {
int count = 0;
count = sDeck.getNextDueMatureCards(i);
if (cumulative && i > 0) {
series[i] = count + series[i - 1];
} else {
series[i] = count;
}
}
return series;
}
public static double[] getFailedCardsByDue(int length, boolean cumulative) {
double series[] = new double[length];
series[0] = sDeck.getFailedSoonCount();
series[1] = sDeck.getFailedDelayedCount();
if (cumulative) {
series[1] += series[0];
for (int i = 2; i < length; i++) {
series[i] = series[1];
}
}
return series;
}
public static double[][] getReviews(int length) {
double series[][] = new double[3][length];
for (int i = 0; i < length; i++) {
int result[] = sDeck.getDaysReviewed(i - length + 1);
series[0][i] = result[0];
series[1][i] = result[1];
series[2][i] = result[2];
}
return series;
}
public static double[] getReviewTime(int length) {
double series[] = new double[length];
for (int i = 0; i < length; i++) {
series[i] = sDeck.getReviewTime(i - length + 1) / 60;
}
return series;
}
public static double[] getCardsByInterval(int length) {
double series[] = new double[length];
for (int i = 0; i < length; i++) {
series[i] = sDeck.getCardsByInterval(i);
}
return series;
}
public static double getFraction(double numerator, double denominator) {
if (denominator == 0) {
return 0;
} else {
return numerator / denominator;
}
}
/*
private static String getHtmlDeckSummary(Context context) {
Resources res = context.getResources();
int cardCount = sDeckSummaryValues.get("cardCount");
int matureCount = sDeckSummaryValues.getAsInteger("matureCount");
int unseenCount = sDeckSummaryValues.getAsInteger("unseenCount");
int youngCount = cardCount - unseenCount - matureCount;
int intervalSum = sDeckSummaryValues.getAsInteger("intervalSum");
int repsMatCount = sDeckSummaryValues.getAsInteger("repsMatCount");
int repsMatNoCount = sDeckSummaryValues.getAsInteger("repsMatNoCount");
int repsYoungCount = sDeckSummaryValues.getAsInteger("repsYoungCount");
int repsYoungNoCount = sDeckSummaryValues.getAsInteger("repsYoungNoCount");
int repsFirstCount = sDeckSummaryValues.getAsInteger("repsFirstCount");
int repsFirstNoCount = sDeckSummaryValues.getAsInteger("repsFirstNoCount");
int reviewsLastWeek = sDeckSummaryValues.getAsInteger("reviewsLastWeek");
int newsLastWeek = sDeckSummaryValues.getAsInteger("newsLastWeek");
int reviewsLastMonth = sDeckSummaryValues.getAsInteger("reviewsLastMonth");
int newsLastMonth = sDeckSummaryValues.getAsInteger("newsLastMonth");
int reviewsLastYear = sDeckSummaryValues.getAsInteger("reviewsLastYear");
int newsLastYear = sDeckSummaryValues.getAsInteger("newsLastYear");
int deckAge = sDeckSummaryValues.getAsInteger("deckAge");
int revTomorrow = sDeckSummaryValues.getAsInteger("revTomorrow");
int newTomorrow = sDeckSummaryValues.getAsInteger("newTomorrow");
int timeTomorrow = sDeckSummaryValues.getAsInteger("timeTomorrow");
StringBuilder builder = new StringBuilder();
builder.append("<html><body text=\"#FFFFFF\">");
builder.append(res.getString(R.string.deck_summary_deck_age, deckAge)).append("<br>");
builder.append(res.getString(R.string.deck_summary_cards)).append(" <b>").append(Integer.toString(cardCount)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_facts)).append(" <b>").append(Integer.toString(sDeckSummaryValues.getAsInteger("factCount"))).append("</b><br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_card_age)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_cards_mature)).append(" <b>").append(Integer.toString(matureCount)).append("</b> (").append(String.format("%.1f %", 100.0d * getFraction(matureCount, cardCount))).append(")<br>");
builder.append(res.getString(R.string.deck_summary_cards_young)).append(" <b>").append(Integer.toString(youngCount)).append("</b> (").append(String.format("%.1f %", 100.0d * getFraction(youngCount, cardCount))).append(")<br>");
builder.append(res.getString(R.string.deck_summary_cards_unseen)).append(" <b>").append(Integer.toString(unseenCount)).append("</b> (").append(String.format("%.1f %", 100.0d * getFraction(unseenCount, cardCount))).append(")<br>");
builder.append(res.getString(R.string.deck_summary_average_interval, getFraction(intervalSum, (cardCount - unseenCount)))).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_answers_correct)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_answers_mature, 100.0d * getFraction(repsMatCount - repsMatNoCount, repsMatCount), "%", repsMatCount - repsMatNoCount, repsMatCount)).append("<br>");
builder.append(res.getString(R.string.deck_summary_answers_young, 100.0d * getFraction(repsYoungCount - repsYoungNoCount, repsYoungCount), "%", repsYoungCount - repsYoungNoCount, repsYoungCount)).append("<br>");
builder.append(res.getString(R.string.deck_summary_answers_firstseen, 100.0d * getFraction(repsFirstCount - repsFirstNoCount, repsFirstCount), "%", repsFirstCount - repsFirstNoCount, repsFirstCount)).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_forecast)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_tomorrow_due, revTomorrow)).append("<br>");
builder.append(res.getString(R.string.deck_summary_tomorrow_new, newTomorrow)).append("<br>");
builder.append(res.getString(R.string.deck_summary_tomorrow_time, timeTomorrow)).append("<br>");
builder.append(res.getString(R.string.deck_summary_finished_in, (int)Math.round(getFraction(unseenCount, newTomorrow)))).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_average_week)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_reviews)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(reviewsLastWeek, Math.min((double)deckAge, 7.0d)))).append("<br>");
builder.append(res.getString(R.string.deck_summary_news)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(newsLastWeek, Math.min((double)deckAge, 7.0d)))).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_average_month)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_reviews)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(reviewsLastMonth, Math.min((double)deckAge, 30.0d)))).append("<br>");
builder.append(res.getString(R.string.deck_summary_news)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(newsLastMonth, Math.min((double)deckAge, 30.0d)))).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_average_year)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_reviews)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(reviewsLastYear, Math.min((double)deckAge, 365.0d)))).append("<br>");
builder.append(res.getString(R.string.deck_summary_news)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(newsLastYear, Math.min((double)deckAge, 365.0d)))).append("<br>");
builder.append("<br><b>").append(res.getString(R.string.deck_summary_average_total)).append("</b><br>");
builder.append(res.getString(R.string.deck_summary_reviews)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(repsMatCount + repsYoungCount + repsFirstCount, deckAge))).append("<br>");
builder.append(res.getString(R.string.deck_summary_news)).append(" ").append(res.getString(R.string.deck_summary_cards_per_day, getFraction(matureCount + youngCount, deckAge))).append("<br>");
builder.append("</body></html>");
return builder.toString();
}
public static void showDeckSummary(Context context) {
Themes.htmlOkDialog(context, sDeckSummaryValues.containsKey("title") ? sDeckSummaryValues.getAsString("title") : sDeck.getDeckName(), getHtmlDeckSummary(context)).show();
} */
}