The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package SDLx::App;

use strict;
use warnings;
use vars qw($VERSION);

our $VERSION = '2.541_10';
$VERSION = eval $VERSION;

# SDL modules actually used here
use SDL            ();
use SDL::Video     ();
use SDL::Mouse     ();
use SDL::Event     ();
use SDL::Surface   ();
use SDL::VideoInfo ();
use SDLx::Validate ();
use base qw/SDLx::Surface SDLx::Controller/;

# SDL modules used for other reasons
# Please verify their usefulness here
use SDL::Rect        ();
use SDL::Events      ();
use SDL::PixelFormat ();

use Carp ();
use Scalar::Util qw/refaddr/;

my %_stash;
my $_screen_w;
my $_screen_h;
my $_screen_d;

$SDLx::App::USING_OPENGL = 0;

sub new {
	my $class = shift;

	my %o = @_;

	# undef is not a valid input
	my $w   = defined $o{width}    ? $o{width}    : defined $o{w}   ? $o{w}   : 640;
	my $h   = defined $o{height}   ? $o{height}   : defined $o{h}   ? $o{h}   : 480;
	my $d   = defined $o{depth}    ? $o{depth}    : defined $o{d}   ? $o{d}   : undef;
	my $f   = defined $o{flags}    ? $o{flags}    : defined $o{f}   ? $o{f}   : 0;
	my $pos = defined $o{position} ? $o{position} : defined $o{pos} ? $o{pos} : undef;
	my $ico = $o{icon};

	# undef is a valid input
	my $t    = $o{title};
	my $it   = $o{icon_title};
	my $init = exists $o{initialize} ? $o{initialize} : $o{init};
	my $s    = exists $o{stash} ? $o{stash} : {};
	my $icc  = $o{icon_color_key};

	# boolean
	my $sw = $o{software_surface} || $o{sw_surface} || $o{sw};
	my $hw = $o{hardware_surface} || $o{hw_surface} || $o{hw};
	my $ab   = $o{asynchronous_blit} || $o{async_blit};
	my $af   = $o{any_format};
	my $hwp  = $o{hardware_palette} || $o{hw_palette};
	my $db   = $o{double_buffer} || $o{double_buf} || $o{dbl_buf};
	my $fs   = $o{full_screen} || $o{fullscreen} || $o{full};
	my $gl   = $o{open_gl} || $o{opengl} || $o{gl};
	my $rs   = $o{resizable} || $o{resizeable};                   # it's a hard word to spell :-)
	my $nf   = $o{no_frame};
	my $ncur = $o{hide_cursor} || $o{no_cursor};
	my $cen  = $o{centered} || $o{center};
	my $gi   = $o{grab_input};
	my $nc   = $o{no_controller};

	unless ( defined $d ) {

		# specify SDL_ANYFORMAT flag if depth isn't defined
		$d  = 32;
		$af = 1;
	}

	# used to say unless no_init here. I don't think we need it anymore
	if ( !defined $init ) {
		SDLx::App->init(SDL::SDL_INIT_EVERYTHING);
	} else {
		if ( ref $init ) {
			push @$init, "video";
		} else {
			$init |= SDL::SDL_INIT_VIDEO;
		}
		SDLx::App->init($init);
	}

	# keep the screen's original res so we can set the app to that when we're done
	unless ( defined $_screen_w && defined $_screen_h && defined $_screen_d ) {
		my $video_info = SDL::Video::get_video_info();
		if ($video_info) {
			$_screen_w = $video_info->current_w;
			$_screen_h = $video_info->current_h;
			$_screen_d = $video_info->vfmt->BitsPerPixel;
		}
	}

	$f |= SDL::Video::SDL_SWSURFACE  if $sw;
	$f |= SDL::Video::SDL_HWSURFACE  if $hw;
	$f |= SDL::Video::SDL_ASYNCBLIT  if $ab;
	$f |= SDL::Video::SDL_ANYFORMAT  if $af;
	$f |= SDL::Video::SDL_HWPALETTE  if $hwp;
	$f |= SDL::Video::SDL_DOUBLEBUF  if $db;
	$f |= SDL::Video::SDL_FULLSCREEN if $fs;
	$f |= SDL::Video::SDL_OPENGL     if $gl;
	$f |= SDL::Video::SDL_RESIZABLE  if $rs;
	$f |= SDL::Video::SDL_NOFRAME    if $nf;

	# we'll let SDL decide which takes priority and set both if both are specified
	$ENV{SDL_VIDEO_CENTERED}   = $cen if $cen;
	$ENV{SDL_VIDEO_WINDOW_POS} = $pos if $pos;

	if ($gl) {
		$SDLx::App::USING_OPENGL = 1;

		my $r = defined $o{gl_red_size}   ? $o{gl_red_size}   : defined $o{gl_red}   ? $o{gl_red}   : 5;
		my $g = defined $o{gl_green_size} ? $o{gl_green_size} : defined $o{gl_green} ? $o{gl_green} : 5;
		my $b = defined $o{gl_blue_size}  ? $o{gl_blue_size}  : defined $o{gl_blue}  ? $o{gl_blue}  : 5;
		my $a = defined $o{gl_alpha_size} ? $o{gl_alpha_size} : defined $o{gl_alpha} ? $o{gl_alpha} : undef;
		my $ra =
			defined $o{gl_accum_red_size} ? $o{gl_accum_red_size} : defined $o{gl_accum_red} ? $o{gl_accum_red} : undef;
		my $ga =
			  defined $o{gl_accum_green_size} ? $o{gl_accum_green_size}
			: defined $o{gl_accum_green}      ? $o{gl_accum_green}
			:                                   undef;
		my $ba =
			  defined $o{gl_accum_blue_size} ? $o{gl_accum_blue_size}
			: defined $o{gl_accum_blue}      ? $o{gl_accum_blue}
			:                                  undef;
		my $aa =
			  defined $o{gl_accum_alpha_size} ? $o{gl_accum_alpha_size}
			: defined $o{gl_accum_alpha}      ? $o{gl_accum_alpha}
			:                                   undef;
		my $bs = defined $o{gl_buffer_size}  ? $o{gl_buffer_size}  : defined $o{gl_buffer}  ? $o{gl_buffer}  : undef;
		my $ss = defined $o{gl_stencil_size} ? $o{gl_stencil_size} : defined $o{gl_stencil} ? $o{gl_stencil} : undef;
		my $msb = defined $o{gl_multi_sample_buffers} ? $o{gl_multi_sample_buffers} : undef;
		my $mss = defined $o{gl_multi_sample_samples} ? $o{gl_multi_sample_samples} : undef;

		# boolean
		my $s  = $o{gl_stereo};
		my $sc = $o{gl_swap_control};
		my $av = $o{gl_accelerated_visual};

		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_RED_SIZE,   $r ) if defined $r;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_GREEN_SIZE, $g ) if defined $g;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_BLUE_SIZE,  $b ) if defined $b;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ALPHA_SIZE, $a ) if defined $a;

		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ACCUM_RED_SIZE,   $ra ) if defined $ra;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ACCUM_GREEN_SIZE, $ga ) if defined $ga;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ACCUM_BLUE_SIZE,  $ba ) if defined $ba;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ACCUM_ALPHA_SIZE, $aa ) if defined $aa;

		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_BUFFER_SIZE,  $bs ) if defined $bs;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_DOUBLEBUFFER, $db ) if defined $db;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_DEPTH_SIZE,   $d );
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_STENCIL_SIZE, $ss ) if defined $ss;

		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_STEREO,             $s )   if defined $s;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_MULTISAMPLEBUFFERS, $msb ) if defined $msb;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_MULTISAMPLESAMPLES, $mss ) if defined $mss;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_SWAP_CONTROL,       $sc )  if defined $sc;
		SDL::Video::GL_set_attribute( SDL::Video::SDL_GL_ACCELERATED_VISUAL, $av )  if defined $av;
	}

	# icon must be set before set_video_mode
	SDLx::App->icon( $ico, $icc ) if defined $ico;

	my $self = $class->set_video_mode( $w, $h, $d, $f );
	$self->SDLx::Controller::new(%o) unless $nc;

	$t = defined $it ? $it : $0 unless defined $t;
	$it = $t unless defined $it;
	$self->title( $t, $it );

	$self->show_cursor(0)  if $ncur;
	$self->grab_input($gi) if $gi;
	$self->stash = $s;

	$self;
}

