#!/usr/bin/perl

use strict;
use warnings;
use FindBin qw( $Bin );
use lib "$Bin/../../lib";
use Data::Dumper;
use POSIX qw( strftime );
use Spreadsheet::ParseExcel::Simple;
use Encode qw( decode encode from_to );
use Encode::Detect;
use Encoding::FixLatin qw(fix_latin);

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

#####################################################################
my $ENCODING = 'iso-8859-1';

my $cfg = App::POS::Config->new( file => "$Bin/../../etc/config.ini" );
my $info = $cfg->load_fixed();

my $machine = undef;

if (exists $info->{STATION_NUMBER} && $info->{STATION_NUMBER} == 0) {
  $machine = App::POS::Machine::Server->new( config => $cfg );
}
else {
  die "This script can run on the server machine only...\n";
}

my @TABLES = (
  { table => 'groups', name => uc(&translate($machine, "FAMILIAS E SUBFAMILIAS")) },
  { table => 'products', name => uc(&translate($machine, "PRODUTOS")) },
  { table => 'clerks', name => uc(&translate($machine, "FUNCIONARIOS")) },
  { table => 'customers', name => uc(&translate($machine, "CLIENTES")) },
  { table => 'product_menus', name => uc(&translate($machine, "MENUS")) },
  { table => 'product_names', name => uc(&translate($machine, "TRADUCOES PRODUTOS")) },
);

my %TABLES = ();

foreach my $t (@TABLES) {
  my $name = $t->{table};
  my $desc = $t->{name};
  $TABLES{$desc} = $name;
}

my %FIELDS;
my %FIELDS_TMP = ();
read_translation_file($Bin."/../../etc/fields.txt", \%FIELDS_TMP);

foreach my $table (keys %FIELDS_TMP) {
  foreach my $field (@{ $FIELDS_TMP{$table} }) {
    my( $name, $desc ) = each %$field;
    next if $table eq 'products' && $name eq 'groups';
    push @{ $FIELDS{$table} }, $name;
  }
}
#####################################################################

my $xls_file = shift;
my $update_only = shift;
my $tables = shift;

my %tables_only = ();

if (defined $tables) {
  my @t = split /,/, $tables;
  map { $tables_only{$_} = 1 } @t;

  if (exists $tables_only{products}) {
    $tables_only{product_names} = 1;
    $tables_only{product_menus} = 1;
  }
}

my $db = $machine->database();
my $dbh = $db->dbh();

if (! defined $xls_file) {
  $db->do("UPDATE products SET name = replace(name, '''', '`'), button_text = replace(button_text, '''', '`')");
  $db->do("UPDATE groups SET name = replace(name, '''', '`')");
  $db->do("UPDATE clerks SET login = replace(login, '''', '`')");
  $db->do("UPDATE customers SET name = replace(name, '''', '`'), address = replace(address, '''', '`'), city = replace(city, '''', '`')");

  die "Usage: $0 <xls_file>\n";
}

if (! -e $xls_file) {
  die "File $xls_file doesn't exists...\n";
}

my $xls = Spreadsheet::ParseExcel::Simple->read($xls_file);
my %menu_products = ();
my %handy_codes = ();
my %products_with_stock;
my %customer_nifs = ();

