package mod_udphid;

#
# $Id$
#

use strict;

use GD;
use USBLCD;
use UDPHID qw( UP_GENERIC_DESKTOP UP_KEYBOARD UP_BUTTON UP_CONSUMER UP_VENDOR_SPECIFIC
    U_KEYBOARD U_MOUSE U_POINTER U_X U_Y U_CONSUMER_CONTROL
    tagCollection tagEndCollection tagUsagePage tagUsage tagUsageMinimum tagUsageMaximum
    tagLogicalMinimum tagLogicalMaximum
    tagReportID tagReportSize tagReportCount tagInput tagOutput );



### Globals
###

use constant M_TITLE		=> "IR UDPHID";

use constant LIST_WIDTH		=> 66;
use constant LIST_HEIGHT	=> 24;
use constant STAT_WIDTH		=> 56;
use constant STAT_HEIGHT	=> 24;

use constant FLASH_DISK_MOD	=> 3;
use constant FLASH_TIME_MOD	=> 4;
use constant FLASH_TITLE_MOD	=> 3;
use constant MEDIA_TIMEOUT	=> 15;
use constant MEDIA_TITLE_TIME	=> 8;

use constant F_BUTTON_WIDTH	=> 30;

use constant REP_INTEGER	=> 4;
use constant REP_STRING		=> 5;

use constant MP_TYPE_DURATION	=> 0;
use constant MP_TYPE_POSITION	=> 1;
use constant MP_TYPE_STATUS	=> 2;
use constant MP_TYPE_TITLE	=> 3;
use constant MP_TYPE_ARTIST	=> 4;
use constant MP_TYPE_ALBUM	=> 5;
use constant MP_TYPE_YEAR	=> 6;
use constant MP_TYPE_TRACK	=> 7;

use constant MP_STATUS_UNDEF	=> 0;
use constant MP_STATUS_STOP	=> 1;
use constant MP_STATUS_PAUSE	=> 2;
use constant MP_STATUS_PLAY	=> 3;

use constant MP_MODE_TIME	=> 0;
use constant MP_MODE_REMAIN	=> 1;
use constant MP_MODE_MAX	=> 2;


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

my $M_VERBOSE;
my $M_IMAGES_DIR;
my $M_SCREEN_WIDTH;
my $M_SCREEN_HEIGHT;
my %M_CONFIG;

my $M_FOCUS;

my %M_DEVICE_MODE;
my %M_EVT_MODE_CHANGE;
my %M_EVT_MAP;

my $M_HOST_INDEX;

my $M_HOST_SELECT_INDEX;
my $M_HOST_LIST_START;

my $M_UDPHID;
my $M_HID_DESCRIPTOR;

my $M_IM_CONNECT;
my $M_IM_DISCONNECT;
my @M_IM_DISK;
my $M_IM_DISK_STOP;
my $M_DISK_N;
my $M_TIME_N;

my $M_STAT_CODE;
my $M_STAT_REPORT;
my $M_STAT_USAGE;
my $M_STAT_EVENT_NAME;

my %M_MEDIA_INFO;
my $M_FLASH_DISK;
my $M_FLASH_TIME;
my $M_FLASH_TITLE;
my $M_MEDIA_ALIVE;
my $M_MEDIA_MODE;
my $M_MEDIA_UPDATE_CTRL;
my $M_TITLE_POS;
my $M_TITLE_STATE;
my $M_TITLE_STATE_TIME;
# 0 - artist static, 1 - artist dynamic, 3 artistr dynamic pause


### Module subs
###

