Project: jMemorize
/*
 * 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+12) * 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; 
        } 
    } 
}