package NGCP::Panel::Utils::Callflow;
use strict;
use warnings;

use NGCP::Panel::Utils::GD::Simple;

sub generate_pcap {
    my $packets = shift;

    my $pcap = pack("LSSlLLL",
        0xa1b2c3d4,     # magic number
        2, 4,           # major/minor version number
        0, 0,           # gmt offset and timestamp accuracy
        0xffff,         # snap length
        1,		# data link type (http://www.tcpdump.org/linktypes.html)
        );

    foreach my $pkg(@{$packets}) {
        my($ts_sec, $ts_usec) = $pkg->timestamp =~ /^(\d+)\.(\d+)$/;
        my $len = length($pkg->header) + length($pkg->payload) + length($pkg->trailer);

        $pcap .= pack("LLLLa*a*a*",
                $ts_sec, $ts_usec,      # timestamp
                $len, $len,             # bytes on-wire/off-wire
                $pkg->header,
                $pkg->payload,
                $pkg->trailer,
                );
    }
    return $pcap;
}

sub draw_line {
  my ($c, $from_x, $from_y, $to_x, $to_y, $width, $color) = @_;
  $c->fgcolor($color);
  $c->moveTo($from_x, $from_y);
  $c->penSize($width, $width);
  $c->lineTo($to_x, $to_y);
}

sub draw_arrow {
  my ($c, $from_x, $from_y, $to_x, $to_y, $width, $color) = @_;
  $c->fgcolor($color);
  $c->moveTo($from_x, $from_y);
  $c->penSize($width, $width);
  $c->lineTo($to_x, $to_y);
  my $poly = GD::Polygon->new;
  $poly->addPt($to_x, $to_y);
  my $dir = ($to_x > $from_x) ? -1 : 1;
  $poly->addPt($to_x + 4*$width*$dir, $to_y - 2*$width-(($width%2)?0:1));
  $poly->addPt($to_x + 4*$width*$dir, $to_y + 2*$width);
  my $oldbgcolor = $c->bgcolor();
  $c->bgcolor($color);
  $c->penSize(1,1);
  $c->polygon($poly);
  $c->bgcolor($oldbgcolor);
}

sub draw_text {
  my ($c, $x, $y, $ftype, $fsize, $fcolor, $txt) = @_;
  $c->font($ftype);
  $c->fontsize($fsize);
  $c->fgcolor($fcolor);
  $c->moveTo($x, $y);
  $c->string($txt);
  my @b = $c->stringBounds($txt);
  my %bounds = ('x', $x, 'y', $y, 'dx', $b[0], 'dy', $b[1]);
  return %bounds;
}

