package mod_net;

#
# $Id$
#

use strict;

use GD;
use USBLCD;
use POSIX qw( strftime );
use Time::HiRes qw( gettimeofday );



### Globals
###

use constant M_TITLE		=> "Network";

use constant CHART_X_STEP	=> 20;
use constant CHART_WIDTH	=> 61;
use constant CHART_HEIGHT	=> 32;
use constant CHART_INC_STEP     => 100 * 1024;

use constant UNIT_G		=> 1024 * 1024 * 1024;
use constant UNIT_M		=> 1024 * 1024;
use constant UNIT_K		=> 1024;


my %M_INFO = (
    'graph'	=> 1,
    'alarm'	=> undef,
    'event'	=> undef,
    'update'	=> 1000
);

my $M_SCREEN_WIDTH;
my $M_SCREEN_HEIGHT;
my %M_CONFIG;

my $M_FOCUS;

my $M_IFACE_INDEX;
my $M_IFACE_DIR;

my($M_SEC, $M_UCSEC);
my @M_IFACE_LIST;
my %M_IFACE_INFO;
my %M_IFACE_DATA;
my @M_RECV_DATA;
my @M_SEND_DATA;
my $M_CHART_XI;

my $M_FLASH;



### Module methods
###

# convert bps in human readable format
sub bps2human($) {
    my $value = shift;
    my $u = "";

    if ($value >= &UNIT_G) {
	$value /= &UNIT_G;
	$u = "G";
    } elsif($value >= &UNIT_M) {
	$value /= &UNIT_M;
	$u = "M";
    } elsif($value >= &UNIT_K) {
	$value /= &UNIT_K;
	$u = "K";
    }

    return sprintf("%d%s", int($value), $u);
}


# get nearest max value
sub bytes2max($) {
    my $value = shift;
    my $max;
    for ($max = 1024; $value > $max && $max < (&UNIT_G * 100); $max *= 2) {}
    return $max;
}


sub get_speed($$) {
    my $info_ref = shift;
    my $period = shift;

    open(FILE, "</proc/net/dev") || return undef;

    map { 
	$info_ref->{$_}->{'disconnect'} = 1;
	$info_ref->{$_}->{'bytes_recv'} = 0;
	$info_ref->{$_}->{'bytes_send'} = 0;
    } keys %{$info_ref};

    while (<FILE>) {
	/([A-Za-z_0-9\.]+):\s*(.*)/;
	my $iface = $1;
	next unless $iface;
	my @values = split /\s+/, $2;
	my $bytes_recv = int($values[0]);
	my $bytes_send = int($values[8]);

	if (exists $info_ref->{$iface}) {
	    $info_ref->{$iface}->{'disconnect'} = undef;
	    if ($bytes_recv > $info_ref->{$iface}->{'tot_bytes_recv'} and
		    $bytes_send > $info_ref->{$iface}->{'tot_bytes_send'} and
		    defined $info_ref->{$iface}->{'tot_bytes_recv'} and
		    defined $info_ref->{$iface}->{'tot_bytes_send'}) {
		$info_ref->{$iface}->{'bytes_recv'} = int(($bytes_recv - $info_ref->{$iface}->{'tot_bytes_recv'}) / $period);
		$info_ref->{$iface}->{'bytes_send'} = int(($bytes_send - $info_ref->{$iface}->{'tot_bytes_send'}) / $period);
	    }
	    $info_ref->{$iface}->{'tot_bytes_recv'} = $bytes_recv;
	    $info_ref->{$iface}->{'tot_bytes_send'} = $bytes_send;
	}
    }

    close FILE;
}


# draw module header
sub draw_header($$) {
    my $w = shift;
    my $h = shift;

    my $im =  new GD::Image($w, $h);
    my $white = $im->colorAllocate(255, 255, 255);
    my $black = $im->colorAllocate(0, 0, 0);

    my $x = ($w - (gdTinyFont->width * length(&M_TITLE))) / 2;

    if ($M_FOCUS) {
	$im->filledRectangle(0, 0, $w, gdTinyFont->height, $black);
	$im->string(gdTinyFont, $x, 0, &M_TITLE, $white);
    } else {
	$im->string(gdTinyFont, $x, 0, &M_TITLE, $black);
    }

    return $im;
}


