/*
* jMemorize - Learning made easy (and fun) - A Leitner flashcards tool
* Copyright(C) 2004-2008 Riad Djemili and contributors
*
* 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 1, 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jmemorize.core.learn;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.Logger;
import jmemorize.core.Main;
import jmemorize.gui.LC;
import jmemorize.gui.Localization;
/**
* This class holds learn session settings which can be saved and loaded. This
* class is used when creating a learn session and defines the strategy that
* should be used while in that learn session. You can enable limits, schedules
* and other customizations.
*
* @author djemili
*/
public class LearnSettings
{
public enum SchedulePreset {CONST, LINEAR, QUAD, EXPONENTIAL, CRAM, CUSTOM}
// schedules
public static final int SCHEDULE_LEVELS = 10;
public static final String[] SCHEDULE_PRESETS = new String[] {
Localization.get(LC.SCHEDULE_CONST),
Localization.get(LC.SCHEDULE_LINEAR),
Localization.get(LC.SCHEDULE_QUAD),
Localization.get(LC.SCHEDULE_EXPONENTIAL),
Localization.get(LC.SCHEDULE_CRAM),
Localization.get(LC.SCHEDULE_CUSTOM)
};
// side mode enums
public static final int SIDES_NORMAL = 0;
public static final int SIDES_FLIPPED = 1;
public static final int SIDES_RANDOM = 2;
public static final int SIDES_BOTH = 3;
// category order when grouping
public static final int CATEGORY_ORDER_FIXED = 0;
public static final int CATEGORY_ORDER_RANDOM = 1;
// Indicates the number of times that each side must be done correctly
// before it is declared 'learned'
private int m_amountToTestFront = 1;
private int m_amountToTestBack = 1;
private SchedulePreset m_schedulePreset;
private int[] m_schedule;
private boolean m_fixedExpirationTimeEnabled;
private int m_fixedExpirationHour;
private int m_fixedExpirationMinute;
private int m_limitTime;
private boolean m_retestFailedCards;
private int m_sides;
private boolean m_groupByCategory;
private int m_categoryOrder;
private float m_shuffleRatio;
private boolean m_limitCardsEnabled;
private boolean m_limitTimeEnabled;
private int m_limitCards;
/**
* Constructs a new learn settings object with default settings.
*/
public LearnSettings()
{
setSchedulePreset(SchedulePreset.LINEAR);
}
/**
* @return <code>true</code> if the card limit is enabled.
*/
public boolean isCardLimitEnabled()
{
return m_limitCardsEnabled;
}
/**
* Enables/disables the card limit.
*
* @param enabled <code>true</code> if the card limit should be enabled.
* <code>false</code> otherwise.
*/
public void setCardLimitEnabled(boolean enabled)
{
m_limitCardsEnabled = enabled;
}
/**
* Sets the cards limit.
*
* @param limit the new card limit.
*/
public void setCardLimit(int limit)
{
m_limitCards = limit;
}
/**
* @return the card limit.
*/
public int getCardLimit()
{
return m_limitCards;
}
/**
* @return <code>true</code> if the time limit is enabled.
*/
public boolean isTimeLimitEnabled()
{
return m_limitTimeEnabled;
}
/**
* Enables/disables the time limit.
*
* @param enabled <code>true</code> if the time limit should be enabled.
* <code>false</code> otherwise.
*/
public void setTimeLimitEnabled(boolean enabled)
{
m_limitTimeEnabled = enabled;
}
/**
* Sets the time limit.
*
* @param limit the time limit in minutes.
*/
public void setTimeLimit(int limit)
{
m_limitTime = limit;
}
/**
* @return the time limit in minutes.
*/
public int getTimeLimit()
{
return m_limitTime;
}
/**
* @param retest <code>true</code> if cards that have been failed while
* learning should be put back into the list of cards to learn.
* <code>false</code> if all cards should never be tested more then once
* in a session.
*/
public void setRetestFailedCards(boolean retest)
{
m_retestFailedCards = retest;
}
/**
* @see LearnSettings#setRetestFailedCards(boolean)
* @return <code>true</code> if failed cards can appear more then once in
* a session.
*/
public boolean isRetestFailedCards()
{
return m_retestFailedCards;
}
/**
* Sets the new side mode. The possible side modes are:
*
* <ul>
* <li>SIDES_NORMAL: show cards in regular front-to-flip mode.</li>
* <li>SIDES_FLIPPED: show cards in flip-to-front mode.</li>
* <li>SIDES_RANDOM: show cards with randomly selected NORMAL or FLIPPED
* sides mode.</li>
* <li>SIDES_BOTH: show both sides of cards as specified in
* {@link #setAmountToTest(boolean, int)}.</li>
* </ul>
*
* @param mode either SIDES_NORMAL, SIDES_FLIPPED, SIDES_RANDOM or
* SIDES_BOTH;
*/
public void setSidesMode(int mode)
{
m_sides = mode;
}
/**
* @return The current sides mode as given by enum SIDES_NORMAL,
* SIDES_FLIPPED and SIDES_RANDOM.
*/
public int getSidesMode()
{
return m_sides;
}
/**
* @param frontside True if you want to get the amount to test the front
* @return The amount you need to test a given side before it is declared
* learnt
*/
public int getAmountToTest(boolean frontside)
{
if (frontside)
return m_amountToTestFront;
else
return m_amountToTestBack;
}
/**
* @param frontside True if you want to set the amount to test the front
* @param value The number of correct consecutive tests before it is
* declared learnt
*/
public void setAmountToTest(boolean frontside, int value)
{
if (frontside)
m_amountToTestFront = value;
else
m_amountToTestBack = value;
}
/**
* The schedule tells how much time should pass before a cards that has
* moved into a higher deck level needs be rechecked.
*
* @param schedule A int array that holds the time span values that need to
* pass and where the index is the deck level before the card moved - e.g.
* <code>schedule[0] = 60</code> says that a card that has moved from deck
* 0 to deck 1 should be rechecked in one hour. The time spans are given in
* minutes.
*/
public void setCustomSchedule(int[] schedule)
{
m_schedule = schedule;
m_schedulePreset = SchedulePreset.CUSTOM;
}
/**
* @see LearnSettings#setSchedule(int[])
* @return The current schedule.
*/
public int[] getSchedule()
{
return m_schedule;
}
/**
* Sets the schedule to one of the available schedule presets.
*
* @param idx the index of the schedule preset. See
* {@link #SCHEDULE_PRESETS}
*/
public void setSchedulePreset(SchedulePreset preset)
{
m_schedule = getPresetSchedule(preset);
m_schedulePreset = preset;
}
/**
* @return the currently set schedule preset.
*/
public SchedulePreset getSchedulePreset()
{
return m_schedulePreset;
}
/**
* @param hour needs to be given in 24-hour format.
*/
public void setFixedExpirationTime(int hour, int minute)
{
m_fixedExpirationHour = hour;
m_fixedExpirationMinute = minute;
}
public int getFixedExpirationHour()
{
return m_fixedExpirationHour;
}
public int getFixedExpirationMinute()
{
return m_fixedExpirationMinute;
}
public void setFixedExpirationTimeEnabled(boolean enable)
{
m_fixedExpirationTimeEnabled = enable;
}
public boolean isFixedExpirationTimeEnabled()
{
return m_fixedExpirationTimeEnabled;
}
/**
* @return the correct expiration date according to the current schedule
* settings.
*
* @param learnDate The moment that the card is learned.
* @param currentLavel The deck level of the card before raising it to the
* next level.
*/
public Date getExpirationDate(Date learnDate, int currentLevel)
{
int deckDelay = getSchedule()[Math.min(currentLevel, 9)];
long millis = learnDate.getTime() + 60l * 1000l * deckDelay;
Date date = new Date(millis);
if (m_fixedExpirationTimeEnabled)
{
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
// if due time already passed today
if (hour > m_fixedExpirationHour ||
(hour == m_fixedExpirationHour && minute >= m_fixedExpirationMinute))
{
cal.add(Calendar.DAY_OF_YEAR, 1);
}
cal.set(Calendar.HOUR_OF_DAY, m_fixedExpirationHour);
cal.set(Calendar.MINUTE, m_fixedExpirationMinute);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
date = cal.getTime();
}
return date;
}
/**
* Enables/disables grouping by categories.
*
* @param enable <code>true</code> if grouping should be enabled.
* <code>false</code> otherwise.
*/
public void setGroupByCategory(boolean enable)
{
m_groupByCategory = enable;
}
/**
* @return <code>true</code> if cards should be grouped by categories.
*/
public boolean isGroupByCategory()
{
return m_groupByCategory;
}
/**
* This method sets the order by which categories will be shown when
* grouping by categories is enabled.
*
* @param order Is either CATEGORY_ORDER_CARDS or CATEGORY_ORDER_FIXED.
*/
public void setCategoryOrder(int order)
{
m_categoryOrder = order;
}
/**
* @return either CATEGORY_ORDER_CARDS or CATEGORY_ORDER_FIXED
*/
public int getCategoryOrder()
{
return m_categoryOrder;
}
/**
* 0.0f means that all cards appear in the order of their level. 1.0f means
* that all cards appear in totally random order.
*
* Something in between denotes the share of cards that will be learned at a
* random level value.
*/
public void setShuffleRatio(float ratio)
{
m_shuffleRatio = ratio;
}
/**
* The share of cards that will appear at a random level in the learn
* session.
*/
public float getShuffleRatio()
{
return m_shuffleRatio;
}
/**
* Gets one of the preset schedules.
*
* @param idx the index of the preset schedule. See
* {@link #SCHEDULE_PRESETS}
* @return one of the preset schedule.
*/
public static int[] getPresetSchedule(SchedulePreset preset)
{
int schedule[] = new int[SCHEDULE_LEVELS];
int presetIndex = preset.ordinal();
if (presetIndex < 0 || presetIndex > 4)
{
Logger log = Main.getLogger();
log.warning("Preset schedule with this index not found."); //$NON-NLS-1$
presetIndex = 1;
}
switch (presetIndex)
{
case 0 : //constant
for (int i = 0; i < SCHEDULE_LEVELS; i++)
{
schedule[i] = 60 * 24;
}
return schedule;
case 1 : //linear
for (int i = 0; i < SCHEDULE_LEVELS; i++)
{
schedule[i] = (i+1) * 60 * 24;
}
return schedule;
case 2 : //quadratic
for (int i = 0; i < SCHEDULE_LEVELS; i++)
{
schedule[i] = (int)Math.pow(i+1, 2) * 60 * 24;
}
return schedule;
case 3 : //exponential
for (int i = 0; i < SCHEDULE_LEVELS; i++)
{
schedule[i] = (int)Math.pow(2, i) * 60 * 24;
}
return schedule;
case 4 : //cram
default:
for (int i = 0; i < SCHEDULE_LEVELS; i++)
{
schedule[i] = (i+1) * 5;
}
return schedule;
}
}
}