#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use lib qw( /opt/pos/lib );
use FindBin qw( $Bin );
use Encode qw( encode decode );
use File::Path qw( make_path );

use App::POS::Utils qw( translate );
use App::POS::Config;
use App::POS::Machine::Server;

use GD::Graph::Data;

use GD::Graph::lines;
use GD::Graph::bars;
use GD::Graph::points;
use GD::Graph::linespoints;
use GD::Graph::area;
use GD::Graph::pie;

my $from = shift;
my $to = shift;

my $out_dir = "/tmp/graphs";
make_path($out_dir);

my $cfg = App::POS::Config->new( file => "/opt/pos/etc/config.ini" );
my $machine = App::POS::Machine::Server->new( config => $cfg );
my $db = $machine->database();

my $file;
my $data;
my $title;
my $arr;
my $out;

my $graph = GD::Graph::bars->new(800, 600);
$title = translate($machine, 'Vendas por produto');
$graph->set(
  title    => $title,
  x_label  => translate($machine, 'Produto'),
  y_label  => translate($machine, 'Vendas c/ IVA'),
  bar_spacing     => 20,
  show_values => 1,
  shadow_depth    => 4,
) or die $graph->error;
$arr = sales_by_product($db, $from, $to);
#print STDERR Dumper($arr);
$data = GD::Graph::Data->new($arr) or die GD::Graph::Data->error;
$graph->plot($arr) or die $graph->error;
$file = "$out_dir/$title.png";
open($out, '>', $file) or die "Cannot open '$file' for write: $!";
binmode $out;
print $out $graph->gd->png;
close $out;


$graph = GD::Graph::area->new(800, 600);
$title = translate($machine, 'Vendas por hora');
$graph->set(
  title    => $title,
  x_label  => translate($machine, 'Hora'),
  y_label  => translate($machine, 'Vendas c/ IVA'),
  show_values => 1,
  show_values => 1,
  shadow_depth    => 4,
) or die $graph->error;
$arr = sales_by_hour($db, $from, $to);
#print STDERR Dumper($arr);
$data = GD::Graph::Data->new($arr) or die GD::Graph::Data->error;
$graph->plot($arr) or die $graph->error;
$file = "$out_dir/$title.png";
open($out, '>', $file) or die "Cannot open '$file' for write: $!";
binmode $out;
print $out $graph->gd->png;
close $out;


$graph = GD::Graph::bars->new(800, 600);
$title = translate($machine, 'Mesas com maior rotacao');
$graph->set(
  title => $title,
  x_label  => translate($machine, 'Mesa'),
  y_label  => translate($machine, 'Atendimentos'),
  show_values => 1,
  show_values => 1,
  shadow_depth    => 4,
  bar_spacing     => 20,
) or die $graph->error;
$arr = transactins_by_table($db, $from, $to);
#print STDERR Dumper($arr);
$data = GD::Graph::Data->new($arr) or die GD::Graph::Data->error;
$graph->plot($arr) or die $graph->error;
$file = "$out_dir/$title.png";
open($out, '>', $file) or die "Cannot open '$file' for write: $!";
binmode $out;
print $out $graph->gd->png;
close $out;


$graph = GD::Graph::bars->new(800, 600);
$title = translate($machine, 'Vendas por funcionario');
$graph->set(
  title => $title,
  x_label  => translate($machine, 'Funcionario'),
  y_label  => translate($machine, 'Vendas c/ IVA'),
  show_values => 1,
  show_values => 1,
  shadow_depth    => 4,
  bar_spacing     => 20,
) or die $graph->error;
$arr = sales_by_clerk($db, $from, $to);
#print STDERR Dumper($arr);
$data = GD::Graph::Data->new($arr) or die GD::Graph::Data->error;
$graph->plot($arr) or die $graph->error;
$file = "$out_dir/$title.png";
open($out, '>', $file) or die "Cannot open '$file' for write: $!";
binmode $out;
print $out $graph->gd->png;
close $out;


