#!/usr/bin/perl
# gcalc.pl
# a (mostly) standalone command line interface to Google's calculator feature.
#
# Matt Sparks
# http://f0rked.com
use strict;
use Term::ReadLine;
use Getopt::Long qw(:config bundling);
use LWP::UserAgent;
use HTTP::Status;
use File::Basename;

use constant VERSION => '1.0';

use constant {
    AGENT   => "gcalc/".VERSION,
    ERROR   => 1,
    INVALID => 2,
    FAILURE => 1,
    SUCCESS => 0,
};

my $ua = LWP::UserAgent->new(agent   => AGENT,
                             timeout => 10);

my $errno;

&startup;

# Perform startup checks and parsing of command line options.
sub startup {
    my($help,$interactive);
    GetOptions('help|h|?'      => \$help,
               'interactive|i' => \$interactive);
    shift @ARGV if $ARGV[0] eq $0;
    my $expression = join ' ',@ARGV;

    if ($help) {
        &print_help;
        exit(0);
    }
    elsif (!$interactive && $expression) {
        run_once($expression);
    }
    else {
        interactive($expression);
    }   
}

# Start interactive mode. This allows the user to enter expressions in a
# shell-like interface.
sub interactive {
    my $term = new Term::ReadLine 'gcalc.pl';
    $term->ornaments(0) if $term->Features->{ornaments};
    $term->using_history if $term->Features->{addhistory};
    my $prompt = ">> ";

    if ($_[0] ne "") {
        _docalc($_[0]);
        $term->addhistory($_[0]);
    }
    else {
        print "gcalc.pl ".VERSION.": Google Calculator\n";
        print "type 'help' for help\n";
    }

    while (1) {
        my $expr = $term->readline($prompt);
        next if $expr eq "";

        if ($expr eq "help")    { &print_help_i; next; }
        if ($expr eq "version") { &print_version; next; }
        if ($expr eq "history") { print_history($term); next; }
        if ($expr eq "exit" or
            $expr eq "quit" or 
            $expr eq "q")       { exit(SUCCESS); }

        # calculate and print
        _docalc($expr);
        $term->addhistory($expr) if !$term->Features->{autohistory};
    }
}

sub _docalc {
    my $result = calculate($_[0]);
    if ($result eq ERROR) {
        printf "error returned (%d: %s)\n",$errno,status_message($errno);
    }
    elsif ($result eq INVALID) {
        printf "Invalid expression: %s\n",$_[0];
    }
    else {
        printf "%s\n",$result;
    }
    return $result if $result eq ERROR or $result eq INVALID;
}

# Do one calculation and exit.
# args:
#   <expression>
sub run_once {
    my $result = _docalc($_[0]);
    exit FAILURE if ($result & (ERROR | INVALID));
    exit SUCCESS;
}

# Print the help
sub print_help {
    my $bn = basename $0;
    my $v  = VERSION;
    print <<EOF;
gcalc.pl, version $v
usage: $bn [options] [expression]
options:
  -h, --help        \t display this help
  -i, --interactive \t interactive mode (default if no expression is given)

examples:
  \$ $bn '2+2'
  2 + 2 = 4
  \$ $bn one meter in light years
  one meter = 1.05702341 x 10^-16 light years

more: http://www.google.com/help/features.html#calculator
EOF
}

# Print the help in interactive mode
sub print_help_i {
    print <<EOF;
You are in interactive mode. From here you can enter expressions at the prompt
and Google will be queried for the results. If you have readline support
(through the Term::ReadLine::Gnu Perl module or similar), you will have access
to a command history which you can use by pressing the up arrow key.

Commands supported in interactive mode:
  help  version  history  exit  quit  q
EOF
}

sub print_version {
    print "gcalc.pl, version ".VERSION."\n";
    print "Matt Sparks (http://f0rked.com)\n";
    my @date = 
        split / /,'$Date: 2007-01-14 15:00:17 -0600 (Sun, 14 Jan 2007) $';
    printf "%s %s\n",$date[1],$date[2];
}

sub print_history {
    my $term = $_[0];
    
    if (!$term->Features->{readHistory}) {
        print "You do not have history support. Install a Term::ReadLine::*".
            " package to acquire this feature.\n";
        return;
    }
    
    my @history = $term->GetHistory;
    printf "history (%d):\n", scalar @history;
    printf "  %s\n", $_ for(@history);
}

# Perform a calculation
# args:
#   <query>
# returns:
#   formatted text-only result or error code. In case of return == ERROR, $errno
#   is set with the HTTP status code.
sub calculate {
    my $query = $_[0];
    $query =~ s/^\s*(.+?)\s*$/$1/;
    $query =~ s/\+/%2B/g;
    $query =~ s/ /+/g;

    my $response = $ua->get("http://www.google.com/search?hl=en&ie=UTF-8&".
                            "oe=UTF-8&q=$query&btnG=Google+Search");

    if ($response->is_error) {
        $errno = $response->code;
        return ERROR;
    }
    elsif ($response->content !~ /calc_img\.gif/) {
        return INVALID;
    }
    else {
        $response->content =~ m|<td nowrap><font size=\+1><b>(.+?)</b></td>|;
        my $result = $1;
        $result =~ s|<sup>(.+?)</sup>|^$1|g;
        $result =~ s|&\#215;|x|g;
        $result =~ s|&times;|x|g;
        $result =~ s|<.+?>||g;
        $result =~ s|(\d) (\d)|$1,$2|g;
        return $result;
    }
}

# $Id: gcalc.pl 3 2007-01-14 21:00:17Z f0rked $
