#!/usr/bin/perl
#*****************************************************************************
#  Tangobulario (a program to practice your vocabulary of foreign words)
#
# Copyright (c) 2008-2009 Nancho Alvarez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# 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/>.

#******************************************************************************

$file="palabras.txt";
$repaso_extension=".review";
@sv=(); #secciones validas
$tanda=25;
$escribir_flag="yes";
$listarsecciones="no";
$exponente=1.5;
$reverse="no";
$repaso="no";
&parser;
if ($repaso eq "yes") {@sv=()} #in review-mode disable sections

if (!open (LEER, $file)) {print "Error de lectura: $file - $!\n"; &usage; exit 1}
#open(LEER, $file) || die "Error de lectura: $file\n$!";  
@lines = <LEER>;
close(LEER);

$j=0;
@preguntables=();
@s=(); #todas las secciones
if (@sv==0) {$flag=1} else {$flag=0}
$seccionactual="";
foreach(@lines){
	if (/^\[(.+)\]$/) {
		$seccionactual=$1;
		if ($1 =~ /=/) {print "Warning: section name '$seccionactual' contains the symbol '='\n";}
		push(@s, $seccionactual."\n");
		&activarflag;
	}
	elsif (/=/ and $flag){
		push(@preguntables, $j);
		&subdivide;
	}
	$j++;	
}
if ($listarsecciones eq "yes") {
	foreach(@s){print }
	exit 0;
}
if (@preguntables==0) {die "There are no words\n"}
if ($tanda>@preguntables) {$tanda=@preguntables}


@probabilidades=();
&asignarprobabilidades;

@elegidos=();
if ($repaso eq "no"){
	&elegir;
	foreach(@elegidos){ $veces[$_]++ } #increment only once
}
else {
	&leer_repaso;
	$tanda=@elegidos;
}

$answer="yes";
$preguntar_solo_falladas="no";
@falladas_primera_vez=();
$primera_vez="yes";
while ($answer eq "yes") { #bucle principal mas externo
	$ronda=1;
	if ($preguntar_solo_falladas eq "yes") {
		@lista=@falladas_primera_vez;
		$aciertos=$tanda-@falladas_primera_vez;
	}
	else {
		@lista=@elegidos;
		$aciertos=0
	}
	while($aciertos<$tanda){ #here starts a round
		@falladas=();
		$primera_pregunta="yes";
		while(@lista){ #here starts a question
			$cuantos=@lista;
			if ($primera_pregunta eq "yes" and $cuantos > 1){
				$cuantos--;
				# hack: the first question of each round 
				# should be different from the last failed one 
			}
			$i=int(rand $cuantos);
			$primera_pregunta="no";
			&asignar_idiomas;
			print "$idioma1: "; 
			chomp($respuesta=<>);
			if ($respuesta eq "=" and $escribir_flag eq "yes"){
				&corregir;
			}
			elsif ($respuesta eq "=="){
				$aciertos++;
				splice(@lista, $i, 1);
				print "---> $idioma2\n";
			}
			elsif ($respuesta eq "=exit"){
				&abortar;
			}
			elsif ($respuesta eq $idioma2){
				$aciertos++;
				splice(@lista, $i, 1);
			}
			else {
				print "***** WRONG ******: $idioma2\n";
				$fallos[$lista[$i]]++;
				push(@falladas, $lista[$i]);
				splice(@lista, $i, 1);
			}
		}
		my $f=@falladas;
		print "\n\n--- round: $ronda --- failed: $f ---\n\n\n\n";
		if ($primera_vez eq "yes"){
			@falladas_primera_vez=@falladas;
			$primera_vez="no"
		}
		$ronda++;
		@lista=@falladas;
	}
	$answer=&menu; 
}

#THE END


