package GCStats;

###################################################
#
#  Copyright 2005-2010 Christian Jodar
#
#  This file is part of GCstar.
#
#  GCstar 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 2 of the License, or
#  (at your option) any later version.
#
#  GCstar 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 GCstar; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
###################################################
use utf8;
use Gtk2;

use strict;

our $statisticsActivated;
BEGIN {
    $statisticsActivated = 1;
    # Trick for depencies checker. Don't remove these commented lines.
    #eval 'use GD';
    #eval 'use GD::Graph::bars';
    #eval 'use GD::Graph::pie';
    #eval 'use GD::Graph::area';
    #eval 'use GD::Text';
    #eval 'use Date::Calc';
    foreach my $module (qw/GD GD::Graph::bars GD::Graph::pie GD::Graph::area GD::Text Date::Calc/)
    {
        eval "use $module";
        if ($@)
        {
            $statisticsActivated = 0;
            last;
        }
    }
}

{
    package GCStatsImageGenerator;

    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self  = {parent => $parent};
        bless ($self, $class);

        GD::Text->font_path($ENV{GCS_SHARE_DIR}.'/fonts');

        return $self;
    }

    sub setData
    {
        my ($self, $items, $sort, $type, $useNumbers) = @_;
        my %stats;
        $self->{yMax} = 0;
        foreach my $item(@$items)
        {
            if (ref($item) eq 'ARRAY')
            {
                $stats{$_}++ foreach (@$item);
            }
            else
            {
                $stats{$item}++;
            }
        }
        my (@val1, @val2);
        my @sortedKeys;
        if ($sort)
        {
            @sortedKeys = sort {$stats{$a} <=> $stats{$b}} keys %stats;
        }
        else
        {
            # TODO This sort should be more complicated, depending on the type of field
            @sortedKeys = sort {$a cmp $b} keys %stats;
        }
        my $val;
        foreach my $key(@sortedKeys)
        {
            $val = $key;
            $val .= ' ('.$stats{$key}.')' if $useNumbers;
            push @val1, $val;
            my $stat = $stats{$key};
            push @val2, $stat;
            $self->{yMax} = $stat if $stat > $self->{yMax};
        }
        $self->{data} = [\@val1, \@val2];
    }

    sub generate
    {
        my ($self, %options) = @_;
        my $graph;
        
        # Plot the graph twice as large as desired, so we can resample it down later, and eliminate
        # the jagged lines caused by GD::Graph's lack of anti-aliasing
        my $scaledWidth = $options{width} * 2;
        my $scaledHeight = $options{height} * 2;   
        my $scaledFontSize = $options{fontSize} * 2;
        
        if ($options{type} eq 'bars')
        {
            $graph = GD::Graph::bars->new($scaledWidth, $scaledHeight);
            $graph->set(
                x_labels_vertical => 1,
            );
            $graph->set_x_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_x_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_legend_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_values_font('LiberationSans-Regular.ttf', $scaledFontSize);
        }
        elsif ($options{type} eq 'area')
        {
            $graph = GD::Graph::area->new($scaledWidth, $scaledHeight);
            $graph->set(
                x_labels_vertical => 1,
            );
            $graph->set_x_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_x_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_legend_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_values_font('LiberationSans-Regular.ttf', $scaledFontSize);
        }
        elsif ($options{type} eq 'history')
        {
            $graph = GD::Graph::area->new($scaledWidth, $scaledHeight);
            $graph->set_x_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_x_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_y_axis_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_legend_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_values_font('LiberationSans-Regular.ttf', $scaledFontSize);
            
            # Modify data to accumulate
            if ($options{accumulate})
            {
                my $prev = 0;
                foreach (@{$self->{data}->[1]})
                {
                    $_ += $prev;
                    $prev = $_;
                }
                $self->{yMax} = $prev;
            }
            # Transform dates into numbers of days
            # Reference is the 1st one as they are ordered
            # Or the 2nd one if there are items without value
            my @refDate;
            if ($self->{data}->[0]->[0])
            {
                @refDate = split m|/|, $self->{data}->[0]->[0];
            }
            else
            {
                @refDate = split m|/|, $self->{data}->[0]->[1];
                # We consider items without value as being the day before
                @refDate = Date::Calc::Add_Delta_Days(@refDate, -1);
                $self->{data}->[0]->[0] = sprintf('%d/%02d/%02d', @refDate);
            }
            @refDate = GCPreProcess::completePartialDate(@refDate);
            my $prev = -1;
            my @newDates;
            my $dateFormat = $self->{parent}->{options}->dateFormat;
            foreach my $date(@{$self->{data}->[0]})
            {
                push @newDates, GCUtils::timeToStr(GCPreProcess::restoreDate($date), $dateFormat);
                if (!$date)
                {
                    $prev = 0;
                    next;
                }
                my @cmpDate = split m|/|, $date;
                @cmpDate = GCPreProcess::completePartialDate(@cmpDate);
                my $diff = Date::Calc::Delta_Days(@refDate, @cmpDate);
                if ($diff > $prev + 1)
                {
                    my @filler;
                    $#filler = $diff - $prev - 2;
                    if (!$options{accumulate})
                    {
                        @filler = map {''} @filler;
                    }
                    splice (@newDates, $prev + 1, 0, @filler);
                    splice (@{$self->{data}->[1]}, $prev + 1, 0, @filler);
                }
                $prev = $diff;
            }
            $self->{data}->[0] = \@newDates;
            if (!$options{showAllDates})
            {
                for my $idx(0..$#newDates)
                {
                    my @date = Date::Calc::Add_Delta_Days(@refDate, $idx);
                    my $value;
                    if ($date[2] == 1)
                    {
                        my $dateStr = sprintf('%d/%02d/%02d', @date);
                        $value = GCUtils::timeToStr(GCPreProcess::restoreDate($dateStr), $dateFormat);
                    }
                    else
                    {
                        if ($self->{data}->[1]->[$idx])
                        {
                            $value = '';
                        }
                    }
                    $self->{data}->[0]->[$idx] = $value;
                }
            }
            $graph->set(
                #x_label_skip => 5,
                x_labels_vertical => 1,
                show_values => 1,
            );
        }
        else
        {
            $graph = GD::Graph::pie->new($scaledWidth, $scaledHeight);
            $graph->set_value_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set_label_font('LiberationSans-Regular.ttf', $scaledFontSize);
            $graph->set('3d' => ($options{type} eq '3dpie'));
        }
        $graph->set_title_font('LiberationSans-Regular.ttf', $scaledFontSize);
        $graph->set(
            title       => $options{title},
            show_values => $options{showValues},
            transparent => 0,
            bgclr       => '#ffffff',
            dclrs       => ['#ffdf33', '#1c86ee', '#cdad00', '#6c7b8b', '#ffb618'],
            cycle_clrs  => 1,
            t_margin    => 20,
            b_margin    => 20,
            l_margin    => 20,
            r_margin    => 20,
            text_space  => 20,
            y_max_value => $self->{yMax},
            y_number_format => "%d",
        );
        my $gd = $graph->plot($self->{data});
        
        # Now, resample the graph down to the desired size, effectively anti-aliasing the sharp edges
        my $aaImage = GD::Image->new($options{width},  $options{height}, 1);
        $aaImage->copyResampled($gd, 0, 0, 0, 0, $options{width}, $options{height}, $scaledWidth, $scaledHeight);

	return $aaImage->png;
    }
}

