#!/usr/bin/perl -T

use strict;
use warnings;
#use diagnostics;
use JSON::Parse 'read_json'; #          sudo cpan; cpan> install JSON::Parse
use FCGI; #                             sudo cpan; cpan> install FCGI
use Text::Levenshtein::XS 'distance'; # sudo cpan; cpan> install Text::Levenshtein::XS
# FCGI will strip out UTF-8 chars otherwise. 
use bytes;

our $VERSION = "1.9";

# 1.9
# Made it so that this script only accepts word|api key inputs in it's %cgi_vars.
# Added checking for word|key inputs

# 1.8
# whitespace as initial input causes warnings in first_char() and in various other spots - fixed

# 1.7
# Added increased 'spell-checking' if the use reallllllly bungles a word, up to 6.

# 1.6
# Better dictionary for spellchecker

# 1.5
# Spellchecker based on Text::Levenshtein::XS
#   I am assuming the user did not mess up the first char.
# Added matching for hypen -

my $input_dict;
my %words_list;
my %env;

my $socket = FCGI::OpenSocket( ":9090", 5 );
my $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%env, $socket );

populate_dict();

while ($request->Accept() >= 0) {
	print "Content-type: text/html\r\n\r\n";
	my %cgi_vars;

	if ($env{'CONTENT_LENGTH'}) {
		my $stdin = <STDIN>;
		chomp($stdin);
		if ($stdin =~ m/([\w\=\&\+\-\ ]+)/) {
			$stdin = $1;
		}
		else {
			$stdin = "invalid";
		}
		$stdin =~ tr/+/ /;
		chomp($stdin);
		$stdin = lc $stdin;
		my @pairs = split('&', $stdin);
		for my $pair (@pairs) {
			my ($name, $value) = split('=', $pair);
			if ($name =~ /^word$|^api$/) {
				$cgi_vars{$name} = $value;
			}
		}
	}

	$cgi_vars{word} =~ s/^\s*(.*)$/$1/ if ($cgi_vars{word});
	
	if ($cgi_vars{word} && exists $$input_dict{$cgi_vars{word}}) {
		my $string = $$input_dict{$cgi_vars{word}};
		$string =~ s/\. (\d+\.)/\n$1/g;
		print "$string\n";
		if ($string =~ m/^See ([\w]*)\.$/) {
			my $word = $1;
			$word = lc $word;
			print "\n$word:\n";
			print "$$input_dict{$word}\n";
		}
	} elsif ($cgi_vars{word}) {
		print "'$cgi_vars{word}' does not exist in this dictionary.\n";
		my $char = first_char($cgi_vars{word});
		my $spellchecker = 0;
		my $distance_limit = 2;

		while (! $spellchecker) {
			for my $word (@{$words_list{$char}}) {
				if (distance("$cgi_vars{word}","$word") <= $distance_limit) {
					print "$word ?\n";
					$spellchecker = 1;
				}
			}
			$distance_limit++;
			if ($distance_limit == 6) {
				last;
			}
		}
	}
	else {
		print "Usage: POST with \"word=the word\".\n";
	}

	ask_me_a_q() if (($cgi_vars{api} and $cgi_vars{api} eq 'no') || $env{CONTENT_LENGTH} + 0 == 0);
	
	$request->Finish();
}
FCGI::CloseSocket( $socket );

sub first_char {
	my ($word) = @_;
	$word =~ m/^([\w\-]).*$/;
	return $1;
}

sub populate_dict {
	my $jp = JSON::Parse->new();
	$input_dict = $jp->read ("/home/jake/code/perl/wotd/dict.json");
	for my $word (sort keys %$input_dict) {
		push(@{$words_list{first_char($word)}}, $word);
	}
}

sub ask_me_a_q {
	my $form = <<'EOF';
<meta charset="UTF-8">
<form method="POST" action="/dictionary">
	<label>Word</label>
	<input id="word" name="word" placeholder="truffle" type="text" required>
	<input type="hidden" id="api" name="api" value="no">
	<input type="submit" value="Lookup">
</form>
<p>API: curl -d "word=the word" https://jakesthoughts.xyz/dictionary</p>
<p><pre>You can put this in your .bashrc!
function lookup() {
	curl -d "word=$1" https://jakesthoughts.xyz/dictionary
}</pre>
If there is white space in your word make sure to add quotes! e.g. $ lookup "water lily"</p>
<p>Why does this exist? I created this for an upcoming blog post regarding FCGI vs CGI. I like to practice what I preach and preach what I practice, I think that this makes for a good policy for reducing idiotic posts. Not that it takes much to say FCGI is better than CGI because it objectively is. But to run this program each time someone wants to access it like with CGI? Starting Perl and loading the dictionary each time... for a single word? No. Waste of CPU cycles and generally inefficient; keep Perl loaded and the dictionary loaded so that it can just return the definition immediately.</p>
EOF
	print $form;
}
