/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: mnemosyne.c 17847 2008-06-28 18:10:04Z bagder $
 *
 * Copyright (C) 2008 Joseph Garvin
 *
 * 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 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/*

Database File Layout:

,rockbox/mnemosyne is folder for database. Every .txt file inside the folder contains
a one line file that describes a single flash card. The line is laid out like so:

NEXT_REP@INTERVAL@PAST_REPETITIONS@EASY_FACTOR@ ITEM_QUESTION@ITEM_ANSWER

There are also two subfolders, .rockbox/mnemosyne/forgot and .rockbox/mnemosyne/new. Forgot is 
used internally by mnemosyne for temporarily storing cards with low grades. To make a new card, save
 it as a text file in the new folder like so:
 
 0@0@0@2.5@Your question@Your answer
 
 There are two more subfolders, .rockbox/mnemosyne/fuure, which is used by mnemosyne internally
 to organize cards by when they will expire, and .rockbox/mnemosyne/toedit, which cards will get
 chucked into if you press the record button before grading them. This way they are set aside so
 you can edit them later when the player is connected to the computer.

*/

/*
Preference order:
-Expired cards that have been repeated before
-Unexpired cards that have been repeated before but with a grade of 0 or 1
-New cards that have never been repeated
-Unexpired priorly repeated cards
 */

#include "plugin.h"
#include "lib/helper.h"

PLUGIN_HEADER

#ifdef BUTTON_OFF
#define MNEMO_QUIT BUTTON_OFF
#endif
#ifdef BUTTON_POWER
#define MNEMO_QUIT BUTTON_POWER
#endif
#ifdef BUTTON_MENU
#define MNEMO_QUIT BUTTON_MENU
#endif

static const struct plugin_api* rb;

#define MIN_EASY_FACTOR 1.3

#define MAX_QUESTION_CHAR_SIZE 2000
#define MAX_ANSWER_CHAR_SIZE 2000

#define FLOAT_EPSILON 2.0e-024 /* Test if should be e-023 because of sign bit */

#define MNEMO_DATABASE_DIR ROCKBOX_DIR "/mnemosyne/"
#define MNEMO_FORGOT_DIR ROCKBOX_DIR "/mnemosyne/forgot/"
#define MNEMO_NEW_DIR ROCKBOX_DIR "/mnemosyne/new/"
#define MNEMO_TOEDIT_DIR ROCKBOX_DIR "/mnemosyne/toedit/"
#define MNEMO_FUTURE_DIR ROCKBOX_DIR "/mnemosyne/future/"

static DIR* database_dir;
static char database_folder[500];
static char cur_item_file_name[500];
static char cur_item_file_full_path[500];
static int cur_item_file;
static struct item cur_item;
#define MNEMO_DATABASE_BUFFER_SIZE MAX_QUESTION_CHAR_SIZE + MAX_ANSWER_CHAR_SIZE + 1000
static char database_buffer[MNEMO_DATABASE_BUFFER_SIZE];

/* Expired and future seek modes both actually look for expired cards, but expired looks in the root mnemosyne folder and future looks
in the subfolders of the future folder. This is to ease transition. Later should be able to get rid of expired mode altogether... */
enum seek_mode_t { SEEKING_EXPIRED, SEEKING_FUTURE, SEEKING_FORGOTTEN, SEEKING_NEW, SEEKING_REPEAT } seek_mode = SEEKING_EXPIRED;

struct item
{
	long next_rep;
	int interval;
	int past_repetitions;
	float easy_factor;
	char question[MAX_QUESTION_CHAR_SIZE];
	char answer[MAX_ANSWER_CHAR_SIZE];
};

static int char_width = -1;
static int char_height = -1;
static bool usb_detected = false;
static bool should_quit = false;

static bool some_forgotten = false;

static unsigned long string_to_long(const char* toConvert)
{
	unsigned long result = 0;
	int i;
	unsigned long pow_of_10 = 1;

	for(i = rb->strlen(toConvert); i > 0; --i) {
		result += (toConvert[i-1] - '0') * pow_of_10;
		pow_of_10 *= 10;
	}

	return result;
}

/* Assumes positive no whitespace float with valid digits */
static float atof( char s[] )
{
	int i = 0;
	float val, power;

	for( val = 0.0; s[i] != '\0' && s[i] != '.'; ++i )
	{
		val = (10.0 * val) + (s[i] - '0');

		/* s[i] - '0' (zero in quotes)
		* always gives "int i" as output
		*/
	}

	if( s[i] == '.' )
	{
		++i;
		/* skip the dot(.) of a floating point */
	}

	for( power = 1.0; s[i] != '\0'; ++i )
	{
		val = (10.0 * val) + (s[i] - '0');
		power *= 10.0;
	}

	return val / power;
}

