#!/usr/bin/env perl

use strict;
use warnings;

use POSIX qw( strftime );
use Data::Dumper;
use GD::Simple;
use Text::Wrap qw(wrap $columns $huge);
use Getopt::Long;
use lib "/opt/pos/lib";
use Encode qw( decode encode );
use App::POS::Config;
use App::POS::Machine::Station;
use App::POS::Machine::Server;
use App::POS::Utils qw( translate );

my $cfg = config_info();

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

$info->{PRINT_LABEL_PRINT_BARCODE} ||= 0;
$info->{PRINT_LABEL_USE_EURO_SYMBOL} = $info->{PRINT_LABEL_USE_EURO_SYMBOL} eq '0' || $info->{PRINT_LABEL_USE_EURO_SYMBOL} eq '' ? 0 : 1;
$info->{PRINT_LABEL_WIDTH} ||= 40;

my $line_len = $info->{PRINT_LABEL_WIDTH};
my $print_barcode = $info->{PRINT_LABEL_PRINT_BARCODE};
my $use_euro = $info->{PRINT_LABEL_USE_EURO_SYMBOL};

my $machine = undef;

if (exists $info->{STATION_NUMBER} && $info->{STATION_NUMBER} > 0) {
  $machine = App::POS::Machine::Station->new( config => $cfg_o );  
}
else {
  $machine = App::POS::Machine::Server->new( config => $cfg_o );  
}

my $name     = "";
my $barcode  = "";
my $price    = 0;
my $vat      = 0;
my $biz_name = "";
#my $currency = "€";
my $cnt      = "";
my $copies   = 1;

GetOptions (
  "price=s"    => \$price,
  "vat=s"      => \$vat,
  "barcode=s"  => \$barcode,
  "biz_name=s" => \$biz_name,
  "name=s"     => \$name,
  #"currency=s" => \$currency,
  "copies=i"   => \$copies,
) or die("Error in command line arguments\n");

my @name = decode_text(uc($name), $line_len);

$barcode ||= "";

if ($barcode) {
  my @barcode = decode_text(uc($barcode), $line_len);
  $barcode = $barcode[0];
}

my @biz_name = decode_text(uc($biz_name), $line_len);

$cnt .= euro_init($use_euro);
$cnt .= justify_left();
$cnt .= size_normal();
$cnt .= color_normal();
$cnt .= line_spacing_small();

# upper line
# $cnt .= chr(27).chr(116).chr(3);
$cnt .= size_normal().chr(218) . chr(196) x $line_len . chr(191) . "\n";
#$cnt .= size_big2().chr(179).size_big1().$price.size_big2()."  ".chr(179)."\n";
#$cnt .= size_normal();

if (scalar @name) {
  foreach my $name (@name) {
    $cnt .= size_medium().chr(179).e($name).chr(179)."\n";
  }

  $cnt .= size_normal();
}

if ($barcode && ! $print_barcode) {
  $cnt .= chr(179).(" " x ($line_len)).chr(179)."\n";
  $cnt .= size_normal().chr(179).$barcode.chr(179)."\n";
}

if ($vat) {
  $vat =~ s/\.00$//;
}

if ($price) {
  my $vat_str = translate($machine, "IVA") . ": " . $vat . "%";
  my $spaces_len = ($line_len - (length($price) * 4)) - length($vat_str);

  my $spaces = " " x ($spaces_len-4*$use_euro);

  #open(F, ">> /tmp/d.txt");
  #print F "### $spaces - $spaces_len #$vat_str#\n";
  #close(F);

  my $str = size_medium().chr(179).size_normal().$vat_str.$spaces.size_big().euro_char($use_euro).$price.size_medium().chr(179)."\n";
  $cnt .= $str;
}

if ($vat) {
  $vat =~ s/\.00$//;
  #my $spaces_len = $line_len - (length($price) * 4);
  #my $spaces = " " x ($spaces_len-4*$use_euro);
  #my $str = size_medium().chr(179).size_normal().$spaces.size_big().euro_char($use_euro).$price.size_medium().chr(179)."\n";
  #$cnt .= $str;
}

if (scalar @biz_name) {
  $cnt .= size_normal();
  $cnt .= chr(179).(" " x ($line_len)).chr(179)."\n";
  $cnt .= chr(222).color_invert().$biz_name[0].chr(222)."\n";
}

$cnt .= color_normal().size_normal();
$cnt .= justify_left();