# update Event Map according configuration
sub evt_map_update() {
    # get current host
    my $host_info_ref;
    if (ref $M_CONFIG{'host'} eq "ARRAY") {
	# defined hosts list
	$M_HOST_INDEX %= scalar @{$M_CONFIG{'host'}};
	$host_info_ref = $M_CONFIG{'host'}->[$M_HOST_INDEX];
    } else {
	# defined single host
	$host_info_ref = $M_CONFIG{'host'};
    }

    # get global events
    %M_EVT_MAP = ();
    if (exists $M_CONFIG{'event'}) {
	if (ref $M_CONFIG{'event'} eq "ARRAY") {
	    foreach my $event_ref (@{$M_CONFIG{'event'}}) {
		my $key = "$event_ref->{'type'}_$event_ref->{'code'}";
		$M_EVT_MAP{$key}  = {
		    'name' => $event_ref->{'name'},
		    'report' => $event_ref->{'report'},
		    'usage' => $event_ref->{'usage'}
		};
	    }
	} else {
	    my $key = "$M_CONFIG{'event'}->{'type'}_$M_CONFIG{'event'}->{'code'}";
	    $M_EVT_MAP{$key}  = {
		'name' => $M_CONFIG{'event'}->{'name'},
		'report' => $M_CONFIG{'event'}->{'report'},
		'usage' => $M_CONFIG{'event'}->{'usage'}
	    };
	}
    }

    if (exists $M_CONFIG{'device'}) {
	# get allowed devices configuration
	my @devices_array;
	my %allowed_devices;
	map { $allowed_devices{$_} = undef } split /,/, $host_info_ref->{'devices'};

	if (ref $M_CONFIG{'device'} eq "ARRAY") {
	    foreach my $device_ref (@{$M_CONFIG{'device'}}) {
		if (exists $allowed_devices{'*'} || exists $allowed_devices{$device_ref->{'name'}}) {
		    push @devices_array, $device_ref;
		}
	    }
	} else {
	    if (exists $allowed_devices{'*'} || exists $allowed_devices{$M_CONFIG{'device'}->{'name'}}) {
		push @devices_array, $M_CONFIG{'device'};
	    }
	}

	# get mode change events from related devices
	%M_EVT_MODE_CHANGE = ();
	foreach my $device_ref (@devices_array) {
	    if (exists $device_ref->{'mode_change_event'} && exists $device_ref->{'mode'} && ref $device_ref->{'mode'} eq "ARRAY") {
		if (ref $device_ref->{'mode_change_event'} eq "ARRAY") {
		    foreach my $event_ref (@{$device_ref->{'mode_change_event'}}) {
			my $key = "$event_ref->{'type'}_$event_ref->{'code'}";
			$M_EVT_MODE_CHANGE{$key} = {
			    'device' => $device_ref->{'name'},
			    'value' => $event_ref->{'value'}
			};
		    }
		} else {
		    my $key = "$device_ref->{'mode_change_event'}->{'type'}_$device_ref->{'mode_change_event'}->{'code'}";
		    $M_EVT_MODE_CHANGE{$key} = {
			'device' => $device_ref->{'name'},
			'value' => $device_ref->{'mode_change_event'}->{'value'}
		    };
		}
	    }
	}

	# get events from related devices according current modes
	foreach my $device_ref (@devices_array) {
	    # current mode events
	    if (exists $device_ref->{'mode'}) {
		my $events_array_ref;

		if (ref $device_ref->{'mode'} eq "ARRAY") {
		    $M_DEVICE_MODE{$device_ref->{'name'}} %= scalar @{$device_ref->{'mode'}};
		    $events_array_ref = $device_ref->{'mode'}->[$M_DEVICE_MODE{$device_ref->{'name'}}]->{'event'};
		} else {
		    $events_array_ref = $device_ref->{'mode'}->{'event'};
		}

		if (defined $events_array_ref) {
		    if (ref $events_array_ref eq "ARRAY") {
			foreach my $event_ref (@{$events_array_ref}) {
			    my $key = "$event_ref->{'type'}_$event_ref->{'code'}";
			    $M_EVT_MAP{$key}  = {
				'name' => $event_ref->{'name'},
				'report' => $event_ref->{'report'},
				'usage' => $event_ref->{'usage'}
			    };
			}
		    } else {
			my $key = "$events_array_ref->{'type'}_$events_array_ref->{'code'}";
			$M_EVT_MAP{$key}  = {
			    'name' => $events_array_ref->{'name'},
			    'report' => $events_array_ref->{'report'},
			    'usage' => $events_array_ref->{'usage'}
			};
		    }
		}
	    }

	    # common device events
	    if (exists $device_ref->{'event'}) {
		if (ref $device_ref->{'event'}eq "ARRAY") {
		    foreach my $event_ref (@{$device_ref->{'event'}}) {
			my $key = "$event_ref->{'type'}_$event_ref->{'code'}";
			$M_EVT_MAP{$key}  = {
			    'name' => $event_ref->{'name'},
			    'report' => $event_ref->{'report'},
			    'usage' => $event_ref->{'usage'}
			};
		    }
		} else {
		    my $key = "$device_ref->{'event'}->{'type'}_$device_ref->{'event'}->{'code'}";
		    $M_EVT_MAP{$key}  = {
			'name' => $device_ref->{'event'}->{'name'},
			'report' => $device_ref->{'event'}->{'report'},
			'usage' => $device_ref->{'event'}->{'usage'}
		    };
		}
	    }
	}

	if ($M_VERBOSE) {
	    print "Update Event Map\n";
	    main::_dump(\%M_EVT_MODE_CHANGE);
	    main::_dump(\%M_EVT_MAP);
	}
    }
}



### UDPHID section
###