foreach my $sheet ($xls->sheets) {
  my $obj = $sheet->sheet;
  my $sheet_name = $obj->{Name} || "";
  next unless $sheet_name;
  $sheet_name = uc($sheet_name);

  my $count = 0;

  #$sheet_name = uc($sheet_name);
  print "SHEET: $sheet_name\n";

  #next unless $sheet_name eq 'MENUS';

  if (! exists $TABLES{$sheet_name}) {
    print "Can't find table represented by sheet '$sheet_name'\n";
    next;
  }

  my $table = $TABLES{$sheet_name};

  if (scalar keys %tables_only && ! exists $tables_only{$table}) {
    print "Ignoring table...\n";
    next;
  }

  if (! exists $FIELDS{$table}) {
    print "Can't find fields for table $table\n";
    next;
  }

  # get existant ids
  my %ids = ();

  if ($table ne 'product_menus' && $table ne 'product_names') {
    eval {
      my $tmp = $db->select("SELECT * FROM $table");
      map { $ids{$_->{id}} = $_ } @$tmp;
    };
  }

  #if ($@) {
  #  print "Can't lookup ID's for table $table...\n";
  #}

  #next unless $table eq 'groups';
  print "TABLE: $table\n";

  if (! defined $update_only || $update_only eq '0') {
    if ($table eq 'product_menus' || $table eq 'product_names') {
      $db->do("DELETE FROM $table");
    }
    else {
      eval {
        $db->do("UPDATE $table SET deleted = 1");
      };
    }
  }

  my $sth_insert_product_groups = $dbh->prepare("INSERT INTO product_groups (product_id, group_id) VALUES (?, ?)") or die($dbh->errstr());
  my $sth_delete_product_groups = $dbh->prepare("DELETE FROM product_groups WHERE product_id = ?") or die($dbh->errstr());
  my %prepares = ();

  my $num_headers = 0;

  while ($sheet->has_data) {
    my @data_orig = $sheet->next_row;

    if ($count++ == 0) {
      $num_headers = scalar(@data_orig);
      next;
    }

    next unless scalar @data_orig > 1;

    my @data = ();
    my $fields = $FIELDS{$table};
    my $num_headers = scalar(@$fields);

    # this is here because products have an extra column wich is the groups
    if ($table eq 'products') {
      @data = @data_orig [ 0 .. scalar @$fields ];
    }
    else {
      @data = @data_orig [ 0 .. scalar @$fields - 1 ];
    }

=pod
    # excel has less columns than expected, add null values to the
    # missing ones
    if (scalar @data_orig < $num_headers) {
      my $count = scalar @data_orig;

      while($count < $num_headers) {
        push @data_orig, '';
        $count++;
      }

      @data = @data_orig;
    }
    # excel has more colums than expected, strip them down
    elsif (scalar @data_orig > scalar @$fields) {
      # this is here because products have an extra column wich is the groups
      if ($table eq 'products') {
        @data = @data_orig [ 0 .. scalar @$fields ];
      }
      else {
        @data = @data_orig [ 0 .. scalar @$fields - 1 ];
      }
    }
    else {
      @data = @data_orig;
    }
=cut

    my $groups_tmp = "";

    if ($table eq 'products') {
      my $handy_plu = $data[31];

      if (defined $handy_plu && $handy_plu ne '' && $handy_plu ne '--' && $handy_plu ne '0') {
        if (exists $handy_codes{ $handy_plu }) {
          print STDERR "HANDY PLU DUPLICATED - $handy_plu - IGNORING...\n";
          $data[31] = '0';
        }
        #else {
        #  if ($data[31] =~ /^\d+$/) {
        #    $handy_plu = $handy_plu*1;
        #    $data[31] = $handy_plu;
        #  }
        #}

        $handy_codes{ $handy_plu } = 1;
      }

      $groups_tmp = pop(@data);
      $groups_tmp ||= "";
    }

    # default vat will be -1 if not defined
    if ($table eq 'groups') {
      if (! defined $data[7] || $data[7] eq '') {
        $data[7] = -1;
      }
    }

    # menu importing is treated differently than all the rest
    if ($table eq 'product_menus') {
      next unless scalar @data;

      if ($data[1] !~ /^\d+$/) {
        $data[1] = 0;
      } else {
        $data[1]--;
      }

      $data[3] ||= 0;
      eval { $db->do("INSERT INTO product_menus VALUES (?, ?, ?, ?)", $data[0], $data[2], $data[1], $data[3]); };
      $menu_products{$data[0]} = 1;
      next;
    }

    # product names is treated differently than all the rest
    if ($table eq 'product_names') {
      next unless scalar @data;
      eval { $db->do("INSERT INTO product_names VALUES (?, ?, ?)", $data[0], $data[1], $data[2]); };
      next;
    }

    if (! $data[0]) {
      #print "[$table] Row $count has a null id, skipping...\n";
      next;
    }

    my @tmp = ();

    my $idx = 0;

    # decode all fields
    foreach my $d (@data) {
      if (defined $d && $d ne '' && $d ne '--') {
        # trim word
        $d =~ s/^\s+//;
        $d =~ s/\s+$//;
        push @tmp, &my_decode($d);
      } else {
        if ($table eq 'clerks' && ($idx == 27 || $idx == 28 || $idx == 29)) {
          push @tmp, "";
        }
        else {
          push @tmp, 0;
        }
      }

      $idx++;
    }

    @data = @tmp;

    my $is_new = exists $ids{$data[0]} ? 0 : 1;

    my $query = "";
    my @v = ();

    # stock handling
    if ($table eq 'products') {
      # use stocks
      $data[33] ||= 0;

      if ($data[33]) {
        $products_with_stock{$data[0]}->{is_new} = $is_new;

        if (! $is_new) {
          $products_with_stock{$data[0]}->{stock_old} = $ids{$data[0]}->{stock_current};
        }

        $products_with_stock{$data[0]}->{stock_new} = $data[35];
      }
    }
    elsif ($table eq 'customers') {
      # if birthdate has a '0' on it, clean the value
      if ($data[14] eq '0' || $data[14] eq '') {
        $data[14] = undef;
      }

      $data[3] ||= "";

      if ($data[3] && $data[3] ne '0' && $data[3] ne '--') {
        if (exists $customer_nifs{$data[3]}) {
          print STDERR "Duplicated NIF - $data[3]\n";
          next;
        }
        else {
          $customer_nifs{$data[3]} = 1;
        }
      }

      #if (! $data[3] || $data[3] eq '0' || $data[3] eq '--') {
      #  print STDERR "Ignoring customer with no NIF...\n";
      #  next;
      #}

    }

    my $id = "";

    # insert
    if ($is_new) {
      $query = "INSERT INTO $table (deleted, ";
      $query .= join(", ", @$fields) . ") VALUES (0, ";
      $query .= "?, " x scalar(@$fields);
      $query =~ s/, $/)/;

=pod
      # make sure customers start at 1000000
      if ($table eq 'customers') {
        if ($data[0] < 1000000) {
          $data[0] += 1000000;
        }
      }
=cut

      $id = $data[0];

      @v = @data;

      if (scalar @$fields != scalar @v) {
        push @v, 0;
      }
    }
    # update
    else {
      my $info = $ids{$data[0]};

      $query = "UPDATE $table SET deleted = 0, ";

      foreach my $f (@$fields) {
        next if $f eq 'id';
        $query .= $f." = ?, ";
      }

      $id = shift @data;

      $query =~ s/, $/ WHERE id = ?/;
      @v = @data;

      push @v, $id;

      if (scalar @$fields != scalar @v) {
        push @v, 0;
        my $v1 = $v[-1];
        my $v2 = $v[-2];
        $v[-1] = $v2;
        $v[-2] = $v1;
      }
    }

    # cache prepares
    if (! exists $prepares{$query}) {
      $prepares{$query} = $dbh->prepare($query);
    }

    #print STDERR "$query\n";
    #print STDERR Dumper(\@v);

    for (@v) {
      eval { $_ = decode("utf8", $_) };
    }

    eval { $prepares{$query}->execute(@v); };

    if ($@) {
      print "ERROR!!!!! - $@\n";
      print Dumper(\@v);
      next;
    }

    if ($table eq 'products' && $groups_tmp ne '' && $groups_tmp ne '0') {
      my @g = split /,/, $groups_tmp;

      $sth_delete_product_groups->execute($id);

      for (@g) {
        $sth_insert_product_groups->execute($id, $_);
      }
    }
  }
}