static char* float_to_string(float toConvert, char* buffer)
{
	char convert_buffer[100];
	int chars_written = 0;
	int i = 0;
	int after_decimal_zeroes = 0;

	unsigned long before_decimal = (unsigned long)toConvert;
	double after_decimal_part = (double)toConvert - (double)before_decimal;
	unsigned long after_decimal = (unsigned long)(after_decimal_part * 10000.0); /* 5 post-dec digits, so 10,000 */

	/* Detect 0s after decimal point, multiplying by 10,000 will erase, need to compensate */
	if(after_decimal_part > FLOAT_EPSILON)
		for(after_decimal_zeroes = -1; after_decimal_part < 1; ++after_decimal_zeroes)
			after_decimal_part *= 10.0;

	chars_written = rb->snprintf(convert_buffer, 100, "%ld", before_decimal);
	convert_buffer[chars_written++] = '.';
	
	for(i = 0; i < after_decimal_zeroes; ++i)
		convert_buffer[chars_written++] = '0';

	rb->snprintf(convert_buffer + chars_written, 100 - chars_written, "%ld", after_decimal);
	
	rb->strcpy(buffer, convert_buffer);

	return buffer; /* To simplify calling */
}

static bool is_folder_empty(const char* folder_path)
{
	struct dirent* entry;
	DIR* the_dir = rb->opendir(folder_path);

	do {
		entry = rb->readdir(the_dir);

		if(!entry) {
			rb->closedir(the_dir);
			return true;
		}
	} while(rb->strcmp(entry->d_name, ".") == 0  ||
			rb->strcmp(entry->d_name, "..") == 0);
	
	rb->closedir(the_dir);
	return false;
}

static bool find_folder_for_time(time_t expire_time, char* returned_folder, bool check_both_ends)
{
	struct dirent* entry;
	DIR* future_dir = rb->opendir(MNEMO_FUTURE_DIR);
	long folder_start_time = 0, folder_end_time = 0;
	
	DEBUGF("Searching for folder for time: %ld\n", expire_time);
	
	while(true) {
		entry = rb->readdir(future_dir);

		if(!entry) {
			rb->closedir(future_dir);
			return false;
		}
		
		if(rb->strcmp(entry->d_name, ".") == 0 ||
		   rb->strcmp(entry->d_name, "..") == 0) {
		   continue;
		}
		
		rb->strcpy(returned_folder, MNEMO_FUTURE_DIR);
		rb->strcat(returned_folder, entry->d_name);
		rb->strcat(returned_folder, "/");
		
		if(is_folder_empty(returned_folder)) {
			rb->closedir(future_dir);
			
			DEBUGF("Deleting folder: %s\n", returned_folder);
			rb->rmdir(returned_folder);
			
			future_dir = rb->opendir(MNEMO_FUTURE_DIR);
			continue;
		}
		
		DEBUGF("Checking folder for expiration: %s\n", entry->d_name);
		
		folder_start_time = string_to_long(entry->d_name);
		folder_end_time = folder_start_time + 1 * 24 * 60 * 60;
		
		DEBUGF("Folder start time: %ld Folder end time: %ld\n", folder_start_time, folder_end_time);
		
		if(expire_time >= folder_start_time && (!check_both_ends || expire_time < folder_end_time)) {
			break;
		}
	}

	rb->closedir(future_dir);
	return true;
}

static void move_item_to_future()
{
	char folder_path_buffer[200];
	bool search_success = find_folder_for_time(cur_item.next_rep, folder_path_buffer, true);
	
	DEBUGF("Search success: %s\n folder_buffer: %s\n", search_success ? "Yes" : "No", folder_path_buffer);
	
	if(!search_success) {
		rb->strcpy(folder_path_buffer, MNEMO_FUTURE_DIR);
		rb->snprintf(folder_path_buffer + rb->strlen(MNEMO_FUTURE_DIR), 
					 200 - rb->strlen(MNEMO_FUTURE_DIR), "%ld/",
					 cur_item.next_rep);
					 
		DEBUGF("making folder: %s\n", folder_path_buffer);
		rb->mkdir(folder_path_buffer);
	}
	
	rb->strcat(folder_path_buffer, cur_item_file_name);
	
	DEBUGF("Renaming %s to %s\n", cur_item_file_full_path, folder_path_buffer);
	rb->rename(cur_item_file_full_path, folder_path_buffer);
}