sub udphid_descriptor() {
    $M_HID_DESCRIPTOR =
	# Keyboard
	tagUsagePage(&UP_GENERIC_DESKTOP).
	tagUsage(&U_KEYBOARD).
	tagCollection(Application => 1).
	    tagReportID(0x01).
	    tagUsagePage(&UP_KEYBOARD).
	    tagUsageMinimum(224).
	    tagUsageMaximum(231).
	    tagLogicalMinimum(0).
	    tagLogicalMaximum(1).
	    tagReportSize(1).
	    tagReportCount(8).
	    tagInput(Data => 1, Variable => 1, Absolute => 1).
	    tagReportCount(6).
	    tagReportSize(8).
	    tagLogicalMinimum(0).
	    tagLogicalMaximum(256).
	    tagUsagePage(&UP_KEYBOARD).
	    tagUsageMinimum(0).
	    tagUsageMaximum(256).
	    tagInput(Data => 1, Array => 1).
	tagEndCollection().
	# Mouse
	tagUsagePage(&UP_GENERIC_DESKTOP).
	tagUsage(&U_MOUSE).
	tagCollection(Application => 1).
	    tagReportID(0x02).
	    tagUsage(&U_POINTER).
	    tagCollection(Physical => 1).
		tagUsagePage(&UP_BUTTON).
		tagUsageMinimum(1).
		tagUsageMaximum(3).
		tagLogicalMinimum(0).
		tagLogicalMaximum(1).
		tagReportCount(3).
		tagReportSize(1).
		tagInput(Data => 1, Variable => 1, Absolute => 1).
		tagReportCount(1).
		tagReportSize(5).
		tagInput(Constant => 1).
		tagUsagePage(&UP_GENERIC_DESKTOP).
		tagUsage(&U_X).
		tagUsage(&U_Y).
		tagLogicalMinimum(-127).
		tagLogicalMaximum(127).
		tagReportSize(8).
		tagReportCount(2).
		tagInput(Data => 1, Variable => 1, Relative => 1).
	tagEndCollection().
    tagEndCollection().
    # Consumer control
    tagUsagePage(&UP_CONSUMER).
    tagUsage(&U_CONSUMER_CONTROL).
    tagCollection(Application => 1).
	tagReportID(0x03).
	tagUsagePage(UP_CONSUMER).
	tagUsageMinimum(0).
	tagUsageMaximum(1023).
	tagReportCount(1).
	tagReportSize(16).
	tagLogicalMinimum(0).
	tagLogicalMaximum(1023).
	tagInput(Data => 1, Array => 1, Absolute => 1).
    tagEndCollection().
    # Vendor-specific Media Player display
    tagUsagePage(&UP_VENDOR_SPECIFIC).
    tagUsage(0x01).
    tagCollection(Application => 1).
	tagReportID(&REP_INTEGER).
	tagReportCount(1).
	tagReportSize(8).
	tagLogicalMinimum(0).
	tagLogicalMaximum(127).
	tagUsage(0x01).                 # type of numeric message
	tagOutput(Data => 1, Variable=> 1, Absolute => 1).
	tagReportCount(1).
	tagReportSize(32).              # value of numeric message
	tagLogicalMaximum(2147483647).
	tagUsage(0x02).
	tagOutput(Data => 1, Variable=> 1, Absolute => 1, BufferedBytes => 1).
	tagReportID(&REP_STRING).
	tagReportCount(1).
	tagReportSize(8).
	tagLogicalMinimum(0).
	tagLogicalMaximum(127).
	tagUsage(0x03).                 # type of string message
	tagOutput(Data => 1, Variable=> 1, Absolute => 1).
	tagReportCount(65).
	tagReportSize(8).               # string value
	tagLogicalMinimum(-128).
	tagLogicalMaximum(127).
	tagUsage(0x04).
	tagOutput(Data => 1, Variable=> 1, Absolute => 1, BufferedBytes => 1).
    tagEndCollection();
}


# connect to current UDPHID host
sub udphid_connect() {
    my $host_ref = $M_CONFIG{'host'}->[$M_HOST_INDEX];

    print "connect to $host_ref->{'addr'}:$host_ref->{'port'}\n" if $M_VERBOSE;

    $M_UDPHID = new UDPHID('Host' => $host_ref->{'addr'}, Port => $host_ref->{'port'});
    if (not defined $M_UDPHID) {
	print "failed to create UDPHID object\n" if $M_VERBOSE;
	return;
    }

    my $ret = $M_UDPHID->auth($host_ref->{'login'}, $host_ref->{'password'});
    if ($ret != 0) {
	$M_UDPHID = undef;
	print "failed to connect and auth\n" if $M_VERBOSE;
	return;
    }

    $ret = $M_UDPHID->descriptor($M_HID_DESCRIPTOR);
    if ($ret != 0) {
	$M_UDPHID = undef;
	print "failed to set descriptor\n" if $M_VERBOSE;
    }

    printf "connected successful\n" if $M_VERBOSE;
}