# draw interface info
sub draw_iface() {
    my $im =  new GD::Image(64, 24);

    my $iface = $M_IFACE_LIST[$M_IFACE_INDEX];

    my $white = $im->colorAllocate(255, 255, 255);
    my $black = $im->colorAllocate(0, 0, 0);

    $im->string(gdTinyFont, 0, 0, $M_IFACE_DATA{$iface}->{'title'}, $black);
    $im->string(gdTinyFont, 0, gdTinyFont->height,
	"Recv: ".bps2human($M_IFACE_INFO{$iface}->{'bytes_recv'} * 8),
	$black);
    $im->string(gdTinyFont, 0, gdTinyFont->height * 2,
	"Send: ".bps2human($M_IFACE_INFO{$iface}->{'bytes_send'} * 8),
	$black);
    return $im;
}


# draw disconnect message
sub draw_disconnect($$) {
    my $w = shift;
    my $h = shift;


    my $im =  new GD::Image($w, $h);
    my $white = $im->colorAllocate(255, 255, 255);
    my $black = $im->colorAllocate(0, 0, 0);

    $im->rectangle(0, 0, $w - 1, $h - 1, $black);

    my $msg = "DISCONNECT";
    my $x = ($w - gdTinyFont->width * length($msg)) / 2;
    my $y = ($h - gdTinyFont->height) / 2;

    if ($M_FLASH) {
	$im->string(gdTinyFont, $x, $y, $msg, $black);
    }

    return $im;
}


sub draw_screen($) {
    my $usblcd = shift;

    my $iface = $M_IFACE_LIST[$M_IFACE_INDEX];

    my $im =  new GD::Image($M_SCREEN_WIDTH, $M_SCREEN_HEIGHT);
    my $white = $im->colorAllocate(255, 255, 255);
    my $black = $im->colorAllocate(0, 0, 0);

    my $im_title = draw_header($im->width - &CHART_WIDTH, 8);
    my $im_iface = draw_iface();
    
    my $im_chart;
    if ($M_IFACE_INFO{$iface}->{'disconnect'}) {
	$im_chart = draw_disconnect(&CHART_WIDTH, &CHART_HEIGHT);
    } else {
	$im_chart = main::chart(&CHART_WIDTH, &CHART_HEIGHT,
	    $M_IFACE_DATA{$iface}->{$M_IFACE_DIR == 0 ? 'data_recv' : 'data_send'},
	    $M_IFACE_DATA{$iface}->{'xi'},
	    &CHART_X_STEP,
	    $M_IFACE_INFO{$iface}->{$M_IFACE_DIR == 0 ? 'max_recv' : 'max_send'});
    }

    $im->copy($im_title, 0, 0, 0, 0, $im_title->width, $im_title->height);
    $im->copy($im_iface, 0, 8, 0, 0, $im_iface->width, $im_iface->height);
    $im->copy($im_chart, $M_SCREEN_WIDTH - &CHART_WIDTH, 0, 0, 0, &CHART_WIDTH, &CHART_HEIGHT);

    unless ($M_IFACE_INFO{$iface}->{'disconnect'}) {
	my $dir_title = ($M_IFACE_DIR == 0) ? "Rx" : "Tx";
	$im->filledRectangle($im->width - (&gdTinyFont->width * length($dir_title)) - 2,
	    $im->height - gdTinyFont->height,
	    $im->width, $im->height, $black);
	$im->string(gdTinyFont, $im->width - gdTinyFont->width * length($dir_title) - 1,
	    $im->height - gdTinyFont->height, $dir_title, $white);

	my $max_str = bps2human($M_IFACE_INFO{$iface}->{$M_IFACE_DIR == 0 ? 'max_recv' : 'max_send'});
	$im->string(gdTinyFont, $im->width - (length($max_str) * &gdTinyFont->width) - 3 - $im_chart->width, 8, $max_str, $black);
    }

    $usblcd->bitbltgd(0, 0, $im);
}



### Module interface
###

sub init(%) {
    my %init_params = @_;

    $M_SCREEN_WIDTH = $init_params{'width'};
    $M_SCREEN_HEIGHT = $init_params{'height'};
    %M_CONFIG = %init_params;
    @M_IFACE_LIST = map { $_->{'name'} } @{$M_CONFIG{'interface'}};

    foreach my $iface_ref (@{$M_CONFIG{'interface'}}) {
        my $iface = $iface_ref->{'name'};
	$M_IFACE_INFO{$iface} = {
	    'disconnect'	=> 1,
	    'tot_bytes_send'	=> undef,
	    'tot_bytes_recv'	=> undef,
	    'bytes_send'	=> undef,
	    'bytes_recv'	=> undef
	};
	$M_IFACE_DATA{$iface} = {
	    'title'	=> $iface_ref->{'title'} ? $iface_ref->{'title'} : $iface,
	    'valid' 	=> exists($M_IFACE_INFO{$iface}),
	    'xi'	=> 0,
	    'max_recv'	=> 0,
	    'max_send'	=> 0,
	    'data_recv'	=> [],
	    'data_send'	=> []
	};
    }

    $M_IFACE_INDEX = 0;
    $M_IFACE_DIR = "recv";

    $M_FLASH = 1;

    return %M_INFO;
}