$graph = GD::Graph::bars->new(800, 600);
$title = translate($machine, 'Vendas por cliente');
$graph->set(
  title => $title,
  x_label  => translate($machine, 'Cliente'),
  y_label  => translate($machine, 'Vendas c/ IVA'),
  show_values => 1,
  show_values => 1,
  shadow_depth    => 4,
  bar_spacing     => 20,
) or die $graph->error;
$arr = sales_by_customer($db, $from, $to);
#print STDERR Dumper($arr);
$data = GD::Graph::Data->new($arr) or die GD::Graph::Data->error;
$graph->plot($arr) or die $graph->error;
$file = "$out_dir/$title.png";
open($out, '>', $file) or die "Cannot open '$file' for write: $!";
binmode $out;
print $out $graph->gd->png;
close $out;


#
# Functions
#

sub transactins_by_table {
  my $db = shift;
  my $from = shift;
  my $to = shift;

  my @r;
  my $sql_aux;

  if (defined $from && defined $to) {
    $sql_aux = "sh.business_date >= '$from' AND sh.business_date <= '$to' AND "

  }
  else {
    $sql_aux = "sh.business_date >= DATE(now() - INTERVAL '6 month') AND "

  }

  my $recs = $db->select(qq{
    SELECT table_num AS table,
      COUNT(CASE WHEN doc_type = 2 THEN -1 ELSE 1 END) AS transactions
    FROM sales_headers sh
    WHERE
      $sql_aux
      (sh.doc_type = 1 OR sh.doc_type = 2 OR sh.doc_type = 5 OR sh.doc_type = 4 OR sh.doc_type = 90 OR sh.doc_type = 7)
    GROUP BY table_num
    ORDER BY COUNT(CASE WHEN doc_type = 2 THEN -1 ELSE 1 END) DESC
    LIMIT 5
  });

  if (scalar @$recs) {
    my @tables = ();
    my @transactions = ();

    foreach my $rec (@$recs) {
      push @tables, my_conv($rec->{table});
      push @transactions, sprintf("%.2f", $rec->{transactions});
    }

    return [ \@tables, \@transactions ];
  }

  return \@r;
}

sub sales_by_hour {
  my $db = shift;
  my $from = shift;
  my $to = shift;

  my @r;
  my $sql_aux;

  if (defined $from && defined $to) {
    $sql_aux = "sh.business_date >= '$from' AND sh.business_date <= '$to' AND "

  }
  else {
    $sql_aux = "sh.business_date >= DATE(now() - INTERVAL '6 month') AND "

  }

  my $recs = $db->select(qq{
    SELECT EXTRACT(hour FROM sh.timestamp) AS hour,
      SUM(CASE WHEN doc_type = 2 THEN value*-1 ELSE value END) AS sales
    FROM sales_headers sh
    WHERE
      $sql_aux
      (sh.doc_type = 1 OR sh.doc_type = 2 OR sh.doc_type = 5 OR sh.doc_type = 4 OR sh.doc_type = 90 OR sh.doc_type = 7)
    GROUP BY EXTRACT(hour FROM sh.timestamp)
    ORDER BY EXTRACT(hour FROM sh.timestamp)
  });

  if (scalar @$recs) {
    my %hours = map { $_->{hour} => $_->{sales} } @$recs;

    my @hours = ();
    my @sales = ();

    for (my $i = $recs->[0]->{hour}; $i <= $recs->[-1]->{hour}; $i++) {
      push @hours, $i;

      if (exists $hours{$i}) {
        push @sales, sprintf("%.2f", $hours{$i});
      }
      else {
        push @sales, 0;
      }
    }

    return [ \@hours, \@sales ];
  }

  return \@r;
}

