use strict;
package Games::Roguelike::Console::Win32;

#### refer to Games::Roguelike::Console for docs ###

use Win32::Console;
use Carp;

use base 'Games::Roguelike::Console';

our $VERSION = '0.4.' . [qw$Revision: 232 $]->[1];

sub new {
        my $pkg = shift;
        croak "usage: Games::Roguelike::Console::Win32->new()" unless $pkg;

        my $self = bless {}, $pkg;
        $self->init(@_);
        return $self;
}

my $CON;

#todo: figure out how to free/alloc/resize
sub init {
	my $self = shift;
	my %opts = @_;

	$self->SUPER::init(%opts);	

	$self->{conin} = Win32::Console->new(STD_INPUT_HANDLE);

	# turns off echo
	$self->{conin}->Mode(ENABLE_PROCESSED_INPUT);
		
	$self->{buf} = Win32::Console->new(GENERIC_READ|GENERIC_WRITE);
	$self->{buf}->Cls();
	$self->{buf}->Cursor(-1,-1,-1,0);
	
	$self->{con} = Win32::Console->new(STD_OUTPUT_HANDLE);
	$self->{cur} = 0;

	($self->{winx},$self->{winy}) = $self->{con}->MaxWindow();
	$self->{con}->Size($self->{winx}, $self->{winy});
	$self->{buf}->Size($self->{winx}, $self->{winy});

	$self->{rx} = 0 if !defined $self->{rx};
	
	if (!$opts{noinit}) {
		$self->{con}->Cursor(-1,-1,-1,0);
		$self->{con}->Display();
		$self->{con}->Cls();
	}
	
	$CON = $self->{con} unless $CON;
	
	$SIG{INT} = \&sig_int_handler;
	$SIG{__DIE__} = \&sig_die_handler;
}

sub DESTROY {
	$_[0]->{con}->Cls() if $_[0]->{con};
}

sub sig_int_handler {
	$CON->Cls();
	exit;
}

sub sig_die_handler {
	die @_ if $^S;
        $CON->Cls();
	die @_;
}

sub nativecolor {
        my ($self, $fg, $bg, $fgb, $bgb) = @_;

#	$fg = 'white' if $fg eq '';
#	$bg = 'black' if $bg eq '';

	$fg = 'light' . $fg if $fgb;

	$fg = 'gray' if $fg eq 'lightblack';
	$bg = 'gray' if $bg eq 'lightblack';
	$fg = 'brown' if $fg eq 'yellow';
	$bg = 'brown' if $bg eq 'yellow';
	$fg = 'yellow' if $fg eq 'lightyellow';
	$bg = 'yellow' if $bg eq 'lightyellow';
	$fg = 'lightgray' if $fg eq 'white';
	$fg = 'white' if $fg eq 'lightwhite';
	$bg = 'white' if $bg eq 'lightwhite';

	no strict 'refs';
	my $color = ${"FG_" . uc($fg)} | ${"BG_" . uc($bg)} ;
		
	use strict 'refs';

	$color = $self->defcolor if !$color;
	return $color;
}

sub attron {
        my $self = shift;
        my ($attr) = @_;
        $self->{cattr} = $self->parsecolor($attr);
}

sub attroff {
	my $self = shift;
	$self->{cattr} = $self->defcolor;
}

sub addstr {
	my $self = shift;
	my $str =  pop @_;

	if (@_== 0) {
		if ($self->{cx}+length($str) > ($self->{winx}+1)) {
			$str = substr(0, ($self->{cx}+length($str)) - ($self->{winx}));
		}
		return if length($str) == 0;
		$self->{buf}->WriteChar($str, $self->{cx}, $self->{cy});
		$self->{buf}->WriteAttr(chr($self->{cattr}) x length($str), $self->{cx}, $self->{cy});
		$self->invalidate($self->{cx}, $self->{cy}, $self->{cx} + length($str), $self->{cy});
		$self->{cx} += length($str);
	} elsif (@_==2) {
		my ($y, $x) = @_;
		if ($x+length($str) > ($self->{winx}+1)) {
			$str = substr(0, ($x+length($str)) - ($self->{winx}));
		}
		return if length($str) == 0;
		$self->{buf}->WriteChar($str, $x, $y);
		$self->{buf}->WriteAttr(chr($self->{cattr}) x length($str), $x, $y);
		$self->invalidate($x, $y, $x+length($str), $y);
		$self->{cx} = $x + length($str);
		$self->{cy} = $y;
	}
	if ($self->{cursor}) {
		$self->{con}->Cursor($self->{cx},$self->{cy},-1,1);		
	}
}