/**
 * Display text. Shamelessly stolen from the star plugin code :D
 */
static void simple_display_text(const char *str)
{
    int chars_by_line;
    int lines_by_screen;
    int chars_for_line;
    int current_line = 0;
    int first_char_index = 0;
    const char *ptr_char;
    char *ptr_line;
    int i;
    char line[255];

    rb->lcd_getstringsize("a", &char_width, &char_height);

    rb->lcd_clear_display();

    chars_by_line = LCD_WIDTH / char_width;
    lines_by_screen = LCD_HEIGHT / char_height;

    do
    {
        ptr_char = str + first_char_index;
        chars_for_line = 0;
        i = 0;
        ptr_line = line;
        while (i < chars_by_line)
        {
            switch (*ptr_char)
            {
                case '\t':
                case ' ':
                    *(ptr_line++) = ' ';
                case '\n':
                case '\0':
                    chars_for_line = i;
                    break;

                default:
                    *(ptr_line++) = *ptr_char;
            }
            if (*ptr_char == '\n' || *ptr_char == '\0')
                break;
            ptr_char++;
            i++;
        }

        if (chars_for_line == 0)
            chars_for_line = i;

        line[chars_for_line] = '\0';

        /* test if we have cut a word. If it is the case we don't have to */
        /* skip the space */
        if (i == chars_by_line && chars_for_line == chars_by_line)
            first_char_index += chars_for_line;
        else
            first_char_index += chars_for_line + 1;

        /* print the line on the screen */
        rb->lcd_putsxy(0, current_line * char_height, line);

        /* if the number of line showed on the screen is equals to the */
        /* maximum number of line we can show, we wait for a key pressed to */
        /* clear and show the remaining text. */
        current_line++;
    } while (*ptr_char != '\0');

	rb->lcd_update();
}

static inline float max(float x, float y)
{
	return x > y ? x : y;
}

static inline int ceil(float x)
{
	return (int)(x + 1.0 - FLOAT_EPSILON);
}

static inline bool is_item_expired(struct item* an_item)
{
	time_t current_time = rb->mktime(rb->get_time());

	return an_item->next_rep <= current_time;
}

static int calc_new_interval(struct item* an_item)
{
	if(an_item->past_repetitions == 1)
		return 1;
	else if(an_item->past_repetitions == 2)
		return 6;
		
	return ceil((float)(an_item->interval) * an_item->easy_factor);
}

static float calc_new_easy_factor(float old_easy_factor, int response_quality)
{
	float new_easy_factor;

	/* This should happen naturally based on the formula below, but
	   sometimes floating point arithmetic is tricky, so just in case */
	if(response_quality == 4)
		return old_easy_factor;

	/* Formula is taken from Supermemo2 algorithm description, constants were
	   discovered heuristically. */
	new_easy_factor = old_easy_factor + 
		(0.1 - (float)(5 - response_quality) * 
		 (0.08 + (float)(5 - response_quality) * 0.02));

	return max(new_easy_factor, MIN_EASY_FACTOR);
}

/* Tries to set seek mode, returns false if failed */
static bool set_seek_mode(enum seek_mode_t mode)
{
	bool success = false;
	static char folder_path_buffer[200];
	
	switch(mode) {
	case SEEKING_EXPIRED:
		rb->strcpy(database_folder, MNEMO_DATABASE_DIR);
		success = true;
		break;
	case SEEKING_FUTURE:		
		if(find_folder_for_time(rb->mktime(rb->get_time()), folder_path_buffer, false)) {
			rb->strcpy(database_folder, folder_path_buffer);
			success = true;
		}
		else
			success = false;
		
		break;
	case SEEKING_FORGOTTEN:
		some_forgotten = false;
		rb->strcpy(database_folder, MNEMO_FORGOT_DIR);
		success = true;
		break;
	case SEEKING_NEW:
		rb->strcpy(database_folder, MNEMO_NEW_DIR);
		success = true;
		break;
	}
	
	seek_mode = mode;
	return success;
}