sub sales_by_product {
  my $db = shift;
  my $from = shift;
  my $to = shift;

  my @r;
  my $sql_aux;

  if (defined $from && defined $to) {
    $sql_aux = "sd.business_date >= '$from' AND sd.business_date <= '$to' AND "

  }
  else {
    $sql_aux = "sd.business_date >= DATE(now() - INTERVAL '6 month') AND "
  }

  my $line_total_sql = "sd.quantity*(sd.price-(sd.discount * (1 + sd.vat/100)))";

  my $q = qq{
    SELECT p.name AS product,
      SUM(CASE WHEN doc_type = 2 THEN ($line_total_sql)*-1 ELSE $line_total_sql END) AS sales
    FROM sales_details sd
    LEFT JOIN sales_headers sh ON (sd.header = sh.id)
    LEFT JOIN products p ON (sd.product = p.id)
    WHERE
      $sql_aux
      sd.operation_type = 1 AND
      (
        sh.doc_type = 1 OR sh.doc_type = 2 OR sh.doc_type = 5 OR sh.doc_type = 4 OR
        sh.doc_type = 90 OR sh.doc_type = 7 OR sh.doc_type = 12 OR sh.doc_type = 13
      )
    GROUP BY p.name
    ORDER BY SUM(CASE WHEN doc_type = 2 THEN ($line_total_sql)*-1 ELSE $line_total_sql END) DESC LIMIT 5
  };

  my $recs = $db->select($q);

  if (scalar @$recs) {
    my @products = ();
    my @sales = ();

    foreach my $rec (@$recs) {
      push @products, my_conv($rec->{product});
      push @sales, sprintf("%.2f", $rec->{sales});
    }

    return [ \@products, \@sales ];
  }

  return \@r;
}

sub sales_by_customer {
  my $db = shift;
  my $from = shift;
  my $to = shift;

  my @r;
  my $sql_aux;

  if (defined $from && defined $to) {
    $sql_aux = "sh.business_date >= '$from' AND sh.business_date <= '$to' AND "
  }
  else {
    $sql_aux = "sh.business_date >= DATE(now() - INTERVAL '6 month') AND "
  }

  my $recs = $db->select(qq{
    SELECT sh.customer AS customer,
      SUM(CASE WHEN doc_type = 2 THEN value*-1 ELSE value END) AS sales
    FROM sales_headers sh
    WHERE
      $sql_aux
      (sh.doc_type = 1 OR sh.doc_type = 2 OR sh.doc_type = 5 OR sh.doc_type = 4 OR sh.doc_type = 90 OR sh.doc_type = 7) AND
      sh.customer <> '' AND sh.customer <> '0' AND sh.customer <> '--'
    GROUP BY sh.customer
    ORDER BY SUM(CASE WHEN doc_type = 2 THEN value*-1 ELSE value END) DESC LIMIT 5
  });

  if (scalar @$recs) {
    my @customers = ();
    my @sales = ();

    foreach my $rec (@$recs) {
      push @customers, my_conv($rec->{customer});
      push @sales, sprintf("%.2f", $rec->{sales});
    }

    return [ \@customers, \@sales ];
  }

  return \@r;
}

sub sales_by_clerk {
  my $db = shift;
  my $from = shift;
  my $to = shift;

  my @r;
  my $sql_aux;

  if (defined $from && defined $to) {
    $sql_aux = "sh.business_date >= '$from' AND sh.business_date <= '$to' AND "
  }
  else {
    $sql_aux = "sh.business_date >= DATE(now() - INTERVAL '6 month') AND "
  }

  my $recs = $db->select(qq{
    SELECT sh.clerk_close_name AS clerk,
      SUM(CASE WHEN doc_type = 2 THEN value*-1 ELSE value END) AS sales
    FROM sales_headers sh
    WHERE
      $sql_aux
      (sh.doc_type = 1 OR sh.doc_type = 2 OR sh.doc_type = 5 OR sh.doc_type = 4 OR sh.doc_type = 90 OR sh.doc_type = 7) AND
      sh.clerk_close_name <> ''
    GROUP BY sh.clerk_close_name
    ORDER BY SUM(CASE WHEN doc_type = 2 THEN value*-1 ELSE value END) DESC LIMIT 5
  });

  if (scalar @$recs) {
    my @clerks = ();
    my @sales = ();

    foreach my $rec (@$recs) {
      push @clerks, my_conv($rec->{clerk});
      push @sales, sprintf("%.2f", $rec->{sales});
    }

    return [ \@clerks, \@sales ];
  }

  return \@r;
}

sub my_conv {
  my $w = shift;
  return "" unless defined $w && $w ne '';

  my $bck = $w;

  eval { decode( 'utf8', $bck, Encode::FB_CROAK ) };

  if ($@) {
    $w =~ s/[^\w ]//g;
    return $w;
  }
  else {
    return $w;
  }
}

