package App::POS::PrinterQrCode;

use strict;
use warnings;
use Carp;
use Data::Dumper;
use File::Copy qw( move copy );
use GD::Barcode::QRcode;
use GD::Image;

use base qw( App::Class );

sub init {
  my $self = shift;
 $self->{_content} = "";

  $self->{machine} //= "";
  $self->{info} //= "";

  croak 'Missing \'machine\' parameter...' unless $self->{machine};
  croak 'Missing \'info\' parameter...' unless $self->{info};
}

# generate content for png image
sub generate_png {
  my $self = shift;
  my $save_to = shift;
  croak 'Missing \'save_to\' parameter...' unless defined $save_to;

  my $text = $self->_gen_text();

  #a) Taxa de Recuperação de Erro (ECC): “M”;
  #b) Tipo: Byte;
  #c) Pontos por módulo (Size): 2;
  #d) Versão: v=9 (valor mínimo);
  #e) Dimensões de imagem: mínimo 30x30 milímetros;
  #f) Margem de Segurança (Margin): 0,25 cm.

  my $qr = GD::Barcode::QRcode->new($text, {
    Ecc => 'M', Version => 12, ModuleSize => 8
  });

  open(F, "> $save_to") or croak "Error saving $save_to - $!";
  binmode(F);
  print F $qr->plot()->png;
  close(F);
}

sub generate_link {
  my $self = shift;
  my $text = shift || "";
  return unless $text;

  my $machine = $self->{machine};

  my $cfg = $machine->config()->load_fixed();
  $cfg->{INVOICE_QRCODES_AS_BMP} //= 0;

  my $file = "/tmp/$$.png";

  #a) Taxa de Recuperação de Erro (ECC): “M”;
  #b) Tipo: Byte;
  #c) Pontos por módulo (Size): 2;
  #d) Versão: v=9 (valor mínimo);
  #e) Dimensões de imagem: mínimo 30x30 milímetros;
  #f) Margem de Segurança (Margin): 0,25 cm.

  my $qr = GD::Barcode::QRcode->new($text, {
    Ecc => 'M', Version => 12, ModuleSize => 8
  });

  open(F, "> $file");
  binmode(F);
  print F $qr->plot()->png;
  close(F);

  my $img = GD::Image->newFromPng($file);
  unlink $file;

  my $width = 246;
  my $height = 246;

  my $image = GD::Image->new($width, $height);

  $image->copyResampled($img,
    0, 0,               # (destX, destY)
    0, 0,               # (srcX,  srxY )
    $width, $height,    # (destX, destY)
    $img->width, $img->height
  );

  if ($cfg->{INVOICE_QRCODES_AS_BMP}) {
    return $image->png();
  }
  else {
    $self->_process($image);
    return $self->{_content};
  }
}

# generate content for png image
sub generate_link_png {
  my $self = shift;
  my $link = shift;
  my $save_to = shift;
  croak 'Missing \'save_to\' parameter...' unless defined $save_to;

  #a) Taxa de Recuperação de Erro (ECC): “M”;
  #b) Tipo: Byte;
  #c) Pontos por módulo (Size): 2;
  #d) Versão: v=9 (valor mínimo);
  #e) Dimensões de imagem: mínimo 30x30 milímetros;
  #f) Margem de Segurança (Margin): 0,25 cm.

  my $qr = GD::Barcode::QRcode->new($link, {
    Ecc => 'M', Version => 12, ModuleSize => 8
  });

  open(F, "> $save_to") or croak "Error saving $save_to - $!";
  binmode(F);
  print F $qr->plot()->png;
  close(F);
}

# generate content for escpos printer
sub generate {
  my $self = shift;

  my $machine = $self->{machine};

  my $cfg = $machine->config()->load_fixed();
  $cfg->{INVOICE_QRCODES_AS_BMP} //= 0;

  my $text = $self->_gen_text();
  my $file = "/tmp/$$.png";

  #a) Taxa de Recuperação de Erro (ECC): “M”;
  #b) Tipo: Byte;
  #c) Pontos por módulo (Size): 2;
  #d) Versão: v=9 (valor mínimo);
  #e) Dimensões de imagem: mínimo 30x30 milímetros;
  #f) Margem de Segurança (Margin): 0,25 cm.

  my $qr = GD::Barcode::QRcode->new($text, {
    Ecc => 'M', Version => 12, ModuleSize => 8
  });

  open(F, "> $file");
  binmode(F);
  print F $qr->plot()->png;
  close(F);

  my $img = GD::Image->newFromPng($file);
  unlink $file;

  my $width = 246;
  my $height = 246;

  my $image = GD::Image->new($width, $height);

  $image->copyResampled($img,
    0, 0,               # (destX, destY)
    0, 0,               # (srcX,  srxY )
    $width, $height,    # (destX, destY)
    $img->width, $img->height
  ); 
 
  if ($cfg->{INVOICE_QRCODES_AS_BMP}) {
    return $image->png();
  }
  else {
    $self->_process($image);
    return $self->{_content};
  }
}

