#!/usr/bin/perl -w

=head1 NAME

gtk-invoice - parse a QSF XML file and prepare a simple Gtk invoice

=head1 DATAFREEDOM

These scripts developed from the 'pilot-qof' package but now include
support for other packages and formats and will continue to be extended
along the lines of http://www.data-freedom.org/ - liberating user data 
from the application. Therefore, the datafreedom scripts use a 'df' prefix.

The scripts continue to be developed within the pilot-qof CVS until such
time as the scripts are sufficiently cohesive to form a new source package.

Please feel free to contribute any of your own scripts, under the provisions
of the GNU General Public Licence v3 or later, via the QOF-devel mailing list.
http://lists.sourceforge.net/lists/listinfo/qof-devel

=head1 VERSION

Version 0.0.1

=head1 SYNOPSIS

 gtk-invoice FILENAME
 gtk-invoice -h|--help|--version

=head1 DESCRIPTION

gtk-invoice parses a QSF XML file output by pilot-qof
and prepares a simple invoice which will be presented in a Gtk
window using a monospaced font and based on rates specified
in ~/.datafreedom/currency which will be created for you.

Specify '-' as the filename to parse STDIN.

e.g.
 pilot-qof -x data.xml --invoice-city -t 2006-11-09 | gtk-invoice -

gtk-invoice, like pilot-qof, is designed to be used in pipes like this. It is
intended to provide support for your own scripts where details like the date 
can be set as an option:

 #!/bin/bash
 DATE=$1
 
 if [ ! $DATE ]; then
	echo "$0: please specify the date of the invoice you want to view."
	exit
 fi
 pilot-qof -x data.xml --invoice-city -t $DATE | gtk-invoice -

gtk-invoice improves on the zenity implementation using dfxml-invoice
by supporting monospaced fonts. This allows table layouts to be clearer by 
putting the invoice item in the first column instead of the second, creating
a more 'standard' invoice format.

TODO: A two window approach which allows the date to be selected in a calendar widget.

gtk-invoice depends on gtk-perl and is not currently installed in the
system path because of the dependencies of gtk-perl. In due course, this and
other GUI scripts will form a new datafreedom-gnome package. For now, all scripts
with GUI dependencies are available in /usr/share.

TODO: Similar scripts to support HTML output with gtk-html-perl (libgtk2-html2-perl
is currently in Debian experimental).

=head1 OBJECTS

pilot_expenses is part of pilot-qof.
Can also be used with gpe-expenses - compatibility with the
default SQLite gpe-expenses backend is pending.

L<http://qof.sourceforge.net/>

L<http://pilot-qof.sourceforge.net/>

L<http://gpe-expenses.sourceforge.net/>

=head1 AUTHOR

Neil Williams, C<< <codehelp at debian.org> >>

=head1 BUGS

Please report bugs via the pilot-qof package, either
in the Debian BTS or via SourceForge trackers.

=head1 COPYRIGHT & LICENSE

  Copyright 2007 Neil Williams.

  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 3 of the License, or
  (at your option) any later version.

  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/>.

=cut

# need to tie Number::Format with the Palm Default Currency Table

require 5.004;
use strict;
use POSIX qw(locale_h);
use locale;
use Date::Format;
use XML::QOFQSF qw(QSFParse);
use Number::Format qw(:subs :vars);
use Config::Auto;
use File::HomeDir;
use Text::FormatTable;
use Math::BigInt;
use Data::Random qw(:all);
use utf8;
use Unicode::String qw(utf8 latin1 utf16be);
use Glib qw(FALSE TRUE);
use Gtk2 -init;

my $our_version = "0.0.1";
sub usageversion {
    print(STDERR <<END)
gtk-invoice version $our_version

Usage:
 gtk-invoice FILENAME
 gtk-invoice -h|--help|--version

Options:
 -h|--help:           print this usage message and exit
 --version:           print this usage message and exit

gtk-invoice parses a QSF XML file output by pilot-qof
and prepares a simple invoice based on rates specified
in ~/.datafreedom/currency which has been created for you.

Specify '-' as the filename to parse STDIN.

e.g.
pilot-qof -x data.xml --invoice-city -t 2006-11-09 | gtk-invoice -

END
        || die "$0: failed to write usage: $!\n";
exit 0;
}

