#!/usr/bin/perl -s

use common::sense;
use Math::MatrixReal 2.11;
use Text::Ngram 'ngram_counts';
use File::Slurp;
use Sereal::Decoder qw.decode_sereal.;
use Math::GSL::Matrix qw/:all/;
use Math::GSL qw/:all/;

require "./tools/features.pl";

our (
		$v, # verbose
		$gsl, # matrix is in GSL format
	);

my $features = load_features("3-features.txt");
my $thetas   = $gsl ? load_gsl_matrices("3-Thetas.gsl") : deserialize_thetas("3-Thetas.ser");
my $classes  = load_classes("classes.txt");

while (@ARGV) {
	my $file = load_file(shift @ARGV);

	if ($v) {

		my %langs = infer_language($file);
		for my $k (sort { $langs{$b} <=> $langs{$a}} keys %langs) {
			say "$k\t$langs{$k}";
		}

	} else {
		say infer_language($file);
	}
	say;
}

sub load_gsl_matrices {
	my $gsl = shift;

	my (@matrices, $err);

	my $fh = gsl_fopen($gsl, "rb");

	my $tmp = gsl_matrix_alloc(1,1);
	$err = gsl_matrix_fread($fh, $tmp);
	die "Can't read matrix: $err" if $err;

	my $nr_layers = gsl_matrix_get($tmp, 0, 0);
	gsl_matrix_free($tmp);

	$tmp = gsl_matrix_alloc($nr_layers, 1);
	$err = gsl_matrix_fread($fh, $tmp);

	my @layers = map { gsl_matrix_get($tmp, $_, 0) } (0 .. $nr_layers-1);
	gsl_matrix_free($tmp);

	my $current = 0;
	for (1..$nr_layers-1) {
		my $matrix = gsl_matrix_alloc($layers[$current+1], $layers[$current]+1);
		$err = gsl_matrix_fread($fh, $matrix);

		die "Can't read matrix: $err" if $err;
		push @matrices, Math::GSL::Matrix->new($matrix);

		++$current;
	}

	gsl_fclose($fh);
	return \@matrices;
}

sub infer_language {
	my $file = shift;

	my $ngrams = compute_features($file);

	my $data = $gsl ?
			   Math::GSL::Matrix->new(scalar(@$features), 1) :
			   Math::MatrixReal->new(scalar(@$features), 1);

	my $i = 1;
	for my $feature (@$features) {
		if (exists($ngrams->{$feature})) {
			if ($gsl) {
				$data->set_elem($i-1, 0, $ngrams->{$feature});
			} else {
				$data->assign($i, 1, $ngrams->{$feature});
			}
		}
		$i++;
	}

	my $ans = forward_prop($data, $thetas);

	my ($max, $pos) = $gsl ? $ans->max() : $ans->maximum();

	if (wantarray) {
		my $prob_classes = {};
		for (keys %$classes) {
			if ($gsl) {
				$prob_classes->{$classes->{$_}} = $ans->get_elem($_-1, 0);					
			} else {
				$prob_classes->{$classes->{$_}} = $ans->element($_, 1);					
			}
		}
		return (%$prob_classes);
	} else {
		return $classes->{$pos};
	}
}

sub load_file {
	my $file = shift;
	my $str = "";
	open my $fh, "<:utf8", $file or die "Can not open file $file for reading: $!";
	while (<$fh>) {
		next if /^#/;
		$str .= $_;
	}
	close $fh;

	return $str;
}

sub load_classes {
	my $file = shift;
	my $classes = {};

	open my $fh, "<:utf8", $file or die "Can not open classes file $file: $!";
	while(<$fh>) {
		chomp;
		my ($id, $name) = split /\t/;
		$classes->{$id + 0} = $name;
	}
	close $fh;

	return $classes;
}

sub load_features {
	my $file = shift;
	my $features = [];

	open my $fh, "<:utf8", $file or die "Can not open features file $file: $!";
	while (<$fh>) {
		chomp;
		push @$features, $_;
	}
	close $fh;

	return $features;
}

sub compute_features {
	my $str = shift;
	my $conf = { n => 3 };

	return features($str, $conf->{n});
}

sub deserialize_thetas {
	my $file = shift;
	my $Thetas;

	local $/;
	undef $/;
	open R, "<:raw", $file or die "Can't open file $file: $!";
	my $str = <R>;
	close R;
	return decode_sereal($str);
}

sub sigmoid {
	my $matrix = shift;

	my $func = sub { 1 / ( 1 + exp( - shift)) };

	return $gsl ? $matrix->ieach($func) : $matrix->each($func);
}

sub forward_prop {
	my ($x, $Thetas) = @_;

	my $a = [];
	$a->[0] = $x;

	for my $i (1 .. scalar(@$Thetas)) {
		my ($z);
		if ($gsl) {
			my $m = Math::GSL::Matrix->new(1,1)->set_elem(0,0,1);
			$z = $Thetas->[$i-1] * $m->vconcat($a->[$i-1]);
		} else {
			my $m = Math::MatrixReal->new_from_cols( [[1]] );	
			$z = $Thetas->[$i-1] * (~($m . (~ $a->[$i-1])));
			
		}
		$a->[$i] = sigmoid($z);
	}

	return $a->[-1];
}

__END__