sub udphid_report_keyboard($) {
    my $value = shift;
    my $report = pack("CCCCCCCC", (0x01, 0x00, $value, 0xff, 0xff, 0xff, 0xff, 0xff));
    return $report;
}

sub udphid_report_mouse($$$$$) {
    my $button_1 = shift;
    my $button_2 = shift;
    my $button_3 = shift;
    my $x = shift;
    my $y = shift;

    my $buttons_map = ($button_1 << 0) | ($button_2 << 1) | ($button_3 << 2);

    my $report = pack("CCcc", (0x02, $buttons_map, $x, $y));
    return $report;
}

sub udphid_report_consumer($) {
    my $value = shift;
    my $report = pack("Cv", (0x03, $value));
    return $report;
}

sub udphid_output_report()
{
    my $data;

    if (defined $M_UDPHID) {
	while ($data = $M_UDPHID->get_report()) {
	    my ($report) = unpack("c", $data);
	    if ($report == &REP_INTEGER) {
		my(undef, $type, $value) = unpack("ccV", $data);
		$M_MEDIA_INFO{'change'}->{$type} = ($M_MEDIA_INFO{$type} != $value);
		$M_MEDIA_INFO{$type} = $value;
		if ($type == &MP_TYPE_STATUS && $value > &MP_STATUS_UNDEF) {
		    if ($M_MEDIA_ALIVE <= 0) {
			$M_MEDIA_UPDATE_CTRL = 1;
		    }
		    $M_MEDIA_ALIVE = &MEDIA_TIMEOUT;
		}
	    } elsif ($report == &REP_STRING) {
		my(undef, $type, $value) = unpack("ccZ*", $data);
		$M_MEDIA_INFO{'change'}->{$type} = ($M_MEDIA_INFO{$type} ne $value);
		$M_MEDIA_INFO{$type} = $value;
	    }
	}
    }
}



# Configuration screen

# 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, $im->width, $im->height, $black);
	$im->string(gdTinyFont, $x, 0, &M_TITLE, $white);
    } else {
	$im->string(gdTinyFont, $x, 0, &M_TITLE, $black);
    }

    return $im;
}

sub draw_stat($$) {
    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->string(gdTinyFont, 0, 0, sprintf("%08X", $M_STAT_CODE), $black) if defined $M_STAT_CODE;
    $im->string(gdTinyFont, 0, 8, uc(substr $M_STAT_REPORT, 0, 1). " ".$M_STAT_USAGE, $black);
    $im->string(gdTinyFont, 0, 16, $M_STAT_EVENT_NAME, $black);

    return $im;
}