static void cycle_seek_mode()
{
	if(seek_mode == SEEKING_EXPIRED || seek_mode == SEEKING_FUTURE) {
		if(!set_seek_mode(SEEKING_FUTURE)) { /* Maybe another folder of expired cards */
			set_seek_mode(SEEKING_FORGOTTEN);
		}
	}
	else if(seek_mode == SEEKING_FORGOTTEN && !some_forgotten) {
		set_seek_mode(SEEKING_NEW);
	}
	else if(seek_mode == SEEKING_NEW)
		set_seek_mode(SEEKING_FORGOTTEN);
}

static void init_database()
{
	if(database_dir) {
		rb->closedir(database_dir);
		database_dir = 0;
	}

	DEBUGF("Checking for database directory: %s\n", database_folder);

	if(!rb->dir_exists(database_folder)) {
		DEBUGF("Error! Database expired folder not found!\n");
	}
	else {
		DEBUGF("Database folder found\n");
	}

	database_dir = rb->opendir(database_folder);
}

static bool valid_item_file_name(const char* name)
{
	return rb->strcmp(name, ".") != 0  &&
		 rb->strcmp(name, "..") != 0 &&
		 rb->strcmp(name, "new") != 0 &&
		 rb->strcmp(name, "forgot") != 0 &&
		 rb->strcmp(name, "toedit") != 0 &&
		 rb->strcmp(name, "future") != 0;
}

static bool find_item_file()
{
	struct dirent* entry = NULL;

	do {
		entry = rb->readdir(database_dir);

		if(!entry)
			return false;
	} while(!valid_item_file_name(entry->d_name));

	rb->strcpy(cur_item_file_full_path, database_folder);
	rb->strcat(cur_item_file_full_path, entry->d_name);

	rb->strcpy(cur_item_file_name, entry->d_name);

	cur_item_file = rb->open(cur_item_file_full_path, O_RDWR);
	if(cur_item_file < 0) {
		DEBUGF("Error, couldn't find file: %s full path: %s\n", entry->d_name, cur_item_file_full_path);
		return false;
	}

	DEBUGF("File found: %s\n", entry->d_name);
	return true;
}

static int read_and_fill_until(int fd, char* fill, char until)
{
	char ch;
	int fillindex = 0;

	while(1) {
		if(!rb->read(fd, &ch, 1) || ch == EOF)
			break;
		if(ch == until)
			break;
		fill[fillindex++] = ch;
	}

	fill[fillindex] = '\0';

	return fillindex;
}

static bool want_to_test_item()
{
	DEBUGF("Buffer: %s\n", database_buffer);

	read_and_fill_until(cur_item_file, database_buffer, '@');
	DEBUGF("Token: %s\n Length: %lu\n", database_buffer, rb->strlen(database_buffer));
	cur_item.next_rep = string_to_long(database_buffer);

	if(seek_mode == SEEKING_EXPIRED && !is_item_expired(&cur_item))
		return false;
		
	read_and_fill_until(cur_item_file, database_buffer, '@');
	DEBUGF("Token: %s\n Length: %lu\n", database_buffer, rb->strlen(database_buffer));
	cur_item.interval = rb->atoi(database_buffer);
	
	read_and_fill_until(cur_item_file, database_buffer, '@');
	DEBUGF("Token: %s\n Length: %lu\n", database_buffer, rb->strlen(database_buffer));
	cur_item.past_repetitions = rb->atoi(database_buffer);

	read_and_fill_until(cur_item_file, database_buffer, '@');
	DEBUGF("Token: %s\n Length: %lu\n", database_buffer, rb->strlen(database_buffer));
	cur_item.easy_factor = atof(database_buffer);

	read_and_fill_until(cur_item_file, cur_item.question, '@');
	DEBUGF("Token: %s\n Length: %lu\n", cur_item.question, rb->strlen(cur_item.question));

	read_and_fill_until(cur_item_file, cur_item.answer, 'ç'); /* Nonsense char */
	DEBUGF("Token: %s\n Length: %lu\n", cur_item.answer, rb->strlen(cur_item.answer));

	return true;
}

static const char* seek_mode_name()
{
	switch(seek_mode) {
	case SEEKING_EXPIRED:
		return "SEEKING_EXPIRED";
	case SEEKING_FUTURE:
		return "SEEKING_FUTURE";
	case SEEKING_FORGOTTEN:
		return "SEEKING_FORGOTTEN";
	case SEEKING_NEW:
		return "SEEKING_NEW";
	case SEEKING_REPEAT:
		return "SEEKING_REPEAT";
	default:
		return "Unknown seek mode!";
	}
}

