The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

package Net::OICQ::Win32Client;
use strict;
use warnings;
use Net::OICQ::TextConsole;
use Win32::GUI ();
use Data::Dumper;

our @ISA = qw(Net::OICQ::TextConsole);

our $ModInfo = "Net::OICQ v$Net::OICQ::VERSION Crypt::OICQ v$Crypt::OICQ::VERSION";

sub new {
	my ($class, $oicq) = @_;
	defined $oicq or $oicq = new Net::OICQ;
	my $main = create_main(600, 600);
	my $self = {
		OICQ => $oicq,
		Main => $main,
		ChatWindow => {},
	};
	#print Data::Dumper->Dump([$main]);
	return bless($self, $class);
}

sub create_main {
	my ($gui_w, $gui_h) = @_;

	my $gui = Win32::GUI::Window->new(-name => 'Main', -title => $ModInfo,
		-size => [$gui_w, $gui_h]);

	$gui->AddLabel(-name => "MyInfo", -title => "", -pos => [0, 0], -size => [$gui_w -8, 20]);

	$gui->AddTextfield(-name => "Input", -readonly => 0, -default => 1,
			-pos => [0, $gui_h - 80], -size => [$gui_w - 8, 80],
			-multiline => 1, -autohscroll => 1, -autovscroll => 1);

	$gui->AddTextfield(-name => "Output", -readonly => 1,
			-pos => [0, 20], -size => [$gui_w - 8, $gui_h - 100],
			-multiline => 1, -autohscroll => 1, -autovscroll => 1);

	$gui->{Input}->SetFocus();
	$gui->Center();
	return $gui;
}

sub set_myinfo {
	my ($self, $text) = @_;
	$self->{Main}->{MyInfo}->Text($text);
}

sub get_chat_window {
	my ($self, $qq, $group) = @_;
	die "Bad arg for get_chat_window: $qq\n" unless $qq =~ /^\d+$/;
	my $cw = $self->{ChatWindow};
	my $winid = $group ? $group : $qq;
	return $cw->{$winid} if exists $cw->{$winid};

	my $dt = Win32::GUI::GetDesktopWindow();
	my $dtw = Win32::GUI::Width($dt);
	my $dth = Win32::GUI::Height($dt);
	my $win_l = sprintf '%.0f', (($winid % 2) ? $dtw/2 : 0) + rand(50);
	my $win_t = sprintf '%.0f', (length($winid)-5)*10+rand(10);
	my $oicq = $self->{OICQ};
	my $myid = $oicq->{Id};
	my ($width, $height) = (500, 500);
	# Note: the word "group" in the window title will be used to check whether it is
	# a chat window for a group
	my $chatwin = Win32::GUI::Window->new(-name => 'Chat',
		-title => $group ? "$myid chat in group $group" : "$myid chat with $qq",
		-pos => [$win_l, $win_t], -size => [$width, $height]);
	$chatwin->AddLabel(-name => "ChatInfo",
		-title => $group ? "$myid -> group $group" : "$myid -> $qq",
	       	-pos => [0, 0], -size => [$width -8, 20]);
	$chatwin->AddTextfield(-name => "ChatOutput", -readonly => 1,
		-pos => [0, 20], -size => [$width - 8, $height -120],
		-multiline => 1, -autohscroll => 1, -autovscroll => 1);

	$chatwin->AddTextfield(-name => "ChatInput", -readonly => 0, -default => 1,
		-pos => [0, $height - 100], -size => [$width - 50, 80],
		-multiline => 1, -autohscroll => 1, -autovscroll => 1);

	$chatwin->AddButton(-name => "ChatCancelButton", -title => 'Cancel', -cancel => 1,
		-pos => [$width - 48, $height - 80], -size => [40, 20]);

	$cw->{$winid} = $chatwin;
	return $chatwin;
}

sub info {
	my ($self, @text) = @_;
	my $output = $self->{Main}->{Output};
	$output->Append(join('', map {s/([^\r]?)\n/$1\r\n/g; $_} @text));
}

sub warn {
	my ($self, @text) = @_;
	$self->info("Warning:\n", @text);
}

sub error {
	my ($self, @text) = @_;
	$self->info("Error:\n", @text);
}

sub mesg {
	my ($self, $time, $group, $srcid, $text, $font) = @_;
	my $win = $self->get_chat_window($srcid, $group);
	return 0 unless defined $win;
	$win->{ChatInput}->SetFocus();
	$win->Show();
	my $output = $win->{ChatOutput};
	unless (defined $time) {
		$output->Append(substr(localtime, 11, 9) . "$srcid\r\n$text\r\n");
		if ($font) {
			$output->Append("$font\r\n");
		}
		return;
	}
	my $oicq = $self->{OICQ};
	my $nick = $oicq->get_nickname($srcid);
	my $id_color = $self->id_color($srcid);

	my $srcinfo = $oicq->{Info}->{$srcid};
	my $addr = $srcinfo->{Addr} || 'unknown';
	my $ver  = defined $srcinfo->{Client} ? "0x$srcinfo->{Client}" : 'unknown';

	$output->Append(substr(localtime($time), 11, 9) . ($group ? "Group $group " : " ") .
		"$nick($srcid, IP $addr, version $ver)\r\n$text\r\n");
	if ($font) {
		$output->Append("$font\r\n");
	}
	return;
}