#####################################################################
sub menu{
	my $repetir_menu="yes";
	$preguntar_solo_falladas="no";
	while ($repetir_menu eq "yes"){
		print "Again (Y/n/x/s/f/h)?: ";
		$respuesta=<>;
		chomp ($respuesta);
		if ($respuesta =~ /^[hH]/) {
			print 	"y - again\n",
				"n - exit (saving statistics and review)\n",
				"x - exit without saving\n",
				"s - save statistics\n",
				"f - ask only failed questions\n",
				"h - this help\n";
			print "read-only mode: <n> and <q> are equivalent, and <s> has no effect\n" 
			unless ($escribir_flag eq "yes");
			print "\n";
		}
		if ($respuesta =~ /^[xX]/) {
			$answer=0; 
			$escribir_flag="no";
			$repetir_menu="no";
		}
		if ($respuesta =~ /^[nN]/) {
			$answer=0;
			$repetir_menu="no";
			if ($escribir_flag eq "yes"){
				&escribir;
				&escribir_repaso unless ($repaso eq "yes");
			}
		}
		if (($respuesta =~ /^[yY]/) || ($respuesta eq "")) {
			$repetir_menu="no";
		}
		if ($respuesta =~ /^[sS]/) {
			if ($escribir_flag eq "yes") {
				&escribir;
				print "statistics saved\n";
			}
			else {print "no effect\n"}
		}
		if ($respuesta =~ /^[fF]/) {
			if (@falladas_primera_vez==0){
				print "no failed answers in the first round\n"
			}
			else{
				$preguntar_solo_falladas="yes";
				$repetir_menu="no"
			}
		}
	}
	return $answer;
}
##################################################
sub abortar{
	#saving in the first round alters statistics, so it is disabled
	if ($primera_vez eq "no") {
		if ($escribir_flag eq "yes"){
			&escribir;
			&escribir_repaso unless ($repaso eq "yes");
		}
	}
	exit;
}
##################################################
sub asignar_idiomas{
	if ($reverse eq "no"){
			$idioma1=$espanol[$lista[$i]];
			$idioma2=$aleman[$lista[$i]];
		}
		else{
			$idioma2=$espanol[$lista[$i]];
			$idioma1=$aleman[$lista[$i]];
		}
}
##################################################
sub corregir{
	print "corregir \"$espanol[$lista[$i]]\": ";
	my $a=<>;
	chomp ($a);
	if ($a eq "") {$a = $espanol[$lista[$i]]}
	print "corregir \"$aleman[$lista[$i]]\": ";
	my $b=<>;
	chomp ($b);
	if ($b eq "") {$b = $aleman[$lista[$i]]}
	print "\"$a=$b\" correcto? (s/n): ";
	my $c=<>;
	chomp ($c);
	if ($c eq "s") {
		$espanol[$lista[$i]]=$a;
		$aleman[$lista[$i]]=$b;
		&escribir;
		print "corregido\!\n";
	}
}
##################################################
sub leer_repaso{
	my $file_repaso=$file . $repaso_extension;
	open(REPASO, $file_repaso) || die "Error de lectura: $file_repaso\n$!";  
	@elegidos = <REPASO>;
	close(REPASO);
	foreach(@elegidos){
		if ($veces[$_] !~ /^[1-9][0-9]*$/){
			die "Incompatibility between files $file and $file_repaso\n"
		}
	}
}
##################################################
sub elegir{
	srand;
	foreach(0..$tanda-1){
		my $t=$probabilidades[0];
		my $r=rand;
		my $i=0;
		while($t<$r){$t=$t+$probabilidades[$i+1]; $i++}
		push(@elegidos, splice(@preguntables, $i, 1));
		my $p=splice(@probabilidades, $i, 1);
		foreach(@probabilidades){
			$_=$_/(1-$p);  #corregimos las probabilidades
		}
	}
}
##################################################
sub asignarprobabilidades{
	my $maximo=1;
	my $numero_de_ceros=0;
	my $suma=0;
	foreach (@preguntables){
		if ($veces[$_]==0){
			push(@probabilidades, -1);
			$numero_de_ceros++;
		}
		else{
			my $aux=&peso($veces[$_],$fallos[$_]);
			push(@probabilidades, $aux);
			if ($maximo<$aux) {$maximo=$aux}
			$suma=$suma+$aux;
		}
	}
	$suma=$suma+$numero_de_ceros*$maximo;
	foreach(@probabilidades){
		$_=($_==-1)? $maximo/$suma: $_/$suma;
	}
}
##################################################
sub peso{
	# Feel free to write your own function.
	# @_[0] is the number of times a word has been selected,
	# @_[1] is the number of failures,
	# $exponente is a global variable than can be used as a parameter.
	# The return should be the relative probability of each word:
	# for example, a word with return value of 3 
	# is 6 times more likely to be chosen
	# than a word with return value 0.5
	((1+@_[1])/@_[0])**$exponente
	#other example: 2**(1+@_[1]/@_[0])-1
	
}
##################################################
sub activarflag{
	if (@sv==0) {return }
	foreach(@sv){
		if ($_ eq $seccionactual) {$flag=1; return}
	}
	$flag=0;
}
################################################
sub subdivide{
	my @four=split /[=\n]/;
	$espanol[$j]=$four[0];
	$aleman[$j]=$four[1];
	$veces[$j]  = ($four[2] eq "")? 0 : $four[2]; 
	$fallos[$j] = ($four[3] eq "")? 0 : $four[3];
}
#######################################################
sub escribir{
	foreach(@elegidos){
		$lines[$_]=$espanol[$_]."=".$aleman[$_]."=". $veces[$_]."=".$fallos[$_]."\n";
	}
	if (open(ESCRIBIR, ">$file")){ 
		print ESCRIBIR @lines;
		close(ESCRIBIR);
	}
	else {print "Error de escritura: ", $file, "\n"; }
}
#####################################################
sub escribir_repaso{
	my $file_repaso=$file . $repaso_extension;
	if (open(REPASO, ">$file_repaso")){ 
		foreach(@elegidos){
			print REPASO $_;
			print REPASO "\n";
		}
		close(REPASO);
	}
	else {print "Error de escritura: ", $file_repaso, "\n"; }
	
}
#####################################################
sub parser{
	my $fichero_flag="no";
	my $seccion_flag="no";
	my $exponente_flag="no";
	foreach(@ARGV){
		if ($_ =~ /^-([0-9]+$)/) {$tanda = $1}
		elsif ($_ eq "-h") {&ayuda }
		elsif ($_ eq "-ro") {$escribir_flag="no"}
		elsif ($_ eq "-l") {$listarsecciones="yes"}
		elsif ($_ eq "-r") {$reverse="yes"}
		elsif ($_ eq "-v") {$repaso="yes"}
		elsif ($_ eq "-s") {$seccion_flag="yes"}
		elsif ($_ eq "-e") {$exponente_flag="yes"}
		else {
			if ($seccion_flag eq "yes"){
				push (@sv, split /,/);
				$seccion_flag="no";
			}
			elsif ($exponente_flag eq "yes"){
				$exponente = $_;
				$exponente_flag = "no";
				die "Error: exponent must be a positive number\n" unless $exponente =~ /^\d*\.?\d+$/ ;
			}
			elsif ($fichero_flag eq "no"){
				$file = $_;
				$fichero_flag="yes"
			}
			else {&usage; exit 1}
		}
	}
	$#ARGV=-1;
}
#####################################################
sub ayuda{
	print "Tangobulario 0.7 Copyright (C) 2008-2009 Nancho Alvarez\n",
	"http://tomasluisdevictoria.org/nancho/tangobulario\n\n",
	"This program comes with ABSOLUTELY NO WARRANTY. ",
	"This is free software, and you are welcome to ",
	"redistribute it under certain conditions. ",
	"For details see <http://www.gnu.org/licenses/>.\n\n";
	print "Tangobulario is a small program to assist you in learning vocabulary of foreign languages. ",
	"It uses a database file which contains ",
	"words and their translation. ",
	"It also mantains a statistic of the number of times a ",
	"word has been asked and failed.\n\n";
	print "Each line of the database is a pair of words (and ",
	"optionally its associated statistics) separated by an '=' sign. ",
	"The file can contain also section names enclosed by square brackets.\n\n";
	&usage;
	print "\nOptions:\n",
	"-# (# is a natural number)\n",
	"\t the number of words to use. Default is 25.\n",
	"-s section1,section2,...\n",
	"\t comma-separated list of sections (no spaces)\n",
	"-e exponent (exponent is a small non-negative real number)\n",
	"\t it affects the use of statistics. Default value is 1.5\n",
	"-l",
	"\t show a list of all sections contained in the database\n",
	"-r",
	"\t reverse the direction of the questions\n",
	"-v",
	"\t review mode: it asks the same words as in the previous run\n",
	"-ro",
	"\t read only: do not save statistics and review information\n",
	"-h",
	"\t this help\n\n";
	print "Other commands (inside the program):\n",
	"=",
	"\t edition mode (disabled if -ro option is given)\n",
	"==",
	"\t accepts the answer as correct\n",
	"=exit",
	"\t exit the program saving statistics if applicable\n\n";
	exit 0;
}
#####################################################
sub usage{
	print "Usage: \n", "tangobulario [-#] [-h] [-l] [-ro] [-r] [-v] [-s section1,section2,...] [-e exponent] [database_file]\n";  
}


=head1 NAME

tangobulario - a script to train your vocabulary in foreign languages

=head1 README

tangobulario is a perl script to train your vocabulary in foreign languages.

The user must create a plain text datafile containing pairs of words separated by an '=' sign. In each run, tangobulario asks a random subset of these words (25 by default) in several rounds till all the words are answered correctly.

Also, tangobulario stores statistics of the number of wrong answers, and uses this information to ask difficult words more frequently.

You have documentation (only in Spanish, sorry) and example datafiles in
http://tomasluisdevictoria.org/nancho/tangobulario


=pod SCRIPT CATEGORIES

Educational

=cut