static void debug_print_seek_mode()
{
	DEBUGF("Seek mode: ");
	DEBUGF(seek_mode_name());
	DEBUGF("\n");
}

static bool find_next_item()
{
	while(find_item_file()) {
		if(want_to_test_item())
			return true;

		if(cur_item_file > 0)
			rb->close(cur_item_file);
	}

	simple_display_text(seek_mode_name());
	while(rb->button_get(true) != BUTTON_SELECT) {}

	cycle_seek_mode();
	init_database();

	return find_next_item();
}

static void debug_print_current_item()
{
	DEBUGF("Current item - Next rep: %ld Repetitions: %d Easy Factor: %f Question: %s Answer: %s\n",
		   cur_item.next_rep,
		   cur_item.past_repetitions,
		   cur_item.easy_factor,
		   cur_item.question,
		   cur_item.answer);
}

/* Returns line number of item in text file */
static void present_question()
{
	simple_display_text(cur_item.question);
	while(rb->button_get(true) != BUTTON_SELECT) {}

	rb->strcpy(database_buffer, cur_item.question);
	rb->strcat(database_buffer, "\n\n");
	rb->strcat(database_buffer, cur_item.answer);
	rb->strcat(database_buffer, "\n\n<0    >1    ^2    v3    .-4    -.5");

	simple_display_text(database_buffer);
}

static void give_response_feedback(int response_quality)
{
	char response_buffer[500];
	int i = 0;
	int chars_by_line;

    rb->lcd_getstringsize("a", &char_width, &char_height);

    chars_by_line = LCD_WIDTH / char_width;

	for(i = 0; i < chars_by_line; ++i)
		rb->snprintf(response_buffer + i, 2, "%d", response_quality);
	response_buffer[i] = '\0';

	rb->lcd_set_foreground(LCD_RGBPACK(255 - (255 / 5) * response_quality, 0 + (255 / 5) * response_quality, 0));
	rb->lcd_putsxy(0, LCD_HEIGHT - char_height, response_buffer);
	rb->lcd_set_foreground(LCD_RGBPACK(255, 255, 255));

	rb->lcd_update();

	rb->sleep(0.10*HZ);
}

/* Waits for the user to request the answer, then returns their inputted score */
static bool want_to_edit = false;
static int query_response()
{
	int score = -1, key = 0;
	
	want_to_edit = false;
	
	while(score == -1) {
		key = rb->button_get(true);
		switch (key)
		{
		case BUTTON_REC:
			want_to_edit = true;
			break;
		case BUTTON_LEFT:
			score = 0;
			break;
		case BUTTON_RIGHT:
			score = 1;
			break;
		case BUTTON_UP:
			score = 2;
			break;
		case BUTTON_DOWN:
			score = 3;
			break;
		case BUTTON_SCROLL_BACK:
			score = 4;
			break;
		case BUTTON_SCROLL_FWD:
			score = 5;
			break;
		case MNEMO_QUIT:
			should_quit = true;
			break;
		default:
			if (rb->default_event_handler(key) == SYS_USB_CONNECTED)
			{
				should_quit = true;
				usb_detected = true;
				break;
			}
		}
	}

	give_response_feedback(score);

	return score;
}

static void debug_test_string_conversion()
{
	char convert_buffer[100];

	DEBUGF("string_to_long test, should be: 63216, result: %ld\n", string_to_long("63216"));
	DEBUGF("string_to_long test, should be: 0, result: %ld\n", string_to_long("0"));
	DEBUGF("string_to_long test, should be: 942308, result: %ld\n", string_to_long("942308"));
	DEBUGF("string_to_long test, should be: 1, result: %ld\n", string_to_long("1"));
	DEBUGF("string_to_long test, should be: 91309921, result: %ld\n", string_to_long("91309921"));

	DEBUGF("atof test, should be: 63216.231, result: %.10f\n", atof("63216.231"));
	DEBUGF("atof test, should be: 0.0, result: %.10f\n", atof("0.0"));
	DEBUGF("atof test, should be: 942308.2751, result: %.10f\n", atof("942308.2751"));
	DEBUGF("atof test, should be: 1.3, result: %.10f\n", atof("1.3"));
	DEBUGF("atof test, should be: 913.132, result: %.10f\n", atof("913.132"));
	DEBUGF("atof test, should be: 32913.05967, result: %.10f\n", atof("32913.05967"));
	DEBUGF("atof test, should be: 913.132423, result: %.10f\n", atof("913.132423"));

	DEBUGF("float_to_string test, should be: 63216.231, result: %s\n", float_to_string(63216.231, convert_buffer));
	DEBUGF("float_to_string test, should be: 0.0, result: %s\n", float_to_string(0.0, convert_buffer));
	DEBUGF("float_to_string test, should be: 942308.2751, result: %s\n", float_to_string(942308.2751, convert_buffer));
	DEBUGF("float_to_string test, should be: 1.3, result: %s\n", float_to_string(1.3, convert_buffer));
	DEBUGF("float_to_string test, should be: 913.132, result: %s\n", float_to_string(913.132, convert_buffer));
	DEBUGF("float_to_string test, should be: 32913.05967, result: %s\n", float_to_string(32913.05967, convert_buffer));
	DEBUGF("float_to_string test, should be: 913.132423, result: %s\n", float_to_string(913.132423, convert_buffer));
}