sub _gen_text {
  my $self = shift;

  my $m = $self->{machine};

  my $pt_cert = $m->pt_certification_info();
  my $cert_num = scalar(@$pt_cert) ? $pt_cert->[0] : '2351';

  my $c = $m->config->load_fixed();
  my $i = $self->{info};

  my $h = $i->{check}->{headers};
  my $ex = $i->{check}->{extra_data};

  $i->{vat_info} //= {};
  $i->{check}->{kept_in_source} //= 0;

  my $total_w_vat = 0;
  my $total_wo_vat = 0;
  my $total_vat = 0;

  #print STDERR Dumper($i->{vat_info});

  foreach my $rate (keys %{$i->{vat_info}}) {
    my $i = $i->{vat_info}->{$rate};
    $i->{w_vat} //= 0;
    $i->{wo_vat} //= 0;
    $i->{vat_v} //= 0;
    $total_w_vat += $i->{w_vat};
    $total_wo_vat += $i->{wo_vat};
    $total_vat += $i->{vat_v};
  }

  $h->{canceled} //= 0;

  my $nif;
  my $nif_country = "PT";

  if (! $h->{customer_id}) {
    $nif = '999999990' unless $h->{customer_id};
  }
  else {
    my @a = split /\-/, $h->{customer_nif};
    $nif = $h->{customer_nif};
    if (scalar @a > 1) {
      $nif_country = uc($a[0]);
      $nif = $a[1];
    }
  }

  # FS 20001/8
  my( $doc_type, $doc_serie, $doc_num ) = $h->{fo_doc_number} =~ /^(.*?)\s(.*?)\/(.*?)$/;

  # 2020-08-18 14:27:17
  my( $doc_date ) = $h->{date_closed} =~ /^(\d\d\d\d\-\d\d\-\d\d)/;
  $doc_date =~ s/\-//g;

  $ex->{atcud} ||= 0;

  my $str = "A:".substr($h->{owner_nif}, 0, 9)."*".
    "B:".substr($nif, 0, 30)."*".
    "C:".substr($nif_country, 0, 12)."*".
    "D:".substr($doc_type, 0, 2)."*".
    "E:".substr($h->{canceled} ? 'A' : 'N', 0, 1)."*".
    "F:".substr($doc_date, 0, 8)."*".
    "G:".substr($h->{fo_doc_number}, 0, 60)."*".
    "H:".substr($ex->{atcud}, 0, 70)."*";

  $c->{SAFT_FISCAL_REGION} //= "CONTINENTE";

  my %tax_idx = ();

  for (@{$c->{SAFT_TAX}}) {
    my @a = split /,/, $_;

    if (uc($a[1]) eq 'ISENTA') {
      $tax_idx{$a[0]*1} = 2;
    }
    elsif (uc($a[1]) eq 'REDUZIDA') {
      $tax_idx{$a[0]*1} = 3;
    }
    elsif (uc($a[1]) eq 'INTERMEDIA') {
      $tax_idx{$a[0]*1} = 5;
    }
    elsif (uc($a[1]) eq 'NORMAL') {
      $tax_idx{$a[0]*1} = 7;
    }
  }

  if (uc($c->{SAFT_FISCAL_REGION}) eq 'CONTINENTE') {
    $str .= "I1:".substr('PT', 0, 5)."*";
    foreach my $vat (sort { $a <=> $b } keys %{$i->{vat_info}}) {
      my $start = $tax_idx{$vat};
      if ($vat == 0) {
        $str .= "I".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
      } else {
        $str .= "I".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
        $str .= "I".($start+1).":"._to_positive($i->{vat_info}->{$vat}->{vat_v})."*";
      }
    }
  }
  elsif (uc($c->{SAFT_FISCAL_REGION}) eq 'MADEIRA') {
    $str .= "K1:".substr('PT-MA', 0, 5)."*";
    foreach my $vat (sort { $a <=> $b } keys %{$i->{vat_info}}) {
      my $start = $tax_idx{$vat};
      if ($vat == 0) {
        $str .= "K".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
      } else {
        $str .= "K".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
        $str .= "K".($start+1).":"._to_positive($i->{vat_info}->{$vat}->{vat_v})."*";
      }
    }
  }
  else {
    $str .= "J1:".substr('PT-AC', 0, 5)."*";
    foreach my $vat (sort { $a <=> $b } keys %{$i->{vat_info}}) {
      my $start = $tax_idx{$vat};
      if ($vat == 0) {
        $str .= "J".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
      } else {
        $str .= "J".$start.":"._to_positive($i->{vat_info}->{$vat}->{wo_vat})."*";
        $str .= "J".($start+1).":"._to_positive($i->{vat_info}->{$vat}->{vat_v})."*";
      }
    }
  }

  $str .= "N:".substr(sprintf("%.2f", _to_positive($total_vat)), 0, 16)."*".
    "O:".substr(sprintf("%.2f", _to_positive($total_w_vat)), 0, 16)."*";

  if ($i->{check}->{kept_in_source} > 0) {
    $str .= "P:".substr(sprintf("%.2f", _to_positive($i->{check}->{kept_in_source})), 0, 16)."*";
  }

  $str .= "Q:".substr($h->{hash_small}, 0, 4)."*".
    "R:".substr($cert_num, 0, 4);

  #$str = "A:123456789*B:999999990*C:PT*D:FS*E:N*F:20190812*G:FS CDVF/12345*H:CDF7T5HD-12345*I1:PT*I7:0.65*I8:0.15*N:0.15*O:0.80*Q:YhGV*R:9999*S:NU;0.80";
  #$str = "A:123456789*B:999999990*C:PT*D:FS*E:N*F:20190812*G:FS CDVF/12345*H:CDF7T5HD-12345*I1:PT*I7:0.65*I8:0.15*N:0.15*O:0.80*Q:YhGV*R:9999*S:NU;0.80";
  #print STDERR Dumper($i);
  #print STDERR Dumper($h);
  #$str = "A:500000000*B:123456789*C:PT*D:PF*E:N*F:20190123*G:PF G2019CB/145789*H:HB6FT7RV145789*I1:PT*I2:12345.34*I3:12532.65*I4:751.96*I5:52789.00*I6:6862.57*I7:32425.69*I8:7457.91*N:15072.44*O:125165.12*Q:r/fY*R:9999";
  #print STDERR "$str\n";
  #print STDERR Dumper($pt_cert);

  #print STDERR "#### $str\n";

  return $str;
}