sub process_callmap {
    my $c = shift;
    my $packets = shift;
    my $r_png = shift;
    my $r_info = shift;
    my $i = 0;

    my %int_uas = (
      $c->config->{callflow}->{lb_int}, 'lb',
      $c->config->{callflow}->{lb_ext}, 'lb',
      $c->config->{callflow}->{proxy},  'proxy',
      $c->config->{callflow}->{sbc},    'sbc',
      $c->config->{callflow}->{app},    'app',
      $c->config->{callflow}->{pbx},    'pbx',
      $c->config->{callflow}->{b2b},    'b2b',
    );

    my $canvas_margin = 100; # enough free space around diagram for text etc
    my $canvas_elem_distance = 220; # horizontal distance between element lines
    my $canvas_pkg_distance = 30; # vertical distance between packet arrows

    my $canvas_elem_line_width = 2;
    my $canvas_elem_line_color = 'darkgray';
    my $canvas_elem_font = 'Courier:bold';
    my $canvas_elem_font_size = 8;
    my $canvas_elem_font_color = 'darkgray';

    my $canvas_pkg_line_width = 2;
    my $canvas_pkg_line_color = 'green';
    my %canvas_pkg_line_colors = (TCP => 'blue');
    my $canvas_pkg_font = 'Courier:bold';
    my $canvas_pkg_font_size = 8;
    my $canvas_pkg_font_color = 'dimgray';

    my $html_padding = 5;

    my %ext_uas = ();
    my @uas = ();

    ### gather all involved elements
    foreach my $packet(@{$packets}) {
      if(exists($int_uas{$packet->src_ip.':'.$packet->src_port})) {
        #print "skipping internal elem ".$packet->src_ip.':'.$packet->src_port." (".$int_uas{$packet->src_ip.':'.$packet->src_port}.")\n";
        my $ua = $int_uas{$packet->src_ip.':'.$packet->src_port};
        push (@uas, $ua) unless grep {$_ eq $ua} @uas;
      }
      elsif(exists($ext_uas{$packet->src_ip.':'.$packet->src_port})) {
        #print "skipping known external elem ".$packet->src_ip.':'.$packet->src_port."\n";
      }
      else {
        #print "adding new src elem ".$packet->src_ip.':'.$packet->src_port."\n";
        $ext_uas{$packet->src_ip.':'.$packet->src_port} = 1;
        # TODO: prefix "proto:" as well
        push @uas, $packet->src_ip.':'.$packet->src_port;
      }

      if(exists($int_uas{$packet->dst_ip.':'.$packet->dst_port})) {
        #print "skipping internal elem ".$packet->dst_ip.':'.$packet->dst_port." (".$int_uas{$packet->dst_ip.':'.$packet->dst_port}.")\n";
        my $ua = $int_uas{$packet->dst_ip.':'.$packet->dst_port};
        push (@uas, $ua) unless grep {$_ eq $ua} @uas;
      }
      elsif(exists($ext_uas{$packet->dst_ip.':'.$packet->dst_port})) {
        #print "skipping known external elem ".$packet->dst_ip.':'.$packet->dst_port."\n";
      }
      else {
        #print "adding new dst elem ".$packet->dst_ip.':'.$packet->dst_port."\n";
        $ext_uas{$packet->dst_ip.':'.$packet->dst_port} = 1;
        # TODO: prefix "proto:" as well
        push @uas, $packet->dst_ip.':'.$packet->dst_port;
      }
    }

    ### calculate x position of all uas
    my %uas_pos_x = ();
    $i = 0;
    foreach my $ua(@uas) {
      my $name = $ua;
      foreach my $k(keys %int_uas) {
        if($ua eq $int_uas{$k}) {
          $uas_pos_x{$k} = $canvas_margin + $canvas_elem_distance*$i;
        }
      }
      $uas_pos_x{$ua} = $canvas_margin + $canvas_elem_distance*$i;
      ++$i;
    }

    ### calculate canvas size
    # TODO: take into account length of "proto:[ipv6]:port"
    my $canvas_width = 2*$canvas_margin + $canvas_elem_distance*(@uas - 1);
    my $canvas_height = 2*$canvas_margin + $canvas_pkg_distance*(@{$packets} + 1); # leave one pkg_distance free at begin and end
    my $canvas = NGCP::Panel::Utils::GD::Simple->new($canvas_width, $canvas_height);
    $canvas->bgcolor('white');

    ### prepare html
    $r_info->{width} = $canvas_width;
    $r_info->{height} = $canvas_height;
    $r_info->{areas} = ();

    ### draw vertical lines
    my $offset = $canvas_margin;
    foreach my $ua(@uas) {
      draw_line($canvas, $offset, $canvas_margin, $offset, $canvas_height-$canvas_margin, $canvas_elem_line_width, $canvas_elem_line_color);
      my @bounds = $canvas->stringBounds($ua); # get bounds for text centering
      draw_text($canvas, $offset-int(abs($bounds[0])/2), $canvas_margin-abs($bounds[1]), $canvas_elem_font, $canvas_elem_font_size, $canvas_elem_font_color, $ua);
      $offset += $canvas_elem_distance;
    }

    ### draw arrows
    my $last_timestamp = undef;
    my $y_offset = $canvas_margin + $canvas_pkg_distance;
    $i = 1;
    foreach my $packet(@{$packets}) {
      my $time_offset = defined $last_timestamp ? ($packet->timestamp->hires_epoch - $last_timestamp->hires_epoch) : 0;
      $last_timestamp = $packet->timestamp;
      my $from_x = $uas_pos_x{$packet->src_ip.':'.$packet->src_port};
      my $to_x = $uas_pos_x{$packet->dst_ip.':'.$packet->dst_port};
      #print "arrow from ".$packet->src_ip.':'.$packet->src_port." to ".$packet->dst_ip.':'.$packet->dst_port.": $from_x - $to_x\n";
      draw_arrow($canvas, $from_x, $y_offset, $to_x, $y_offset, $canvas_pkg_line_width,
      	$canvas_pkg_line_colors{$packet->transport} || $canvas_pkg_line_color);
      $packet->payload =~ /\ncseq:\s*(\d+)\s+[a-zA-Z]+/i;
      my $cseq = $1 ? $1 : '?';
      my $txt = sprintf($i.'. '.$packet->get_column('method').' ('.$cseq.', +%0.3fs)', $time_offset);
      my @bounds = $canvas->stringBounds($txt); # get bounds for text centering
      if($from_x < $to_x) {
        $from_x = $from_x+int($canvas_elem_distance/2)-int($bounds[0]/2);
      } elsif($from_x > $to_x) {
        $from_x = $from_x-int($canvas_elem_distance/2)-int($bounds[0]/2);
      } else {
        $from_x += 10; # call to itself, e.g. in cf loop
      }
      draw_text($canvas, $from_x, $y_offset-int(abs($bounds[1])/2), $canvas_pkg_font, $canvas_pkg_font_size, $canvas_pkg_font_color, $txt);

      push @{$r_info->{areas}}, {"id", $packet->id, "coords", ($from_x-$html_padding).','.($y_offset-abs($bounds[1])-$html_padding).','.($from_x+abs($bounds[0])+$html_padding).','.($y_offset)};

      $y_offset += $canvas_pkg_distance;
      ++$i;
    }
    $$r_png = $canvas->png;
}


sub generate_callmap {
    my $c = shift;
    my $packets = shift;
    my $png; my %info;
    process_callmap($c, $packets, \$png, \%info);
    return \%info;
}

sub generate_callmap_png {
    my $c = shift;
    my $packets = shift;
    my $png; my %info;
    process_callmap($c, $packets, \$png, \%info);
    return $png;    
}

1;