sub tagstr {
        my $self = shift;
        my ($y, $x, $str);
        if (@_ == 1) {
                ($y, $x, $str) = ($self->{cy}, $self->{cx}, @_);
        } else {
                ($y, $x, $str) = @_;
        }
        my $attr = chr($self->defcolor);
        my $r = $x;
        my $c;
        for (my $i = 0; $i < length($str); ++$i) {
                $c = substr($str,$i,1);
                if ($c eq '<') {
                        substr($str,$i) =~ s/<([^>]*)>//;
			if ($1 eq 'gt') {
				$c = '>';
				--$i;
			} elsif ($1 eq 'lt') {
				$c = '<';
				--$i;
                        } else {
				$attr = chr($self->parsecolor($1));
                        	$c = substr($str,$i,1);
			}
                }
		if ($c eq "\r") {
			next;
		}
		if ($c eq "\n") {
			$r = $self->{rx};
			$y++;
			next;
		}

                $self->{buf}->WriteChar($c, $r, $y);
                $self->{buf}->WriteAttr($attr, $r, $y);
                ++$r;
        }
        $self->invalidate($x, $y, $x+$r, $y);
        $self->{cy}=$y;
        $self->{cx}=$x+$r;
}

sub refresh {
	my $self = shift;
	#my $rect = $self->{buf}->ReadRect($self->{invl}, $self->{invt}, $self->{invr}, $self->{invb});
	#$self->{con}->WriteRect($rect, $self->{invl}, $self->{invt}, $self->{invr}, $self->{invb});
	my $rect = $self->{buf}->ReadRect(0, 0, $self->{winx}, $self->{winy});
	$self->{con}->WriteRect($rect, 0, 0, $self->{winx}, $self->{winy});
	$self->{invl} = $self->{winx}+1;
	$self->{invt} = $self->{winy}+1;
	$self->{invr} = $self->{invb} = -1;
}

sub move {
	my $self = shift;
	my ($y, $x) = @_;
	$self->{cx}=$x;
	$self->{cy}=$y;
	if ($self->{cursor}) {
		$self->{con}->Cursor($x,$y,-1,1);		
	}
}

sub cursor {
	my $self = shift;
	if ($self->{cursor} != shift) {
		$self->{cursor} = !$self->{cursor};
		$self->{con}->Cursor($self->{cx},$self->{cy},-1,$self->{cursor});
	}
}

sub printw   { 
	my $self = shift;
	$self->addstr(sprintf shift, @_)
} 

sub addch {
	my $self = shift;
	$self->addstr(@_);
}

sub invalidate {
	my $self = shift;
	my ($l, $t, $r, $b) = @_;
	$r = 0 if ($r < 0);
	$t = 0 if ($t < 0);
	$b = $self->{winy} if ($b > $self->{winy});
	$r = $self->{winx} if ($r > $self->{winx});

	if ($r < $l) {
		my $m = $r;
		$r = $l;
		$l = $m;
	}
	if ($b < $t) {
		my $m = $t;
		$b = $t;
		$t = $m;
	}
	$self->{invl} = $l if $l < $self->{invl};
	$self->{invr} = $r if $r > $self->{invr};
	$self->{invt} = $t if $t < $self->{invt};
	$self->{invb} = $b if $b > $self->{invb};
}

# read 1 event, translate and return translated value
sub getev {
        my $self = shift;
	my ($type, @e)= $self->{conin}->Input();
	if ($type == 1) {
		my ($kd, $rep, $vk, $vs, $c, $ctrl) = @e;
		next if $kd;
		return 'DOWN' if $vk == 0x28;
		return 'RIGHT' if $vk == 0x27;
		return 'LEFT' if $vk == 0x25;
		return 'UP' if $vk == 0x26;
		return 'ESC' if $c == 27;
		return chr($c) if $c > 0;
	}
	return undef;
}

# todo, support win32 arrow/function/control keys - ReadKey ignores them
sub getch {
        my $self = shift;
	# readkey breaks on carraige returns
	while (1) {
		my $c = $self->getev();
		return $c if defined $c;
	};
}

sub nbgetch {
        my $self = shift;
	# readkey breaks on carraige returns
	while ($self->{conin}->GetEvents() > 0) {
		my $c = $self->getev();
		return $c if defined $c;
	};
	return undef;
}

1;