my $s = 0;
sub draw_conf_screen($) {
    my $usblcd = shift;

    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 @l = map { $_->{'title'} } @{$M_CONFIG{'host'}};

    my $im_header = draw_header($im->width, 8);
    my $im_list = main::draw_select_list(&LIST_WIDTH, &LIST_HEIGHT, \@l, $M_HOST_LIST_START, $M_HOST_SELECT_INDEX);
    my $im_stat = draw_stat(&STAT_WIDTH, &STAT_HEIGHT);

    $im->copy($im_header, 0, 0, 0, 0, $im_header->width, $im_header->height);
    $im->copy($im_list, 0, $im->height - $im_list->height, 0, 0, $im_list->width, $im_list->height);

    if (defined $M_UDPHID) {
        $im->copy($M_IM_CONNECT, $im->width - $M_IM_CONNECT->width, 0, 0, 0, $M_IM_CONNECT->width, $M_IM_CONNECT->height);
    }
    else {
        $im->copy($M_IM_DISCONNECT, $im->width - $M_IM_CONNECT->width, 0, 0, 0, $M_IM_CONNECT->width, $M_IM_CONNECT->height);
    }

    $im->copy($im_stat, $im->width - $im_stat->width, $im->height - $im_stat->height, 0, 0, $im_stat->width, $im_stat->height);

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


# Media Screen
sub ms_time($) {
    my $time = shift;

    my $sec = $time % 60;
    my $min = ($time / 60) % 60;
    my $hour = ($time / 3600);

    return ($hour, $min, $sec);
}

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


    my $change_ref = $M_MEDIA_INFO{'change'};
    my $status = $M_MEDIA_INFO{&MP_TYPE_STATUS};

    if ($change_ref->{&MP_TYPE_STATUS} && ($status == &MP_STATUS_STOP || $status == &MP_STATUS_PAUSE || $status == &MP_STATUS_PLAY)) {
	$M_MEDIA_UPDATE_CTRL = 1;
    }

    if ($status == &MP_STATUS_PLAY) {
        $M_DISK_N = ($M_DISK_N + 1) % 3 if $M_FLASH_DISK == 0;
    }

    # reset title state machine if media info was changed
    if ($change_ref->{&MP_TYPE_TITLE} || $change_ref->{&MP_TYPE_ARTIST} || 
		$change_ref->{&MP_TYPE_YEAR} || $change_ref->{&MP_TYPE_ALBUM}) {
	$M_TITLE_STATE = 0;
	$M_TITLE_STATE_TIME = time() + &MEDIA_TITLE_TIME;
	$M_TITLE_POS = 0;
    }

    # change title state machine state by timeout
    if ($M_TITLE_STATE_TIME > 0 && time() >= $M_TITLE_STATE_TIME) {
	$M_TITLE_STATE = ($M_TITLE_STATE + 1) % 2;
	$M_TITLE_STATE_TIME = time() + &MEDIA_TITLE_TIME;
	$M_TITLE_POS = 0;
    }
    # if there is no year and album in media info then title state machine will be reset
    if ($M_TITLE_STATE == 1 && !$M_MEDIA_INFO{&MP_TYPE_YEAR} && !$M_MEDIA_INFO{&MP_TYPE_ALBUM}) {
	$M_TITLE_STATE = 0;
	$M_TITLE_STATE_TIME = 0;
    }

    $M_MEDIA_INFO{'change'} = {};


    if ($M_MEDIA_UPDATE_CTRL) {
	$M_MEDIA_UPDATE_CTRL = undef;

	# draw control buttons
	my $im =  new GD::Image($M_SCREEN_WIDTH, 8);
	my $white = $im->colorAllocate(255, 255, 255);
	my $black = $im->colorAllocate(0, 0, 0);

	my $play_pause_title = ($status == &MP_STATUS_PLAY) ? "Pause" : "Play";
	$im->filledRectangle(0, 0, &F_BUTTON_WIDTH, $im->height, $black);
	$im->string(gdTinyFont,
	    (&F_BUTTON_WIDTH - gdTinyFont->width * length($play_pause_title)) / 2,
	    0, $play_pause_title, $white);

	$im->string(gdTinyFont,
	    ($im->width - (gdTinyFont->width * length(&M_TITLE))) / 2, 0,
	    &M_TITLE, $black);

	my $next_title = "Next";
	$im->filledRectangle($im->width - &F_BUTTON_WIDTH, 0,
	    $im->width - 1, $im->height - 1, $black);
	$im->string(gdTinyFont,
	    $im->width - (&F_BUTTON_WIDTH + gdTinyFont->width * length($next_title)) / 2, 0,
	    $next_title, $white);

	$usblcd->bitbltgd(0, $M_SCREEN_HEIGHT - $im->height, $im);
    }
    else {
	# draw song time and title

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


	# draw time
	my $track_str = "  ";
	my $time_str = " 00:00:00";

	if ($status == &MP_STATUS_PAUSE || $status == &MP_STATUS_PLAY) {
	    $track_str = "--";
	    if ($M_MEDIA_INFO{&MP_TYPE_TRACK} && length($M_MEDIA_INFO{&MP_TYPE_TRACK}) <= 2) {
		$track_str = sprintf("%02d", int($M_MEDIA_INFO{&MP_TYPE_TRACK}));
	    }
	    my $time;
	    my $time_sign;
	    if ($M_MEDIA_MODE == &MP_MODE_REMAIN) {
		$time = $M_MEDIA_INFO{&MP_TYPE_DURATION} - $M_MEDIA_INFO{&MP_TYPE_POSITION};
		$time_sign = "-";
	    } else {
		$time = $M_MEDIA_INFO{&MP_TYPE_POSITION};
		$time_sign = " ";
	    }
	    my($hour, $min, $sec) = ms_time($time);
	    $time_str = sprintf("%s%02d:%02d:%02d", $time_sign, $hour, $min, $sec);
	}

	if ($status == &MP_STATUS_PAUSE) {
	    $M_TIME_N = !$M_TIME_N if $M_FLASH_TIME == 0;
	    if ($M_TIME_N) {
		$im->string(gdMediumBoldFont, 8, 0, "[$track_str]$time_str", $black);
	    }
	    else {
		$im->string(gdMediumBoldFont, 8, 0, "[$track_str]         ", $black);
	    }
	}
	else {
	    $M_TIME_N = 0;
	    $im->string(gdMediumBoldFont, 8, 0, "[$track_str]$time_str", $black);
	}

	if ($status == &MP_STATUS_PAUSE || $status == &MP_STATUS_PLAY) {
	    $im->copy($M_IM_DISK[$M_DISK_N], $im->width - $M_IM_DISK[$M_DISK_N]->width - 1, 3,
		0, 0, $M_IM_DISK[$M_DISK_N]->width, $M_IM_DISK[$M_DISK_N]->height);
	}
	else {
	    $im->copy($M_IM_DISK_STOP, $im->width - $M_IM_DISK_STOP->width - 1, 3,
		0, 0, $M_IM_DISK_STOP->width, $M_IM_DISK_STOP->height);
	}

	# draw title
	my $title_str = "";
	if ($M_TITLE_STATE == 0) {
	    if ($M_MEDIA_INFO{&MP_TYPE_ARTIST} && $M_MEDIA_INFO{&MP_TYPE_TITLE}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_ARTIST}." - ".$M_MEDIA_INFO{&MP_TYPE_TITLE};
	    }
	    elsif ($M_MEDIA_INFO{&MP_TYPE_ARTIST}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_ARTIST};
	    }
	    elsif ($M_MEDIA_INFO{&MP_TYPE_TITLE}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_TITLE};
	    }
	}
	else {
	    if ($M_MEDIA_INFO{&MP_TYPE_YEAR} && $M_MEDIA_INFO{&MP_TYPE_ALBUM}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_YEAR}." - ".$M_MEDIA_INFO{&MP_TYPE_ALBUM};
	    }
	    elsif ($M_MEDIA_INFO{&MP_TYPE_YEAR}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_YEAR};
	    }
	    elsif ($M_MEDIA_INFO{&MP_TYPE_ALBUM}) {
		$title_str = $M_MEDIA_INFO{&MP_TYPE_ALBUM};
	    }
	}

	my $title_x = 0;
	my $title_width = length($title_str) * gdTinyFont->width;
	if ($title_width <= $im->width) {
	    # static title
	    $title_x = ($im->width - $title_width) / 2;
	}
	else {
	    # scrolling title
	    my $screen_text_width = $im->width / gdTinyFont->width;
	    $title_str = (" " x $screen_text_width).$title_str;
	    my $title_str_length = length($title_str);
	    $title_str = substr($title_str, $M_TITLE_POS, $screen_text_width);

	    # stop title state machine while title scrolling
	    $M_TITLE_STATE_TIME = 0;

	    if ($M_FLASH_TITLE == 0) {
		$M_TITLE_POS += 1;
		if ($M_TITLE_POS > $title_str_length) {
		    # resume title state machine
		    $M_TITLE_STATE_TIME = time();
		}
	    }
	}
	$im->string(gdTinyFont, $title_x, 13, $title_str, $black);

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

    }
}