{
    package GCStatsDialog;
    use base "Gtk2::Dialog";
    
    use GCUtils;
    use GCDialogs;

    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new($parent->{lang}->{MenuStatistics},
                                       $parent,
                                       [qw/destroy-with-parent/],
                                       $parent->{lang}->{StatsGenerate} => 'apply',
                                       'gtk-save-as' => 'ok',
                                       'gtk-close' => 'close'
                                      );
        bless($self, $class);

        $self->{parent} = $parent;
        $self->{generator} = new GCStatsImageGenerator($parent);

        $self->{choices} = [
            {value => 'bars', displayed => $parent->{lang}->{StatsBars}},
            {value => 'pie', displayed => $parent->{lang}->{StatsPie}},
            {value => '3dpie', displayed => $parent->{lang}->{Stats3DPie}},
            {value => 'area', displayed => $parent->{lang}->{StatsArea}},
            {value => 'history', displayed => $parent->{lang}->{StatsHistory}},
        ];
        $self->{typeOption} = new GCMenuList($self->{choices}, 4);
        $self->{typeOption}->setValue('3dpie');
        my $typeLabel = new GCLabel($parent->{lang}->{StatsKindOfGraph});
        $self->{fieldLabel} = new GCLabel($parent->{lang}->{StatsFieldToUse});
        $self->{fieldOption} = new GCFieldSelector(0, undef, 1);
        $self->{dateLabel} = new GCLabel($parent->{lang}->{StatsFieldDate});
        $self->{dateOption} = new GCFieldSelector(0, undef, 1, 0, 0, 'date');
        $self->{sortByNumberOption} = new GCCheckBox($parent->{lang}->{StatsSortByNumber});
        $self->{sortByNumberOption}->setValue(0);
        $self->{accumulateOption} = new GCCheckBox($parent->{lang}->{StatsAccumulate});
        $self->{accumulateOption}->setValue(1);
        $self->{useNumberOption} = new GCCheckBox($parent->{lang}->{StatsDisplayNumber});
        $self->{useNumberOption}->setValue(0);
        
        $self->{groupDatesByChoices} = [
            {value => 'days', displayed => 'Days'},
            {value => 'months', displayed => 'Months'},
            {value => 'years', displayed => 'Years'},
        ];        
        $self->{groupDatesByOption} = new GCMenuList($self->{groupDatesByChoices});
        $self->{groupDatesByOption}->setValue('days');
        my $groupDatesByLabel = new GCLabel('Group dates by');

        my $widthLabel = new GCLabel($parent->{lang}->{StatsWidth});
        $self->{widthOption} = new GCCheckedText('0-9');
        $self->{widthOption}->setValue(600);
        my $heightLabel = new GCLabel($parent->{lang}->{StatsHeight});
        $self->{heightOption} = new GCCheckedText('0-9');
        $self->{heightOption}->setValue(600);
        my $fontLabel = new GCLabel($parent->{lang}->{StatsFontSize});
        $self->{fontOption} = new GCNumeric(12, 1, 100, 1);

        $self->{typeOption}->signal_connect('changed' => sub {
            $self->checkVisible;
        });

        my $table = new Gtk2::Table(3, 11, 0);
        $table->set_row_spacings($GCUtils::margin);
        $table->set_col_spacings($GCUtils::margin);
        $table->set_border_width($GCUtils::halfMargin);

        $table->attach($typeLabel, 0, 1, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($self->{typeOption}, 1, 2, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($widthLabel, 3, 4, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($self->{widthOption}, 4, 5, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($heightLabel, 6, 7, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($self->{heightOption}, 7, 8, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($fontLabel, 9, 10, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($self->{fontOption}, 10, 11, 0, 1, 'fill', 'fill', 0, 0);
        $table->attach($self->{fieldLabel}, 0, 1, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{fieldOption}, 1, 2, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{dateLabel}, 0, 1, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{dateOption}, 1, 2, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{sortByNumberOption}, 3, 5, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{accumulateOption}, 3, 5, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{useNumberOption}, 6, 8, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($groupDatesByLabel, 9, 10, 1, 2, 'fill', 'fill', 0, 0);
        $table->attach($self->{groupDatesByOption}, 10, 11, 1, 2, 'fill', 'fill', 0, 0);
 
        $self->{image} = Gtk2::Image->new;

        $self->vbox->pack_start($self->{image},1,1,0);
        $self->vbox->pack_start($table,0,0,0);

        ($self->action_area->get_children)[1]->set_sensitive(0);

        return $self;
    }

    sub checkVisible
    {
        my $self = shift;
        my $val = $self->{typeOption}->getValue;
        if ($val eq 'history')
        {
            $self->{fieldLabel}->hide;
            $self->{fieldOption}->hide;
            $self->{dateLabel}->show_all;
            $self->{dateOption}->show_all;
            $self->{sortByNumberOption}->hide;
            $self->{accumulateOption}->show_all;
        }
        else
        {
            $self->{fieldLabel}->show_all;
            $self->{fieldOption}->show_all;
            $self->{dateLabel}->hide;
            $self->{dateOption}->hide;
            $self->{sortByNumberOption}->show_all;
            $self->{accumulateOption}->hide;
        }
    }

    sub setData
    {
        my ($self, $model, $data, $title) = @_;
        $self->{model} = $model;
        $self->{data} = $data;
        $self->{title} = $title;
        $self->{fieldOption}->setModel($model);
        $self->{fieldOption}->setValue('genre');
        $self->{dateOption}->setModel($model);
        $self->{dateOption}->setValue('added');
    }

    sub show
    {
        my ($self) = @_;

        #$self->generatePicture;
        $self->SUPER::show();
        $self->show_all;
        $self->checkVisible;
        $self->set_position('center');
        my $response = 'cancel';
        while (!(($response eq 'close') || ($response eq 'delete-event')))
        {
            $response = $self->run;
            $self->generatePicture if $response eq 'apply';
            $self->save if $response eq 'ok';
        }
        $self->hide;
    }

    sub generatePicture
    {
        my $self = shift;

        my $graphType = $self->{typeOption}->getValue;
        my $sortField = ($graphType eq 'history')
                        ? $self->{dateOption}->getValue
                        : $self->{fieldOption}->getValue;
        my $sortByNumber = $self->{sortByNumberOption}->getValue;
        my $accumulate = $self->{accumulateOption}->getValue;
        my $useNumber = ($graphType =~ /pie/) && $self->{useNumberOption}->getValue;
        my $showValues = ($graphType !~ /pie/) && $self->{useNumberOption}->getValue;
        my $groupDatesBy = $self->{groupDatesByOption}->getValue;
        my $width = $self->{widthOption}->getValue;
        my $height = $self->{heightOption}->getValue;
        my $fontSize = $self->{fontOption}->getValue;

        my $type = $self->{parent}->{model}->{fieldsInfo}->{$sortField}->{type};
        my @valuesList;
        if ($type eq 'date')
        {
            @valuesList = map {GCPreProcess::cutReversedDate(
                                    GCPreProcess::reverseDate($_->{$sortField}),
                                    $self->{groupDatesByOption}->getValue)}
                              @{$self->{data}};
        }
        else
        {
            @valuesList = map {$self->{parent}->transformValue($_->{$sortField}, $sortField, 1)} @{$self->{data}};
        }
        $self->{generator}->setData(\@valuesList, $sortByNumber, $type, $useNumber);
        #$self->{generator}->setData($self->{data}, $sortField);
        my $png = $self->{generator}->generate(type         => $graphType,
                                               showValues   => $showValues,
                                               accumulate   => $accumulate,
                                               title        => $self->{title},
                                               width        => $width,
                                               height       => $height,
                                               fontSize     => $fontSize,
                                               showAllDates => 0);

        my $loader = Gtk2::Gdk::PixbufLoader->new;
        $loader->write($png);
        $loader->close;
        $self->{pixbuf} = $loader->get_pixbuf;
        $self->{image}->set_from_pixbuf($self->{pixbuf});
        ($self->action_area->get_children)[1]->set_sensitive(1);
    }

    sub save
    {
        my $self = shift;
        my $fileDialog = new GCFileChooserDialog($self->{parent}->{lang}->{StatsSave}, $self, 'save', 1);
        $fileDialog->set_pattern_filter(['PNG (.png)', '*.png']);
        $fileDialog->set_filename($self->{filename});
        my $response = $fileDialog->run;
        if ($response eq 'ok')
        {
            $self->{filename} = $fileDialog->get_filename;
            $self->{pixbuf}->save($self->{filename}, 'png');
        }        
        $fileDialog->destroy;
    }
}

1;