my $stdin = "false";
while( @ARGV ) {
    $_= shift( @ARGV );
    if (/^-$/) {
 		$stdin = "true";
    	next;
    }
    last if m/^--$/;
    if (!/^-/) {
        unshift(@ARGV,$_);
        last;
    }
    if (/^(-h|--help|--version)$/) {
        &usageversion();
        exit( 0 );
    }
}

my $home = File::HomeDir->my_home;
my $datafreedom_dir = "$home/.datafreedom";
mkdir ($datafreedom_dir) if (! -d $datafreedom_dir);
my $cfile = $datafreedom_dir . "/currency";
if (! -f $cfile)
{
	open (CFG, ">$cfile");
	print CFG "\# config file for datafreedom-perl\n";
	print CFG "\# used by xml-invoice\n";
	print CFG "\# Omit the currency symbols when specifying the rate\n";
	print CFG "\# and use . as the decimal point (i.e. locale C).\n";
	print CFG "\# e.g. for a rate of \$15.45\n";
	print CFG "\# weekday_hourly_rate: 15.45\n";
	print CFG "weekday_hourly_rate:\n";
	print CFG "weekend_hourly_rate:\n";
	print CFG "mileage_rate:\n";
	close (CFG);
	die "Cannot create config file $cfile: $!\n" if (! -f $cfile);
}

my $file = "";
$file = "-" if ($stdin eq "true");
$file = $ARGV[0] if ($stdin eq "false");
&usageversion if (!$file);
die "Cannot find $file. Please specify an invoice XML file.\n" 
	if ((! -f $file) && ($file ne '-'));

my $config = Config::Auto::parse("$cfile", format => "colon");
my $rate1 = 0;
$rate1 += $config->{'weekday_hourly_rate'}
	if ($config->{'weekday_hourly_rate'} ne "");
my $rate2 = 0;
$rate2 += $config->{'weekend_hourly_rate'} 
	if ($config->{'weekend_hourly_rate'} ne "");
my $mrate = 0;
$mrate += $config->{'mileage_rate'} 
	if ($config->{'mileage_rate'} ne "");

if (($rate1 == 0) || ($rate2 == 0) || ($mrate == 0))
{
	print "weekday_hourly_rate=$rate1\tweekend_hourly_Rate=$rate2\tmileage_rate=$mrate\n";
	die "Please specify some rates to use in $cfile\n" 
}

my $lconv = POSIX::localeconv();

my %obj = QSFParse("$file");
my $expenses = $obj{'pilot_expenses'};
my $contacts = $obj{'pilot_address'};
my $appointments =  $obj{'pilot_datebook'};
my $exp_count = @$expenses;
my $contact_count = @$contacts;
my $event_count = @$appointments;

if (($exp_count == 0) && ($contact_count == 0) && ($event_count == 0))
{
	warn "Empty file: $file\n";
	exit(0);
}

my $template = "%A, %o %B %Y";
my $template2 = "%H:%M:%S %P";
my $template3 = "%H hrs, %M mins";
my $currency = "";
$currency = $lconv->{int_curr_symbol} if ($lconv->{int_curr_symbol});
$currency =~ s/ //;
my $symbol = "";
$symbol = $lconv->{currency_symbol} if ($lconv->{currency_symbol});
$symbol =~ s/ //g;
my $hours = 0;
my $charge = 0;
my $miles = 0;
my $materials = 0;
my $decimal_point = ".";
$decimal_point = $lconv->{decimal_point} if ($lconv->{decimal_point});
my $thousands_sep = ",";
$thousands_sep = $lconv->{thousands_sep} if ($lconv->{thousands_sep});

my $amount = new Number::Format(-thousands_sep   => $thousands_sep,
                              -decimal_point   => $decimal_point,
                              -int_curr_symbol => $symbol);

my $table = Text::FormatTable->new('50l 20l');
$table->head('Item', 'Charge');
$table->rule('-');
my $cell = "";
my $cell2 = "";