# common draw screen
sub draw_screen($) {
    my $usblcd = shift;

    if ($M_FOCUS || $M_MEDIA_ALIVE == 0) {
	draw_conf_screen($usblcd);
    } else {
	$M_FLASH_DISK = ($M_FLASH_DISK + 1) % &FLASH_DISK_MOD;
	$M_FLASH_TIME = ($M_FLASH_TIME + 1) % &FLASH_TIME_MOD;
	$M_FLASH_TITLE = ($M_FLASH_TITLE + 1) % &FLASH_TITLE_MOD;
	draw_ms_screen($usblcd);
    }
}



### Module interface
###

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

    $M_VERBOSE = $init_params{'verbose'};
    $M_SCREEN_WIDTH = $init_params{'width'};
    $M_SCREEN_HEIGHT = $init_params{'height'};
    $M_IMAGES_DIR = $init_params{'images_dir'};
    %M_CONFIG = %init_params;

    # get default host index
    $M_HOST_INDEX = 0;
    if (ref $M_CONFIG{'host'} eq 'ARRAY') {
	my $i = 0;
	foreach my $host_ref (@{$M_CONFIG{'host'}}) {
	    if ($host_ref->{'title'} eq $M_CONFIG{'default_host'}) {
		$M_HOST_INDEX = $i;
		last;
	    }
	    $i++;
	}
    }

    %M_MEDIA_INFO = {
	&MP_TYPE_DURATION => 0,
	&MP_TYPE_POSITION => 0,
	&MP_TYPE_STATUS => 0,
	&MP_TYPE_TITLE => "",
	&MP_TYPE_ARTIST => "",
	&MP_TYPE_ALBUM => "",
	&MP_TYPE_YEAR => "",
	&MP_TYPE_TRACK => "",
	'change' => {
	    &MP_TYPE_DURATION => 0,
	    &MP_TYPE_POSITION => 0,
	    &MP_TYPE_STATUS => 0,
	    &MP_TYPE_TITLE => 0,
	    &MP_TYPE_ARTIST => 0,
	    &MP_TYPE_ALBUM => 0,
	    &MP_TYPE_YEAR => 0,
	    &MP_TYPE_TRACK => 0
	}
    };
    $M_MEDIA_ALIVE = 0;
    $M_MEDIA_MODE = &MP_MODE_TIME;

    # create HID descriptor
    udphid_descriptor();

    # setup current host configuration
    evt_map_update();

    # connect to default host
    udphid_connect();

    return %M_INFO;
}


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

    $M_FOCUS = undef;

    $M_HOST_SELECT_INDEX = $M_HOST_INDEX;
    if (scalar @{$M_CONFIG{'host'}} > 3) {
	$M_HOST_LIST_START = ($M_HOST_INDEX > (scalar @{$M_CONFIG{'host'}} - 3)) ?
	    (scalar @{$M_CONFIG{'host'}} - 3) : $M_HOST_INDEX;
    }
    else {
	$M_HOST_LIST_START = 0;
    }

    $M_IM_CONNECT = GD::Image->new("$M_IMAGES_DIR/connect.png");
    $M_IM_DISCONNECT = GD::Image->new("$M_IMAGES_DIR/disconnect.png");

    $M_IM_DISK[0] = GD::Image->new("$M_IMAGES_DIR/disk1.png");
    $M_IM_DISK[1] = GD::Image->new("$M_IMAGES_DIR/disk2.png");
    $M_IM_DISK[2] = GD::Image->new("$M_IMAGES_DIR/disk3.png");
    $M_IM_DISK_STOP = GD::Image->new("$M_IMAGES_DIR/disk_stop.png");

    $M_DISK_N = 0;
    $M_TIME_N = 0;
    $M_FLASH_DISK = 0;
    $M_FLASH_TIME = 0;
    $M_MEDIA_UPDATE_CTRL = 1;
    $M_TITLE_STATE = 0;
    $M_TITLE_POS = 0;

    draw_screen($usblcd);
}


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

    $M_IM_CONNECT = undef;
    $M_IM_DISCONNECT = undef;
}


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;

    my $key = "$event_ref->{'type'}_$event_ref->{'value'}";

    # update laast IR code in statistics
    if ($event_ref->{'type'} == &USBLCD_POLLEVENT_IR) {
        $M_STAT_CODE = $event_ref->{'value'};
	$M_STAT_REPORT = undef;
        $M_STAT_USAGE = undef;
	$M_STAT_EVENT_NAME = undef;
    }

    if ($event_ref->{'type'} == USBLCD_POLLEVENT_KEYDOWN && !$M_FOCUS && $M_MEDIA_ALIVE > 0) {
	my @reports;

	if ($event_ref->{'value'} == USBLCD_KEY_F1) {
	    # play/pause
	    push @reports, udphid_report_consumer(0xcd);
	    push @reports, udphid_report_consumer(0);
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_F2) {
	    # next track
	    push @reports, udphid_report_consumer(0xb5);
	    push @reports, udphid_report_consumer(0);
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_CANCEL) {
	    # togle mode
	    $M_MEDIA_MODE = ($M_MEDIA_MODE  + 1) % &MP_MODE_MAX;
	    return &main::M_EVENT_PROCESS;
	}
	
	# FixMe: make different sub!
	if (defined $M_UDPHID && scalar(@reports) > 0) {
	    foreach my $report (@reports) {
		my $ret = $M_UDPHID->report($report);
		if ($ret != 0) {
		    print "failed to send report to UDPHID server, drop event: $ret\n" if $M_VERBOSE;
		}
	    }
	    return &main::M_EVENT_PROCESS;
	}
    }
    elsif ($event_ref->{'type'} == USBLCD_POLLEVENT_KEYDOWN && $M_FOCUS) {
	if ($event_ref->{'value'} == USBLCD_KEY_CANCEL) {
	    $M_FOCUS = undef;
	    $M_MEDIA_UPDATE_CTRL = 1;
	    draw_screen($usblcd);
	    return &main::M_EVENT_UNFOCUS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_UP) {
	    if ($M_HOST_SELECT_INDEX > 0) {
		$M_HOST_SELECT_INDEX -= 1;
		$M_HOST_LIST_START = $M_HOST_SELECT_INDEX if $M_HOST_LIST_START > $M_HOST_SELECT_INDEX;
	    }
	    draw_screen($usblcd);
	    return &main::M_EVENT_PROCESS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_DOWN) {
	    if ($M_HOST_SELECT_INDEX < (scalar @{$M_CONFIG{'host'}} - 1)) {
		$M_HOST_SELECT_INDEX += 1;
		$M_HOST_LIST_START += 1 if ($M_HOST_LIST_START + 3) <= $M_HOST_SELECT_INDEX;
	    }
	    draw_screen($usblcd);
	    return &main::M_EVENT_PROCESS;
	}
	elsif ($event_ref->{'value'} == USBLCD_KEY_OK) {
	    $M_HOST_INDEX = $M_HOST_SELECT_INDEX;

	    # setup host configuration
	    evt_map_update();

	    # reconnect
	    udphid_connect();

	    # reset statistics
	    $M_STAT_CODE = undef;
	    $M_STAT_REPORT = undef;
	    $M_STAT_USAGE = undef;
	    $M_STAT_EVENT_NAME = undef;

	    return &main::M_EVENT_PROCESS;
	}
    }
    elsif (exists $M_EVT_MODE_CHANGE{$key}) {
	my $mode_change_ref = $M_EVT_MODE_CHANGE{$key};
	my $mode_value = $mode_change_ref->{'value'};
	if ($mode_value =~ /^\d+$/) {
	    $M_DEVICE_MODE{$mode_change_ref->{'device'}} = $mode_value;
	} else {
	    $M_DEVICE_MODE{$mode_change_ref->{'device'}} += 1;
	}
	evt_map_update();

	$M_STAT_EVENT_NAME = "mode ".$M_DEVICE_MODE{$mode_change_ref->{'device'}};

	print "change mode for device $mode_change_ref->{'device'} on $M_DEVICE_MODE{$mode_change_ref->{'device'}}\n";

	return &main::M_EVENT_PROCESS;
    }
    elsif (exists $M_EVT_MAP{$key}) {
	my $evt_map_ref = $M_EVT_MAP{$key};
	my $report_name = $evt_map_ref->{'report'};
	my $usage = $evt_map_ref->{'usage'};

	print "found event, type $event_ref->{'type'}, value $event_ref->{'value'}, ".
	    "name \"$evt_map_ref->{'name'}\", report name \"$report_name\", usage \"$usage\"\n" if $M_VERBOSE;

	my @reports;
	if ($report_name eq "keyboard") {
	    $usage = ($usage =~ /^0x/) ? hex($usage) : int($usage);
	    push @reports, udphid_report_keyboard($usage);
	    push @reports, udphid_report_keyboard(0);
	}
	elsif ($report_name eq "mouse") {
	    if($usage =~ /^X(-?\d+)/) {
		push @reports, udphid_report_mouse(0, 0, 0, int($1), 0);
	    }
	    elsif($usage =~ /^Y(-?\d+)/) {
		push @reports, udphid_report_mouse(0, 0, 0, 0, int($1));
	    }
	    elsif($usage eq "B1") {
		push @reports, udphid_report_mouse(1, 0, 0, 0, 0);
		push @reports, udphid_report_mouse(0, 0, 0, 0, 0);
	    }
	    elsif($usage eq "B2") {
		push @reports, udphid_report_mouse(0, 1, 0, 0, 0);
		push @reports, udphid_report_mouse(0, 0, 0, 0, 0);
	    }
	    elsif($usage eq "B3") {
		push @reports, udphid_report_mouse(0, 0, 1, 0, 0);
		push @reports, udphid_report_mouse(0, 0, 0, 0, 0);
	    }
	}
	elsif ($report_name eq "consumer") {
	    $usage = ($usage =~ /^0x/) ? hex($usage) : int($usage);
	    push @reports, udphid_report_consumer($usage);
	    push @reports, udphid_report_consumer(0);
	}
	
	if (defined $M_UDPHID) {
	    foreach my $report (@reports) {
		my $ret = $M_UDPHID->report($report);
		if ($ret != 0) {
		    print "failed to send report to UDPHID server, drop event: $ret\n" if $M_VERBOSE;
		}
	    }
	}
	else {
	    print "UDPHID not connected, drop event\n" if $M_VERBOSE;
	}

	# update statistics
	$M_STAT_EVENT_NAME = $evt_map_ref->{'name'};
	$M_STAT_REPORT = $report_name;
        $M_STAT_USAGE = $usage;

	return &main::M_EVENT_PROCESS;
    }

    return &main::M_EVENT_DISCARD;
}


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

    $M_MEDIA_ALIVE -= 1 if $M_MEDIA_ALIVE > 0;

    draw_screen($usblcd);

    udphid_output_report();

}


1;