# keep sequences sane
foreach my $t (keys %TABLES) {
  my $table = $TABLES{$t};
  next if $table eq 'product_menus' || $table eq 'product_names';
  my $rec = $db->select("SELECT MAX(id) AS id FROM $table");
  my $next_id = $rec->[0]->{id};

  if (defined $next_id) {
    print "$table sequence value will be ".$rec->[0]->{id}."\n";
    $db->do("SELECT setval('".$table."_seq', ".$rec->[0]->{id}.")");
  }
}

if (scalar keys %menu_products) {
  foreach my $id (keys %menu_products) {
    $db->do("UPDATE products SET is_menu = 1 WHERE id = ?", $id);
  }
}

if (scalar keys %products_with_stock) {
=pod
          '1' => {
                   'stock_old' => '10',
                   'is_new' => 0,
                   'stock_new' => '6'
                 },
=cut
  my $sth_insert = $dbh->prepare("INSERT INTO stock_movements VALUES (?, ?, ?, ?, ?, ?)") or die($dbh->errstr());
  my $sth_update = $dbh->prepare("UPDATE products SET stock_current = ? WHERE id = ?") or die($dbh->errstr());

  foreach my $id (keys %products_with_stock) {
    my $i = $products_with_stock{$id};

    # new product
    if ($i->{is_new}) {
      my $nid = $db->next_id("stock_movements");
      $sth_insert->execute($nid, $id, &strftime("%Y-%m-%d", localtime), $i->{stock_new}, 0, 'EXCEL IMPORT');
    }
    else {
      if ($i->{stock_new} != $i->{stock_old}) {
        my $nid = $db->next_id("stock_movements");
        $sth_insert->execute($nid, $id, &strftime("%Y-%m-%d", localtime), ($i->{stock_new} - $i->{stock_old}), 0, 'EXCEL IMPORT');
        $sth_update->execute($i->{stock_new}, $id);
      }
    }
  }
}

$db->do("UPDATE products SET name = replace(name, '''', '`'), button_text = replace(button_text, '''', '`')");
$db->do("UPDATE groups SET name = replace(name, '''', '`')");
$db->do("UPDATE clerks SET login = replace(login, '''', '`')");
$db->do("UPDATE customers SET name = replace(name, '''', '`'), address = replace(address, '''', '`'), city = replace(city, '''', '`')");

if (-e "/opt/pos/common/bin/fix_encodings.pl") {
  print STDERR "Will fix encodings...\n";
  system("/opt/pos/common/bin/fix_encodings.pl");
}

sub my_decode2 {
  my $w = shift;

  my $orig1 = $w;

  eval {
    my $utf8 = &decode("Detect", $w);
    $utf8 = fix_latin($utf8);
    from_to( $utf8, "iso-8859-1", "utf8" );
    $w = $utf8;
  };

  if ($@) {
    $orig1 =~ s/\W//g;
    print "ENCODING ERROR: #$orig1#\n";
    $w = $orig1;
  }

  return $w;
}

sub my_decode {
  my $w = shift;
  my $bck = $w;

  my $str = "";

  eval {
    $str = encode("utf8", $w);
  };

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

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