# process the addressbook details of the contact to be invoiced.
foreach my $c (@$contacts)
{
	$cell = "\nBranch: " . $c->category . ". " . $c->entryCompany . ", ";
	$cell .= $c->entryAddress;
	$table->row($cell, '');
}
# calculate how long the work took and how much to charge
foreach my $a (@$appointments)
{
	my $diff = ($a->end_time - $a->start_time) / 3600;
	$cell = "";
	$cell2 = "";
	$cell .= time2str($template3, ($a->end_time - $a->start_time - 3600));
	$hours += ($a->end_time - $a->start_time) / 3600;
	if (time2str("%w", $a->start_time) eq "6")
	{
		$charge += $diff * $rate2;
		$cell .= " @ " . $amount->format_price($rate2, 2, 2) . "/hr";
		$cell2 .= $amount->format_price(($diff * $rate2), 2, 2);
		$table->row($cell, $cell2);
	}
	else
	{
		$charge += $diff * $rate1;
		$cell .= " @ " . $amount->format_price($rate1, 2, 2) . "/hr";
		$cell2 .= $amount->format_price(($diff * $rate1), 2, 2);
		$table->row($cell, $cell2);
	}
}
$table->rule('');
# Add expenses and mileage claims.
foreach my $e (@$expenses)
{
	$cell2 = "";
	$cell = "Expenses: ";
	if ($e->type_of_expense eq "Mileage")
	{
		$miles += $e->expense_amount;
		$cell .= $e->expense_city . " " . time2str($template, $e->expense_date) . "\n";
		$table->row($cell, '');
		$cell = $e->expense_amount . " miles @ ";
		$cell .= $amount->format_price($mrate, 2) . "/mile = ";
		$cell2 .= $amount->format_price(($miles * $mrate), 2);
		$table->row($cell, $cell2);
	}
	else
	{
		$materials += $e->expense_amount;
		$cell .= $e->expense_city . " " . time2str($template, $e->expense_date) . "\n";
		$table->row($cell, '');
		warn ("Currency mismatch!" . $currency . ":" . $e->kvp_mnemonic) 
			if ($currency ne $e->kvp_mnemonic);
		$cell = $e->type_of_expense . ": ";
		$cell2 .= $amount->format_price($e->expense_amount, 2);
		$table->row($cell, $cell2);
	}
}
my $gt = $amount->format_price(($charge + ($miles * $mrate) + $materials), 2);
$table->rule('-');
$table->row('Grand Total', $gt);
$table->rule('-');
# unicode handling is quite messed up in gtk-perl.
# Write out a dummy file, read it back and delete it.
# It is a dirty hack but until someone provides a UTF-8 compliant patch . . . 
# (or until zenity can use a monospace font)
open (BUG, ">/tmp/gtkinvoice");
print BUG $table->render(20);
close (BUG);

my $window = Gtk2::Window->new;
$window->signal_connect (destroy => sub {Gtk2->main_quit});
$window->set_default_size (600,300);
my $frame = Gtk2::Frame->new;
$frame->set_shadow_type ('in');

my $hbox = Gtk2::HBox->new (FALSE, 0);
my $vscrollbar = Gtk2::VScrollbar->new;
my $hscrollbar = Gtk2::HScrollbar->new;
my $textview = Gtk2::TextView->new;

$hbox->pack_start ($textview,TRUE, TRUE, 0);
$frame->add ($hbox);
$window->add ($frame);

# use a monospaced font.
# (This is the part that zenity cannot yet do.)
my $fontdesc = Gtk2::Pango::FontDescription->from_string('monospace');
$textview->modify_font ($fontdesc);
$window->show_all;

my $buffer = $textview->get_buffer;
# revisit our dirty hack
open (TXT, "/tmp/gtkinvoice");
my @invoice=<TXT>;
close (TXT);

foreach my $line (@invoice)
{
	my $uline = utf8($line);
	# seems wrong to specify latin1 here.
	$buffer->insert($buffer->get_end_iter, $uline->latin1);
}

Gtk2->main;

# hide the evidence of the dirty hack.
# :-)
unlink "/tmp/gtkinvoice";