sub prompt {}

sub loop {
	my ($self) = @_;
	my $oicq = $self->{OICQ};
	my $select = new IO::Select($oicq->{Socket});
	my $select_t = 0.1;
	my $noack = 0;
  LOOP: while(1) { #Win32::GUI::DoEvents != -1) {
	  	$oicq->keepalive if time - $oicq->{LastKeepaliveTime} >= 60;
		if (time - $oicq->{LastSvrAck} > 120) {
			unless ($noack) {
				$self->warn(substr(localtime(), 11, 9) . "no response from server for > 2 minutes\r\n");
				$noack = 1;
			}
		} else {
			if ($noack) {
				$noack = 0;
				$self->info(substr(localtime(), 11, 9) . "got server response\r\n");
			}
		}
		Win32::GUI::DoEvents();
		my ($socket) = $select->can_read($select_t);
		next LOOP unless defined $socket;
		my $packet;
		$socket->recv($packet, 0x4000);
		foreach my $data ($oicq->get_data($packet)) {
			my $event = new Net::OICQ::ServerEvent($data, $oicq);
			next unless defined($event) && defined($event->{Data});
			$event->parse;
			my $cmd = "ui_".$event->cmd;
			eval {$self->$cmd($event)};
			if ($@) {
				$self->error($@);
			}
		}
	}
}

sub ui_keep_alive {
	my ($self, $event) = @_;
	$self->info(substr(localtime(), 11, 9) .join(', ', @{$event->{ServerInfo}}) . "\r\n");
}

# Handle key stroke event for Textfield input
# Do something for two consecutive ENTER keys

sub text_input_handler {
	my ($self, $input, $key, $callback, @cb_args) = @_;
	return 1 unless $key == 0x0d;
	my $line0 = $input->GetLine(0);
	return 0 if !defined($line0) or $line0 eq "";
	my $line_count = $input->GetLineCount();
	return 1 unless $line_count > 2;
	my $last_input = $input->GetLine($line_count - 2);
	#print "line_count=$line_count   last line len=", length($last_input), "\n";
	if (!defined($last_input) or length($last_input) == 0) {
		my $text = "";
		for (my $i = 0; $i < $line_count; $i++) {
			my $line = $input->GetLine($i);
			next unless defined $line;
			$text .= $line . "\r\n";
		}
		return 1 unless $text =~ /\S/s;
		$input->SelectAll();
		$input->ReplaceSel('');
		return $callback->($self, $text, @cb_args);
	} else {
		return 1;
	}
}






package main;
import Net::OICQ::Win32Client;

my ($QQ, $PW, $Mode, $Proto, $Proxy);
my $Login = create_login_window();
Win32::GUI::Dialog();

my $GUI = new Net::OICQ::Win32Client;
$GUI->set_myinfo("Not logged in.");
$GUI->{Main}->Show();

our $oicq = $GUI->{OICQ};

if ($QQ and $PW) {
	$oicq->{Debug} = 9;
	if ($oicq->login($QQ, $PW, $Mode, $Proto, $Proxy)) {
		$GUI->set_myinfo("QQ $QQ $Mode mode, $Proto connection, " . ($Proxy ? "Proxy $Proxy" : "no proxy"));
		$oicq->get_friends_list;
		$oicq->get_online_friends;
		$oicq->get_user_info($QQ);
	}
	$GUI->loop;
} else {
	Win32::GUI::Dialog();
}

exit;

sub create_login_window {
	use integer;
	my ($login_w, $login_h) = (360, 210);

	my $login = Win32::GUI::DialogBox->new(-name => 'Login',
		-title => "$Net::OICQ::Win32Client::ModInfo login",
		-size => [$login_w, $login_h]);
	my ($x, $y) = (10, 20);
	$login->AddLabel(-name => "QQLabel", -title => 'QQ:', -align => 'right',
			-pos => [$x, $y], -size => [100, 20]);
	$login->AddTextfield(-name => "QQ", -readonly => 0, -number => 1,
			-pos => [$x + 100, $y], -size => [100, 20], -tabstop => 1);
	$y += 30;
	$login->AddLabel(-name => "PWLabel", -title => 'PW:', -align => 'right',
			-pos => [$x, $y], -size => [100, 20]);
	$login->AddTextfield(-name => "PW", -readonly => 0, -password => 1,
			-pos => [$x + 100, $y], -size => [100, 20], -tabstop => 1);
	$y += 30;
	$login->AddLabel(-name => "InvLabel", -title => 'Invisible:', -align => 'right',
			-pos => [$x, $y], -size => [100, 20]);
	$login->AddCheckbox(-name => "Inv", -pos => [$x + 100, $y], -size => [10, 10], -tabstop => 1);
	$login->AddLabel(-name => "UDLabel", -title => 'UDP:', -align => 'right',
			-pos => [$x + 150, $y], -size => [50, 20]);
	$login->AddCheckbox(-name => "UDP", -pos => [$x + 200, $y], -size => [10, 10], -tabstop => 1);
	$y += 20;
	$login->AddLabel(-name => "ProxyLabel", -title => 'Proxy:', -align => 'right',
			-pos => [$x, $y], -size => [100, 20]);
	$login->AddTextfield(-name => "Proxy", -readonly => 0,
			-pos => [$x + 100, $y], -size => [150, 20], -tabstop => 1);
	$y += 40;
	$login->AddButton(-name => "LoginButton", -title => 'Login', -default => 1, -ok => 1,
			-pos => [$x + 100, $y], -size => [40, 20], -tabstop => 1);
	$login->AddButton(-name => "CancelButton", -title => 'Hide', -cancel => 1,
			-pos => [$x + 200, $y], -size => [40, 20], -tabstop => 1);
	$login->Center();
	$login->{Inv}->Click();
	$login->{QQ}->SetFocus();
	$login->Show();
	return $login;
}