sub set_video_mode {
	my ( $self, $w, $h, $d, $f ) = @_;
	my $surface = SDL::Video::set_video_mode( $w, $h, $d, $f )
		or Carp::confess( "set_video_mode failed: ", SDL::get_error() );
	$surface = SDLx::Surface->new( surface => $surface );

	# if we already have an app
	if ( ref $self ) {
		return $self;
	}
	return bless $surface, $self;
}

sub DESTROY {
	my ($self) = @_;

	# set original screen size when app ends
	if ( defined $_screen_w && defined $_screen_h && defined $_screen_d ) {
		SDL::Video::set_video_mode( $_screen_w, $_screen_h, $_screen_d, $self->flags );
	}

	my $ref = refaddr($self);
	delete $_stash{$ref};
}

sub stash : lvalue {
	$_stash{ refaddr( $_[0] ) };
}

sub init {
	my ( undef, $init ) = @_;

	if ( ref $init ) {

		# make a hash with keys of the values in the init array
		my %init = map { $_ => 1 } @$init;
		undef $init;

		$init |= SDL::SDL_INIT_TIMER       if $init{timer};
		$init |= SDL::SDL_INIT_AUDIO       if $init{audio};
		$init |= SDL::SDL_INIT_VIDEO       if $init{video};
		$init |= SDL::SDL_INIT_CDROM       if $init{cd_rom} || $init{cdrom};
		$init |= SDL::SDL_INIT_JOYSTICK    if $init{joystick};
		$init |= SDL::SDL_INIT_EVERYTHING  if $init{everything} || $init{all};
		$init |= SDL::SDL_INIT_NOPARACHUTE if $init{no_parachute};
		$init |= SDL::SDL_INIT_EVENTTHREAD if $init{event_thread};
	}

	# if anything is already inited, only init specified extra subsystems
	if ( SDL::was_init(SDL::SDL_INIT_EVERYTHING) ) {
		SDL::init_sub_system($init)
			and Carp::cluck( "SDL init_sub_system failed: ", SDL::get_error() );
	} else {
		SDL::init($init)
			and Carp::confess( "SDL init failed: ", SDL::get_error() );
	}
}