static void save_cur_item()
{
	char convert_buffer[100];
	int chars_written = 0;

	chars_written = rb->snprintf(database_buffer, 100, "%ld@%d@%d@", 
								 cur_item.next_rep,
								 cur_item.interval,
								 cur_item.past_repetitions);

	rb->strcpy(database_buffer + chars_written, float_to_string(cur_item.easy_factor, convert_buffer));
	chars_written += rb->strlen(convert_buffer);
	database_buffer[chars_written++] = '@';

	rb->strcpy(database_buffer + chars_written, cur_item.question);
	chars_written += rb->strlen(cur_item.question);
	database_buffer[chars_written++] = '@';

	rb->strcpy(database_buffer + chars_written, cur_item.answer);

	DEBUGF("Going to save buffer: %s\n", database_buffer);

	rb->lseek(cur_item_file, 0, SEEK_SET);
	rb->write(cur_item_file, database_buffer, rb->strlen(database_buffer));
	rb->ftruncate(cur_item_file, rb->strlen(database_buffer));
}

enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
{
	int response_quality;
	char filename_buffer[500];
	struct tm* next_time;

    /* if you don't use the parameter, you can do like
       this to avoid the compiler warning about it */
    (void)parameter;

    /* if you are using a global api pointer, don't forget to copy it!
       otherwise you will get lovely "I04: IllInstr" errors... :-) */
    rb = api;
	
	DEBUGF("Current time: %ld\n", rb->mktime(rb->get_time()));

	debug_test_string_conversion();

    rb->lcd_set_backdrop(NULL);
    rb->lcd_set_background(LCD_RGBPACK(0,0,0));
    rb->lcd_clear_display();
    rb->lcd_update();

	set_seek_mode(SEEKING_EXPIRED);
	init_database();

	while(find_next_item()) {
		DEBUGF("Chosen item: \n");
		debug_print_current_item();

		backlight_force_on(rb);
		present_question();
		backlight_use_settings(rb);
		response_quality = query_response();

		DEBUGF("Score: %d\n", response_quality);

		if(should_quit)
			goto end_program;
			
		if(want_to_edit) {
			rb->close(cur_item_file);
			rb->strcpy(filename_buffer, MNEMO_TOEDIT_DIR);
			rb->strcat(filename_buffer, cur_item_file_name);
			rb->rename(cur_item_file_full_path, filename_buffer);
			continue;
		}

		if(response_quality < 3)
			cur_item.past_repetitions = 0; /* Start over in terms of calculating intervals */

		cur_item.past_repetitions++;
		cur_item.interval = calc_new_interval(&cur_item);
		DEBUGF("Interval returned: %d\n", cur_item.interval);
		cur_item.easy_factor = calc_new_easy_factor(cur_item.easy_factor, response_quality);
		
		next_time = rb->get_time();
		next_time->tm_hour = 0; /* Always expire at midnight */
		next_time->tm_mday += cur_item.interval;
		cur_item.next_rep = rb->mktime(next_time);

		save_cur_item();
		rb->close(cur_item_file);

		if(response_quality < 2) {
			some_forgotten = true;

			rb->strcpy(filename_buffer, MNEMO_FORGOT_DIR);
			rb->strcat(filename_buffer, cur_item_file_name);
			rb->rename(cur_item_file_full_path, filename_buffer);
		}
		else {
			move_item_to_future();
		}

		DEBUGF("New item stats: ");
		debug_print_current_item();
	}

end_program:

	rb->closedir(database_dir);

	if(usb_detected)
		return PLUGIN_USB_CONNECTED;
	else
		return PLUGIN_OK;
}