if ($print_barcode && $barcode) {
  $barcode =~ s/\s//g;
  my $barcode_method = "";
  my $barcode_method_small = "";
  my $barcode_method_big = "";

  if (length($barcode) == 12) {
    $barcode_method = 'barcode_ean13';
    my $cd = calcEAN13CD($barcode);
    $barcode = $barcode . $cd;
  }
  elsif (length($barcode) == 13) {
    $barcode_method = 'barcode_ean13';
  }
  else {
    $barcode_method = 'barcode_39';
  }

  $cnt .= justify_center();
  my $res = "";

  my $call = "\$res = ".$barcode_method."(\$barcode)";
  eval "$call";

  $cnt .= "\n";
  $cnt .= $res;
  $cnt .= $barcode;
}

$cnt .= "\n\n\n\n";
$cnt .= paper_cut();

if (exists $cfg->{PRINTER_IP}) {
  my( $printer_ip, $printer_port ) = _network_printer_decode($cfg->{PRINTER_IP});
  my $sock = _get_socket($printer_ip, $printer_port);

  if (defined $sock) {
    print $sock $cnt;
    close($sock);
  }
}
elsif (exists $cfg->{PRINTER_DEVICE}) {
  for (my $i=0; $i<$copies; $i++) {
    open(F, "> ".$cfg->{PRINTER_DEVICE});
    print F $cnt;
    close(F);
  }
}

#
# FUNCTIONS
#

sub chars {
  my $scalar = shift;
  my $num = shift;
  return $scalar unless defined $num;

  my $decoded = decode("utf8", $scalar);

  if (length($decoded) > $num) {
    $decoded = substr($decoded, 0, $num);
  }
  elsif (length($decoded) < $num) {
    my $spaces = $num - length($decoded);
    $decoded .= " " x $spaces;
  }

  $decoded = encode("utf8", $decoded);

  return $decoded;
}

sub e {
  my $str = shift;
  return encode("CP860", decode("utf8", $str));
}

sub euro_init {
  my $use_euro = shift;
  return "" unless $use_euro;

  my $str = "";
  $str .= chr(27) . "&" . chr(3) . "AA";
  $str .= chr(16);

  $str .= chr(0) . chr(0) . chr(0);
  $str .= chr(0) . chr(102) . chr(0);
  $str .= chr(0) . chr(102) . chr(0);
  $str .= chr(1) . chr(255) . chr(128);
  $str .= chr(15) . chr(255) . chr(240);
  $str .= chr(30) . chr(102) . chr(248);
  $str .= chr(48) . chr(102) . chr(12);
  $str .= chr(48) . chr(102) . chr(12);
  $str .= chr(48) . chr(102) . chr(12);
  $str .= chr(48) . chr(102) . chr(12);
  $str .= chr(24) . chr(0) . chr(24);
  $str .= chr(6) . chr(0) . chr(48);
  $str .= chr(0) . chr(0) . chr(0);
  $str .= chr(0x1D) . chr(0x21) . "1\n" . 
  return $str;
}

sub euro_char {
  my $use_euro = shift;
  return "" unless $use_euro;

  my $str = chr(27) . "%" . chr(1) . "A" . chr(27) . "%" . chr(0);
  return $str;
}

sub calcEAN13CD {
  my( $sTxt ) =@_;
  my( $i, $iSum);
  my @aWeight = (1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3);
  $iSum = 0;
  for( $i = 0; $i < 12; $i++ ){
      $iSum += substr($sTxt, $i, 1)  * $aWeight[$i];
  }
  $iSum %= 10;
  $iSum = ($iSum == 0)? 0: (10 - $iSum);
  return "$iSum";
}

sub decode_text {
  my $text = shift;
  my $spaces = shift;
  my $trunc_size = shift;

  #my $tmp = chars($text, $spaces);
  #print STDERR "### #$tmp#\n";

  if (defined $trunc_size) {
    $text = substr($text, 0, $trunc_size);
  }

  my $tmp = wrap_string($text, $spaces);
  my @lines = split /\n/, $tmp;

  for (@lines) {
    $_ = chars($_, $spaces);
    #$_ = sprintf("%-".$spaces."s", $_);
  }

  return wantarray? @lines : \@lines;

  for (@lines) {
    my $line = $_;

    if (defined $spaces && length($line) < $spaces) {
      my $dif = $spaces - length($line);
      my $each = int($dif/2);
      $_ = (" " x $each) . $line . (" " x $each);
    }
  }

  return wantarray? @lines : \@lines;
}

sub wrap_string {
  my $name = shift;
  my $cols = shift;

  $columns = $cols;
  return wrap('', '', ($name));
}

sub size_normal {
  return
    chr(0x1B).
    chr(0x21).
    chr(0x00);
}

sub line_spacing_normal {
  return chr(0x1B)."2";
}

sub line_spacing_small {
  return chr(0x1B)."3".chr(25);
}

sub line_spacing_super_small {
  return chr(0x1B)."3".chr(5);
}