sub screen_size {
	SDLx::App->init(SDL::SDL_INIT_VIDEO);

	my $video_info = SDL::Video::get_video_info();

	return ( $video_info->current_w, $video_info->current_h, $video_info->vfmt->BitsPerPixel );
}

sub resize {
	my ( $self, $w, $h ) = @_;

	my $d = $self->format->BitsPerPixel;
	my $f = $self->flags;

	$self->set_video_mode( $w, $h, $d, $f );
}

sub title {
	my ( $self, $title, $icon_title ) = @_;
	my $video_info = SDL::Video::get_video_info();
	return if $video_info && !$video_info->wm_available();

	if ( @_ > 1 ) {
		my ( $t, $it ) = SDL::Video::wm_get_caption;
		$title      = $t  unless defined $title;
		$icon_title = $it unless defined $icon_title;
		return SDL::Video::wm_set_caption( $title, $icon_title );
	}
	SDL::Video::wm_get_caption;
}

sub icon {
	my ( undef, $icon, $color ) = @_;
	SDLx::App->init(SDL::SDL_INIT_VIDEO);
	unless ( UNIVERSAL::isa( $icon, "SDL::Surface" ) ) {
		$icon = SDL::Video::load_BMP($icon);
		$icon or Carp::confess( "Could not load_BMP icon '$icon': ", SDL::get_error() );
	}
	if ( defined $color ) {
		$color = SDLx::Validate::map_rgb( $color, $icon->format );
		SDL::Video::set_color_key( $icon, SDL::Video::SDL_SRCCOLORKEY, $color );
	}
	SDL::Video::wm_set_icon($icon);
}

sub error {
	shift;
	if ( @_ == 1 and !defined $_[0] ) {
		return SDL::clear_error;
	}
	if (@_) {
		return SDL::set_error_real(@_);
	}
	SDL::get_error;
}

sub warp_cursor {
	my ( undef, $x, $y ) = @_;
	SDL::Mouse::warp_mouse( $x || 0, $y || 0 );
}

sub show_cursor {
	my ( undef, $show ) = @_;
	if ( @_ > 1 ) {
		return SDL::Mouse::show_cursor(SDL::Event::SDL_ENABLE) if $show and $show ne SDL::Event::SDL_QUERY;
		return SDL::Mouse::show_cursor(SDL::Event::SDL_DISABLE) unless $show;
	}
	SDL::Mouse::show_cursor(SDL::Event::SDL_QUERY);
}

sub fullscreen {
	my ($self) = @_;
	return 1 if SDL::Video::wm_toggle_fullscreen($self);

	# fallback to doing it with set_video_mode()
	my $d = $self->format->BitsPerPixel;
	my $f = $self->flags;

	eval { $self->set_video_mode( 0, 0, $d, $f ^ SDL::Video::SDL_FULLSCREEN ) };
	return 1 unless $@;

	# failed going fullscreen, let's revert back
	$self->set_video_mode( 0, 0, $d, $f );

	# report failure to go fullscreen
	return 0;
}

sub iconify {
	SDL::Video::wm_iconify_window;
}

sub grab_input {
	my ( undef, $grab ) = @_;
	if ( @_ > 1 ) {
		return SDL::Video::wm_grab_input(SDL::Video::SDL_GRAB_ON) if $grab and $grab ne SDL::Video::SDL_GRAB_QUERY;
		return SDL::Video::wm_grab_input(SDL::Video::SDL_GRAB_OFF) unless $grab;
	}
	SDL::Video::wm_grab_input(SDL::Video::SDL_GRAB_QUERY);
}

sub sync {
	my ($self) = @_;
	if ($SDLx::App::USING_OPENGL) {
		return SDL::Video::GL_swap_buffers;
	}
	SDL::Video::flip($self);
}

sub gl_attribute {
	my ( undef, $mode, $value ) = @_;

	return unless $SDLx::App::USING_OPENGL;
	if ( @_ > 2 ) {
		return SDL::Video::GL_set_attribute( $mode, $value );
	}
	my $returns = SDL::Video::GL_get_attribute($mode);
	Carp::cluck("SDL::Video::GL_get_attribute failed to get GL attribute")
		if $returns->[0] < 0;
	$returns->[1];
}

1;