#!/usr/bin/perl -w

use Gtk;
use CORBA::MICO ids => [ 'IDL:Tictactoe/Client:1.0' => undef ];

my $orb;

END {
    defined $ior_file and unlink $ior_file;
}

package MyClient;

use Error qw(:try);

@MyClient::ISA = qw(Tictactoe::Client);

sub new {
    my $class = shift;
    my $opponent = shift;
    my $self = bless {
		      tag => undef,
		      opponent => undef,
		      reset_wait => 0,
		      reset_ask => 0,
		      turn => 0,
		      win => 0
		     };

    for my $i (0..2) {
	for my $j (0..2) {
	    $self->{board}->[$i][$j] = 0;
	}
    }

    $self->make_ui;
    
    if (defined $opponent) {
	try {
	    $self->{tag} = $opponent->connect ($self);
	} catch Tictactoe::AlreadyConnected with {
	    die "Game is already in progress\n";
	}
	$self->{opponent} = $opponent;
	$self->set_status;
    }

    $self;
}

sub put {
    my ($self,$tag,$row,$column) = @_;

    if ($tag != $self->{tag}) {
	throw Tictactoe::BadTag;
    }
    if ($self->{turn} || ($self->{board}->[$row][$column] != 0)) {
	throw Tictactoe::InvalidMove;
    }

    $self->update ($row, $column, 2);
    $self->{turn} = 1;
    $self->set_status;
}

sub connect {
    my ($self, $opponent) = @_;
    
    if ($self->{opponent}) {
	throw Tictactoe::AlreadyConnected;
    }

    $self->{opponent} = $opponent;
    $self->{tag} = rand(2**31);		# badly "insecure" before 5.004

    $self->start_over;
    $self->set_status;

    return $self->{tag};
}

sub disconnect {
    my ($self, $tag) = @_;

    if ($tag != $self->{tag}) {
	throw Tictactoe::BadTag;
    }

    $self->{opponent} = undef;
    $self->{tag} = undef;
    $self->set_status;
}

sub request_reset {
    my ($self, $tag) = @_;

    if ($tag != $self->{tag}) {
	throw Tictactoe::BadTag;
    }

    if ($self->{reset_ask}) {
	return 0;
    } else {
	$self->{reset_ask} = 1;

	my $dialog = new Gtk::Dialog;
	my $label = new Gtk::Label "Start Over?";
	$label->set_padding (20, 20);
	$dialog->vbox->add ($label);
	$label->show;

	my $button;

	$button = new Gtk::Button "Yes";
	$dialog->action_area->add ($button);
	$button->signal_connect ("clicked", \&reset_action, $self, $dialog, 1);
	$button->show;

	$button = new Gtk::Button "No";
	$dialog->action_area->add ($button);
	$button->signal_connect ("clicked", \&reset_action, $self, $dialog, 0);
	$button->show;

	$dialog->signal_connect ("destroy", \&reset_action, $self, $dialog, 0);
	
	$dialog->show;

	return 1;
    }
}

sub reset_action
{
    my ($w, $self, $dialog, $ok) = @_;

    $self->{opponent}->reset($self->{tag}, $ok);
    $self->{reset_ask} = 0;

    if ($ok) {
	$self->start_over;
	$self->{turn} = 1;
	$self->set_status;
    }

    $dialog->destroy;
}

sub reset {
    my ($self, $tag, $ok) = @_;

    if ($tag != $self->{tag}) {
	throw Tictactoe::BadTag;
    }

    if ($self->{reset_wait}) {
	if ($ok) {
	    $self->start_over;
	    $self->{turn} = 0;
	    $self->set_status;
	}
	$self->{reset_wait} = 0;
    }
}

sub start_over {
    my $self = shift;

    $self->{turn} = 1;
    $self->{win} = 0;

    for my $i (0..2) {
	for my $j (0..2) {
	    $self->update ($i, $j, 0);
	}
    }
}

my @rwins = ( [ 0, 0, 0 ], [ 1, 1, 1 ], [ 2, 2, 2 ],
	      [ 0, 1, 2 ], [ 0, 1, 2 ], [ 0, 1, 2 ],
	      [ 0, 1, 2 ], [ 0, 1, 2 ] );
my @cwins = ( [ 0, 1, 2 ], [ 0, 1, 2 ], [ 0, 1, 2 ],
	      [ 0, 0, 0 ], [ 1, 1, 1 ], [ 2, 2, 2 ],
	      [ 0, 1, 2 ], [ 2, 1, 0 ] );