sub _to_positive {
  my $scalar = shift;
  my $decimals = shift || 2;
  $scalar ||= 0;

  if ($scalar) {
    if ($scalar =~ /^\-/) {
      $scalar = $scalar*-1;
    }
    $scalar = sprintf("%.0".$decimals."f", $scalar);
  }

  return $scalar;
}


sub _process {
  my( $self, $img ) = @_; # gd image

  my $paddingLeft  = '';
  my $paddingRight = '';

  if ($img->width > 512) {
    carp 'Width is greater than 512 pixels and could be truncated at print time';
  }

  if ($img->height > 255) {
    confess 'Height is greater than 255 pixels';
  }

  my @padding = $self->_pad_image_size($img->width);

  for (1 .. $padding[0]) {
    $paddingLeft .= '0';
  }

  for (1 .. $padding[1]) {
    $paddingRight .= '0';
  }

  my $pixelLine = '';
  my $switch    = 0;
  my @imageSize = ( 0, 0 );

  for my $y ( 0 .. $img->height - 1 ) {
    $imageSize[1]++;
    $pixelLine .= $paddingLeft;
    $imageSize[0] += $padding[0];
    for my $x ( 0 .. $img->width - 1 ) {
      $imageSize[0]++;
      my $index         = $img->getPixel( $x, $y );
      my @rgb           = $img->rgb($index);
      my $imageColour   = $rgb[0] + $rgb[1] + $rgb[2];
      my $imagePattern  = "1X0";
      my $patternLength = length $imagePattern;
      $switch = ($switch - 1) * (-1);

      for my $x ( 1 .. $patternLength ) {
        if ( $imageColour <= ( 255 * 3 / $patternLength * $x ) ) {
          my $patternAtX = substr( $imagePattern, $x - 1, 1 );
          if ( $patternAtX eq 'X' ) {
              $pixelLine .= $switch;
          }
          else {
              $pixelLine .= $patternAtX;
          }
          last;
        }
        elsif (
          $imageColour > ( 255 * 3 / $patternLength * $patternLength )
          and $imageColour <= ( 255 * 3 )) {
          $pixelLine .= substr( $imagePattern, -1, 1 );
          last;
        }
      }
    }

    $pixelLine .= $paddingRight;
    $imageSize[0] += $padding[1];
  }

  $self->_process_print_image( $pixelLine, \@imageSize );
}

sub _pad_image_size {
  my ( $self, $width ) = @_;

  if ( $width % 32 == 0 ) {
    return ( 0, 0 );
  }
  else {
    my $border = 32 - ( $width % 32 );
    if ( $border % 2 == 0 ) {
      return ( $border / 2, $border / 2 );
    }
    else {
      return ( $border / 2 - .5, $border / 2 + .5 );
    }
  }
}

sub _process_print_image {
  my ( $self, $pixelLine, $imageSize ) = @_;

  $self->{_content} .= "\x1d" . "v\x30\x00";

  my $buffer = sprintf(
    "%02X%02X%02X%02X",
    (
        ( ( $imageSize->[0] / $imageSize->[1] ) / 8 ), 0, $imageSize->[1],
        0
    )
  );

  $self->{_content} .= pack( "H*", $buffer );

  $buffer = "";
  my $i     = 0;
  my $count = 0;
  while ( $i < length($pixelLine) ) {
    my $octalString = oct( "0b" . substr( $pixelLine, $i, 8 ) );
    $buffer .= sprintf( "%02X", $octalString );
    $i += 8;
    $count++;
    if ( $count % 4 == 0 ) {
      $self->{_content} .= pack( "H*", $buffer ),
      $buffer = "";
      $count  = 0;
    }
  }
}

1;