sub UDP_Click {
	if ($Login->{UDP}->Checked) {
		$Login->{Proxy}->Disable;
	} else {
		$Login->{Proxy}->Enable;
	}
	return 1;
}

sub LoginButton_Click {
	my $qq = $Login->{QQ}->GetLine(0);
       	my $pw = $Login->{PW}->GetLine(0);
	if (defined($qq) and defined($pw) and $qq =~ /^\d{5,10}$/ and $pw) {
		$QQ = $qq;
		$PW = $pw;
		$Mode = $Login->{Inv}->Checked() ? 'Invisible' : 'Normal';
		if ($Login->{UDP}->Checked()) {
			$Proto = 'udp';
			$Proxy = '';
		} else {
			$Proto = 'tcp';
			$Proxy = $Login->{Proxy}->GetLine(0);
			$Proxy = '' unless defined $Proxy;
		}
		print "Mode: $Mode Proto: $Proto Proxy: $Proxy\n";
		$Login->Hide();
		return -1;
	}
	return 0;
}

sub CancelButton_Click {
	$QQ = undef;
	$PW = undef;
	$Login->Hide;
	return -1;
}

sub ChatCancelButton_Click {
	my ($qq, $win) = get_active_chat_window();
	my $chatwin = $GUI->{ChatWindow};
	return 0 unless defined($qq) and exists($chatwin->{$qq});
	$chatwin->{$qq} = undef;
	delete($chatwin->{$qq});
	$GUI->info("Chat window $qq deleted\r\n");
	return 1;
}

sub Input_KeyUp {
	my ($time, $key) = @_;
	#print "$key\n";
	return $GUI->text_input_handler($GUI->{Main}->{Input}, $key,
		sub {
			my ($gui, $text) = @_;
			exit if $text =~ /^\s*(:?exit|quit)\s*$/;
			if ($text =~ /^(\d{4,10})\s*$/) {
				my $qq = $1;
				my $win = $gui->get_chat_window($qq);
				$win->Show() if defined $win;
				return 1;
			}
			my $text2 = $text;
			$text2 =~ s/^\s+//s;
			$text2 =~ s/\s+$//s;
			my ($cmd, @args) = split(/\s+/, $text2);
			$cmd =~ s/\W//;
			my $cmd_info = $gui->kb_cmd($cmd);
			if (defined($cmd_info)) {
				if (@args < $cmd_info->[1]) {
					$gui->error("Not enough argument for command $cmd\r\n");
				} else {
					eval { $cmd_info->[0]->($gui, @args) };
					$@ && $gui->error("$@");
				}
			} else {
				my $output = $gui->{Main}->{Output};
				$output->Append("eval $text\r\n");
				my $res = eval $text;
				$@ && $gui->error("$@\r\n");
				if ($res) {$res =~ s/\n/\r\n/sg; $gui->info("$res\r\n")}
			}
			return 1;
		});
}

sub ChatInput_KeyUp {
	my ($time, $key) = @_;
	return 1 unless $key == 0x0d;
	my ($winid, $win) = get_active_chat_window();
	return 1 unless defined $win;
	my $input = $win->{ChatInput};
	return $GUI->text_input_handler($input, $key,
		sub {my ($gui, $text, $winid, $win) = @_;
			my $oicq = $gui->{OICQ};
			my $output = $win->{ChatOutput};
			my $nick = $oicq->get_nickname($oicq->{Id});
			$output->Append(substr(localtime, 11, 9) . "$nick\r\n$text\r\n");
			if ($win->Text =~ /group/) {
				$oicq->send_group_msg($winid, $text);
			} else {
				$oicq->send_msg($winid, $text);
			}
			return 1;
		},
		$winid, $win);
}

sub get_active_chat_window {
	my $handle = Win32::GUI->GetActiveWindow();
	my $chatwin = $GUI->{ChatWindow};
	foreach my $winid (keys %$chatwin) {
		if ($chatwin->{$winid}->{'-handle'} eq $handle) {
			return ($winid, $chatwin->{$winid});
		}
	}
	return;
}

sub Login_Terminate {
	return -1;
}

sub Chat_Terminate {
	ChatCancelButton_Click();
	return 1;
}