sub check_win {
    my $self = shift;

    for (my $k = 0; $k <= $#rwins ; $k++) {
	my $success = 1;

	my $player = 0;

	for (my $i = 0; $i < 3 ; $i++) {
	    my $t = $self->{board}->[$rwins[$k][$i]][$cwins[$k][$i]];
	    if (!$player) {
		$player = $t;
	    }
	    if (!$player || ($player != $t)) {
		$success = 0;
		last;
	    }
	}

	if ($success) {
	    $self->{win} = $player;
	    $self->set_status;
	    last;
	}
    }
}

sub update {
    my ($self, $i, $j, $val) = @_;

    $self->{board}->[$i][$j] = $val;
    $self->{squares}->[$i][$j]->set ($self->{pixmaps}->[$val], undef);

    $self->check_win;
}

sub set_status {
    my $self = shift;

    my $status;
    
    if (!$self->{opponent}) {
	$status = "Not connected";
    } elsif ($self->{win}) {
	$status = ($self->{win} == 1) ? "You won" : "Opponent won";
    } else {
	$status = $self->{turn} ? "Your turn" : "Opponent's turn"; 
    }

    $self->{statusbar}->pop($self->{context});
    $self->{statusbar}->push($self->{context}, $status);
}

sub make_ui {
    my $self = shift;
    
    my $window = new Gtk::Window 'toplevel';
    $window->set_policy (0, 0, 0);
    $window->realize;

    my $vbox = new Gtk::VBox 0, 0;
    $window->add ($vbox);
    $vbox->show;

    $self->{pixmaps}->[0] = 
	Gtk::Gdk::Pixmap->create_from_xpm ($window->window, 
					   undef, "empty.xpm");
    $self->{pixmaps}->[1] = 
	Gtk::Gdk::Pixmap->create_from_xpm ($window->window, 
					   undef, "self.xpm");
    $self->{pixmaps}->[2] = 
	Gtk::Gdk::Pixmap->create_from_xpm ($window->window, 
					   undef, "opponent.xpm");

    my $bbox = new Gtk::HButtonBox;
    $vbox->add ($bbox);
    $bbox->show;

    my $button = new Gtk::Button "Start Over";
    $bbox->add ($button);
    $button->signal_connect ("clicked", 
      sub {
	  if ($self->{opponent}) {
	      $self->{reset_wait} = 1;
	      $self->{opponent}->request_reset ($self->{tag});
	  }
      });
    
    $button->show;

    $button = new Gtk::Button "Quit";
    $bbox->add ($button);
    $button->show;

    $button->signal_connect ("clicked", 
      sub {
	  if ($self->{opponent}) {
	      $self->{opponent}->disconnect ($self->{tag});
	  }

	  $self->_boa->deactivate_obj ($self);
	  $self->_boa->dispose ($self);
	  $orb->shutdown (0);
      });

    my $separator = new Gtk::HSeparator;
    $vbox->add ($separator);
    $separator->show;
    
    my $table = new Gtk::Table (5, 5, 1);
    $vbox->add ($table);
    $table->show;

    for my $i (0..2) {
	for my $j (0..2) {
	    my $eventbox = new Gtk::EventBox;
	    $table->attach ($eventbox, $i+1, $i+2, $j+1, $j+2,
			    [], [], 0, 0);
	    $eventbox->set_events ('button-press-mask');

	    $eventbox->signal_connect ("button_press_event", 
	      sub {
		  if ($self->{turn} && !$self->{win} &&
		      ($self->{board}->[$i][$j] == 0)) {
		      $self->update ($i, $j, 1);
		      $self->{opponent}->put ($self->{tag}, $i, $j);
		      $self->{turn} = 0;
		      $self->set_status;
		  }
	      });
	    
	    $eventbox->show;
	    
	    $self->{squares}->[$i][$j] = new Gtk::Pixmap ($self->{pixmaps}->[0], undef);
	    $eventbox->add ($self->{squares}->[$i][$j]);
	    $self->{squares}->[$i][$j]->show;
	}
    }
    
    $self->{statusbar} = new Gtk::Statusbar;
    $self->{context} = $self->{statusbar}->get_context_id("game");
    $vbox->add ($self->{statusbar});
    $self->{statusbar}->show;

    $self->set_status;

    $window->show;
}

package main;

$orb = CORBA::ORB_init("mico-local-orb");
init Gtk;

$orb->dispatcher (new CORBA::MICO::GtkDispatcher);

my $boa = $orb->BOA_init("mico-local-boa");

my $opponent;
my $client;

if (open (IOR, "<tictactoe.ref")) {
    my $ior = <IOR>;
    close IOR;
    $opponent = $orb->string_to_object($ior);
    $client = new MyClient ($opponent);
} else {
    $client = new MyClient;
    my $ior = $orb->object_to_string ($client);

    $ior_file = "tictactoe.ref";

    open (OUT, ">$ior_file") || die "Cannot open file for ior: $!";
    print OUT "$ior";
    close OUT;
}

$boa->obj_is_ready ( $client, undef );
$orb->run;