sub focus($) {
    my $usblcd = shift;

    $M_FOCUS = 1;

    draw_screen($usblcd);

    return &main::M_FOCUS_ACCEPT;
}


sub event($$) {
    my $usblcd = shift;
    my $event_ref = shift;


    if ($event_ref->{'type'} == USBLCD_POLLEVENT_KEYDOWN) {
	if ($event_ref->{'value'} == USBLCD_KEY_CANCEL) {
	    $M_FOCUS = undef;

	    draw_screen($usblcd);

	    return &main::M_EVENT_UNFOCUS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_UP) {
	    for (my $i = 0; $i < scalar @M_IFACE_LIST; $i++) {
		$M_IFACE_INDEX = scalar @M_IFACE_LIST if $M_IFACE_INDEX == 0;
		$M_IFACE_INDEX--;
		last if $M_IFACE_DATA{$M_IFACE_LIST[$i]}->{'valid'};
	    }
	    draw_screen($usblcd);
	    return &main::M_EVENT_PROCESS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_DOWN) {
	    for (my $i = 0; $i < scalar @M_IFACE_LIST; $i++) {
		$M_IFACE_INDEX = ($M_IFACE_INDEX + 1) % scalar @M_IFACE_LIST;
		last if $M_IFACE_DATA{$M_IFACE_LIST[$i]}->{'valid'};
	    }
	    draw_screen($usblcd);
	    return &main::M_EVENT_PROCESS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_F2) {
	    $M_IFACE_DIR = $M_IFACE_DIR == 0 ? 1 : 0;
	    draw_screen($usblcd);
	    return &main::M_EVENT_PROCESS;
	}
    }

    return &main::M_EVENT_DISCARD;
}


sub enter($) {
    my $usblcd = shift;

    $M_FOCUS = undef;
    ($M_SEC, $M_UCSEC) = (undef, undef);

    foreach my $iface (@M_IFACE_LIST) {
	next unless $M_IFACE_DATA{$iface}->{'valid'};

	$M_IFACE_INFO{$iface}->{'tot_bytes_send'} = undef;
	$M_IFACE_INFO{$iface}->{'tot_bytes_recv'} = undef
	$M_IFACE_INFO{$iface}->{'bytes_send'} = undef;
	$M_IFACE_INFO{$iface}->{'bytes_recv'} = undef;

	$M_IFACE_DATA{$iface}->{'xi'} = 0;
	$M_IFACE_DATA{$iface}->{'data_recv'} = [];
	$M_IFACE_DATA{$iface}->{'data_send'} = [];
    }

    draw_screen($usblcd);
}


sub leave($) {
    my $usblcd = shift;

    foreach my $iface (@M_IFACE_LIST) {
	next unless $M_IFACE_DATA{$iface}->{'valid'};
	$M_IFACE_DATA{$iface}->{'data_recv'} = undef;
	$M_IFACE_DATA{$iface}->{'data_send'} = undef;

	$M_IFACE_INFO{$iface} = undef;
    }
}


sub update($) {
    my $usblcd = shift;

    my($sec, $usec) = gettimeofday();
    my $period = (defined $M_SEC and defined $M_UCSEC) ?
	($sec - $M_SEC) + ($usec - $M_UCSEC) / 1000000 : undef;
    get_speed(\%M_IFACE_INFO, $period);
    ($M_SEC, $M_UCSEC) = ($sec, $usec);

    foreach my $iface (@M_IFACE_LIST) {
	next unless $M_IFACE_DATA{$iface}->{'valid'};

	$M_IFACE_DATA{$iface}->{'xi'} = ($M_IFACE_DATA{$iface}->{'xi'} + 1) % &CHART_X_STEP;

	my $max_recv = main::chart_add($M_IFACE_INFO{$iface}->{'bytes_recv'} * 8,
	    $M_IFACE_DATA{$iface}->{'data_recv'}, &CHART_WIDTH - 2);
	$M_IFACE_INFO{$iface}->{'max_recv'} = bytes2max($max_recv);

	my $max_send = main::chart_add($M_IFACE_INFO{$iface}->{'bytes_send'} * 8,
	    $M_IFACE_DATA{$iface}->{'data_send'}, &CHART_WIDTH - 2);
	$M_IFACE_INFO{$iface}->{'max_send'} = bytes2max($max_send);

    }

    $M_FLASH = $M_FLASH ? undef : 1;

    draw_screen($usblcd);
}


1;