sub paper_cut {
  return chr(0x1B).chr(0x64).chr(0x06).chr(0x1B).chr(0x69);
}

sub color_invert {
  return chr(0x1D)."B".chr(0x01);
}

sub color_normal {
  return chr(0x1D)."B".chr(0x00);
}

sub size_big {
  return chr(0x1D).chr(0x21)."1";
}

sub size_medium {
  return chr(0x1D).chr(0x21).chr(1);
}

sub size_small {
  return chr(0x1B).chr(0x21).chr(0x00);
}

sub size_double {
  return
    chr(0x1B).
    chr(0x21).
    chr(0x20);
}

sub justify_center {
  return chr(0x1B).chr(0x61).1;
}

sub justify_left {
  return chr(0x1B).chr(0x61).0;
}

sub justify_right {
  return chr(0x1B).chr(0x61).2
}

sub barcode_ean13 {
  my $barcode = shift;
  return
    chr(29).chr(104).chr(40).chr(0).
    chr(29).chr(107).chr(2)."$barcode".chr(0);
}

sub barcode_39 {
  my $barcode = shift;
  return
    chr(29).chr(104).chr(40).chr(0).
    chr(29).chr(107).chr(4)."$barcode".chr(0);
}

sub config_info {
  my $CFG = {};
  my $area_idx = 0;
  my $prn_started = 0;
  my $prn_idx = -1;
  my $station_num = 0;
  my $server_ip = "";
  my %printers;

  open(F, "< /opt/pos/etc/config.ini");

  while(<F>) {
    chop;

    if (/NETWORK_SERVER_IP=(.*)/) {
      $server_ip = $1;
      #$CFG->{SERVER_IP} = $1;
    } elsif (/STATION_NUMBER=(\d+)/) {
      $station_num = $1;
      #$CFG->{STATION_NUMBER} = $1;
    } elsif (/DOC_TYPE_SERIE=(.*)/) {
      $CFG->{SERIE} = $1;
      my $year = &strftime("%Y", localtime);
      $CFG->{SERIE} =~ s/\@YEAR\@/$year/g;
    } elsif (/AREA=(.*)/) {
      my @t = split /,/, $1;
      # push @$AREAS_INFO, \@t;
    } elsif (/<Printers>/) {
      $prn_started = 1;
      next;
    }

    if ($prn_started) {
      if (/<Device>/) {
        $prn_idx++;
      } elsif (/IP\s+(.*?)$/) {
        $printers{$prn_idx}->{ip} = $1;
      } elsif (/Device\s+(.*?)$/) {
        $printers{$prn_idx}->{device} = $1;
      } elsif (/Main\s+1/) {
        $printers{$prn_idx}->{main} = 1;
      } elsif (/Inactive\s+1/) {
        $printers{$prn_idx}->{inactive} = 1;
      }
    }

    #last if exists $CFG->{STATION_NUMBER} && exists $CFG->{SERVER_IP};
  }

  my $prn = undef;
  my $prn_device = undef;
  my $prn_ip = undef;

  foreach my $k (keys %printers) {
    $printers{$k}->{inactive} //= 0;
    $printers{$k}->{main} //= 0;
    $printers{$k}->{ip} //= "";

    if (! $printers{$k}->{inactive} && $printers{$k}->{main}) {
      $prn = $k;
      $prn_device = $printers{$k}->{device} if $printers{$k}->{device};
      $prn_ip = $printers{$k}->{ip} if $printers{$k}->{ip};
    }
  }

  close(F);

  if (defined $prn) {
    $CFG->{PRINTER} = $prn;
  }

  if (defined $prn_device) {
    $CFG->{PRINTER_DEVICE} = $prn_device;
  }

  if (defined $prn_ip) {
    $CFG->{PRINTER_IP} = $prn_ip;
  }

  $CFG->{IS_SERVER} = $station_num == 0 ? 1 : 0;
  $CFG->{SERVER_IP} = $CFG->{IS_SERVER} ? '127.0.0.1' : $server_ip;

  return $CFG;
}

sub _network_printer_decode {
  my $ip = shift;

  if ($ip =~ /:/) {
    my @a = split /:/, $ip;

    my $ip2 = trim_string($a[0]);
    my $port = 9100;

    if (scalar @a > 1) {
      $port = trim_string($a[1]);
    }

    return ( $ip2, $port );
  }
  else {
    return ( $ip, 9100 );
  }
}

sub _get_socket {
  my( $printer_ip, $printer_port ) = @_;

  my $sock = IO::Socket::INET->new(
    PeerAddr => $printer_ip,
    PeerPort => $printer_port,
    Proto => 'tcp',
    Timeout => 2,
  );

  if (defined $sock && $sock) {
    return $sock;
  }

  return undef;
}

