##########################################################################
# This file is part of Vacuum Magic
# Copyright (C) 2008 by UPi <upi at sourceforge.net>
##########################################################################

use strict;
our ($Level, $ScreenWidth, $ScreenHeight, $ScoreFont);

##########################################################################
package GameTimer;
##########################################################################

use vars qw($FirstTick $LastTick $TotalAdvances $LastFpsTick $LastFps $Fps);

sub ResetTimer {
  $FirstTick = $::App->ticks;
  $LastTick = $LastFpsTick = $FirstTick;
  $TotalAdvances = 0;
  $Fps = $LastFps = 0;
}

sub GetAdvances {
  my ($ticks, $advance);

  $ticks = $::App->ticks;
  $advance = int(($ticks - $FirstTick) / 10) - $TotalAdvances;
  $TotalAdvances += $advance;
  
  # Calculate frames per second;
  ++$Fps if $advance > 0;
  if ($ticks - $LastFpsTick > 1000) {
    $LastFps = $Fps;
    $LastFpsTick = $ticks;
    $Fps = 0;
  }
  
  return $advance;
}

sub GetFramesPerSecond {
  return $LastFps;
}


##########################################################################
package FpsIndicator;
##########################################################################

use vars qw( $IndicatorX $IndicatorY );

sub Init {
  my ($width, $height);
  
  $width = $ScoreFont->TextWidth('99:999');
  $height = $ScoreFont->char_height;
  $IndicatorX = $ScreenWidth - $width - 5;
  $IndicatorY = $ScreenHeight - $height - 5;
}

sub Draw {
  my $self = shift;
  
  ::glColor(1,1,0,1);
  $ScoreFont->pre_output;
  $ScoreFont->output( $IndicatorX, $IndicatorY, scalar(@::GameObjects) . ':' . &GameTimer::GetFramesPerSecond() );
  $ScoreFont->post_output;
  ::glColor(1,1,1,1);
}





package GameBase;
package NormalGame;
package Menu;
package MenuItem;


##########################################################################
package GameBase;
##########################################################################

use SDL::OpenGL;

sub new {
  my ($class) = @_;
  my $self = {
    'abortgame' => 0,
    'anim' => 0,
    'preAdvanceActions' => [],
    'postAdvanceActions' => [],
    'level' => 0,
  };
  $::GamePause = 0;
  bless $self, $class;
}

sub Exit {
  exit;
}

sub Rand {
  if ($::RecorderOn) {
    return &::AddRandToRecorder( int(rand($_[1])) );
  }
  return int(rand($_[1]));
}

sub SetMusic {
  shift;
  &::SetMusic(@_);
}

sub FadeMusic {
  shift;
  &::FadeMusic(@_);
}

sub PlaySound {
  my ($self, $sound) = @_;
  
  $self->{sounds}->{$sound} = 1;
}

sub AdvanceSounds {
  my ($self) = @_;
  
  foreach (keys %{$self->{sounds}}) {
    &::PlaySound($_);
  }
  $self->{sounds} = {};
}

sub FadeInBackground {
  shift;
  &::FadeInBackground(@_);
}

sub FadeOutBackground {
  shift;
  &::FadeOutBackground(@_);
}

sub SetBackground {
  shift;
  &::SetBackground(@_);
}

sub NewMenuItem {
  shift;
  new MenuItem(@_);
}

sub Delay {
  my ($self, $ticks) = @_;
  
  while ($ticks > 0) {
    my $advance = $self->CalculateAdvances();
    %::Events = ();
    &::HandleEvents();
    return if $self->{abortgame};
    $ticks -= $advance;
    $self->DrawGame();
  }
}

sub Fade {
  my ($self, $ticks) = @_;
  my ($baseTicks);
  
  $self->{abortgame} = 0;
  $baseTicks = $ticks = $ticks || 100;
  &::FadeMusic($ticks * 10);
  
  while ($ticks > 0) {
    my $advance = $self->CalculateAdvances();
    %::Events = ();
    &::HandleEvents();
    return if $self->{abortgame};
    
    $ticks -= $advance;
    $self->DrawGame();
    glColor(0,0,0, 1 - $ticks / $baseTicks);
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);
      glVertex(0,0); glVertex($ScreenWidth, 0);
      glVertex($ScreenWidth, $ScreenHeight); glVertex(0, $ScreenHeight);
    glEnd();
    glEnable(GL_TEXTURE_2D);
    $::App->sync();
  }
}

sub ShowTooltip {
}

sub ResetGame {
  my $self = shift;

  $::Difficulty = 6;
  @::GameObjects = ();
  @Guy::Guys = ();
  @Slurp::Slurps = ();
  @Koules::Koules = ();
  $Ball::Balls = 0;
  $PangZeroBoss::Boss = undef;
  %::GameEvents = ();
}

sub CalculateAdvances {
  my $advance = &GameTimer::GetAdvances();
  while ($advance <= 0) {
    $::App->delay(3); # Wait 3ms = 0.3 game ticks
    $advance = &GameTimer::GetAdvances();
  }
  if ($advance > 5) {
    $advance = 5  unless $::TimePrecisePlayback;
  }
  return $advance;
}

sub SetSpecialProjection {
  my ($self, $specialProjection) = @_;
  
  if ($specialProjection) {
    $self->{specialProjection} = $specialProjection;
  } else {
    delete $self->{specialProjection};
  }
}

sub SetTimeEffect {
  my ($self, $timeEffect, $enemyStop) = @_;
  
  $self->{timeEffect} = $timeEffect;
  $self->{enemyStop} = $enemyStop;
}

sub ExpireTimeEffect {
  my ($self) = @_;
  $self->{timeEffect}->Expire()  if $self->{timeEffect};
}

sub DrawGame {
  my ($self) = @_;
  my ($gameObject);

  &ResetProjection();
  glLoadIdentity();
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glColor(1,1,1);
  
  $self->{specialProjection}->SpecialProjection()  if $self->{specialProjection};
  &::DrawBackground();
  
  &ResetProjection()  if $self->{specialProjection};
  $self->DrawScoreBoard();
  &FpsIndicator::Draw()  if $::FpsIndicator;
  
  $self->{specialProjection}->SpecialProjection()  if $self->{specialProjection};
  foreach $gameObject (@::GameObjects) {
    $gameObject->Draw();
    # &DrawCollisionAreas($gameObject);
  }
  if ($self->{timeEffect}) {
    glDisable(GL_TEXTURE_2D);
      glColor( 0.5, 0.5, 0.8, (100 - $self->{timeEffect}->{speed}) / 120);
      glBegin(GL_QUADS);
        glVertex(0, 0); glVertex(0, $ScreenHeight);
        glVertex($ScreenWidth, $ScreenHeight); glVertex($ScreenWidth, 0);
      glEnd();
      glColor(1, 1, 1, 1);
    glEnable(GL_TEXTURE_2D);
  }
}

sub DrawCollisionAreas {
  my ($gameObject) = @_;
  foreach ($gameObject->{collisionPieces} ? @{$gameObject->{collisionPieces}} : $gameObject) {
    next  unless defined $_->{collisionmarginw1};
    glDisable(GL_TEXTURE_2D);
    glColor( 1, 0, 0, 0.3);
    glBegin(GL_QUADS);
      glVertex($gameObject->{x} + $_->{collisionmarginw1}, $gameObject->{y} + $_->{collisionmarginh1});
      glVertex($gameObject->{x} + $_->{collisionmarginw1}, $gameObject->{y} + $_->{collisionmarginh2});
      glVertex($gameObject->{x} + $_->{collisionmarginw2}, $gameObject->{y} + $_->{collisionmarginh2});
      glVertex($gameObject->{x} + $_->{collisionmarginw2}, $gameObject->{y} + $_->{collisionmarginh1});
    glEnd();
    glColor( 1, 1, 1);
    glEnable(GL_TEXTURE_2D);
  }
}

sub ResetProjection {
  $ScreenWidth = 800;
  $ScreenHeight = 600;
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, 800, 0, 600, -1000, 1000);
  glDisable(GL_SCISSOR_TEST);
  glMatrixMode(GL_MODELVIEW);
}

sub DrawScoreBoard {}

sub Run {
  my ($self) = shift;

  $self->ResetGame();
  &GameTimer::ResetTimer();

  while (1) {

    # Calculate advance (how many game updates to perform)
    my $advance = $self->CalculateAdvances();

    # Advance the game
    %::Events = ();
    &::HandleEvents();
    &::AdvanceRecorder($advance)  if $::RecorderOn;
    while ($advance--) {
      return  if $self->{abortgame};
      $self->AdvanceGame();
      %::Events = ();
    }
    $self->DrawGame();
    $::App->sync();
  }
}

sub AdvanceGame {
  my $self = shift;
  
  %::GameEvents = ();
  $self->PreAdvance(); # Hook for something special
  $self->AdvanceGameObjects();
  $self->PostAdvance(); # Hook for something special
  if ($Level->IsLevelOver()) {
    $Level->OnLevelOver();
    $self->GoToNextLevel();
  }
  $self->AdvanceSounds();
}

sub PreAdvance {
  my ($self) = @_;
  my @actions = @{$self->{preAdvanceActions}};
  foreach (@actions) {
    $_->{advance}->($_);
  }
  &::AdvanceBackground()  unless $self->{silent} || $self->{enemyStop};
  $Level->PreAdvance();
}

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

  ++$self->{anim};
  my @gameObjects = @::GameObjects;
  foreach my $gameObject (@gameObjects) {
    $gameObject->Advance()  unless $gameObject->{deleted};
  }
}

sub PostAdvance {
  my ($self) = @_;
  my @actions = @{$self->{postAdvanceActions}};
  foreach (@actions) {
    $_->{advance}->($_);
  }
  $Level->PostAdvance();
}

sub RemoveAction {
  my ($self, $action) = @_;
  
  &::RemoveFromList($self->{preAdvanceActions}, $action);
  &::RemoveFromList($self->{postAdvanceActions}, $action);
}

sub RemoveActionByName {
  my ($self, $actionName) = @_;
  my ($actions);
  
  $actions = $self->{preAdvanceActions};
  @$actions = grep { $_->{name} ne $actionName } @$actions;
  $actions = $self->{postAdvanceActions};
  @$actions = grep { $_->{name} ne $actionName } @$actions;
}

sub AddAction {
  my $self = shift;
  push @{$self->{postAdvanceActions}}, @_;
}

sub AddPreadvanceAction {
  my $self = shift;
  push @{$self->{preAdvanceActions}}, @_;
}

sub OnPlayerRespawn {
  my $self = shift;
  my (@gameObjects, $gameObject);
  
  @gameObjects = @::GameObjects;
  foreach $gameObject (@gameObjects) {
    next  unless $gameObject->isa('Enemy');
    $gameObject->OnPlayerRespawn();
  }
}


##########################################################################
package NormalGame;
##########################################################################

@NormalGame::ISA = qw(GameBase);

sub ResetGame {
  my $self = shift;

  $self->SUPER::ResetGame();
  $::Difficulty = 1;

  foreach (0 .. $::NumGuys-1) {
    $::Players[$_]->Reset();
  }
  $self->AddAction( &::MakeSuperBallSpawner() );
  $self->AddAction( &::MakeBonusBoxSpawner() );
  $self->{levels} = [ @::Levels ];
  warn "Level set is: ", join(':', @::Levels);
  $self->GoToNextLevel();
}

sub GoToNextLevel {
  my ($self) = @_;
  
  my $oldLevelNumber = $Level->{level};
  my $nextLevel = $self->{levels}->[$self->{level}++] || new GameOverLevel($oldLevelNumber);
  warn "Starting level $self->{level}: $nextLevel";
  $nextLevel->Initialize();
  $Level = $nextLevel;
}

sub DrawScoreBoard {
  my ($self) = @_;
  my ($boxWidth, $boxLeft);
  
  $boxLeft = 100;
  $::ScoreFont->pre_output;
  $::ScoreFont->output(5, 25, ::T("Level"));
  $::ScoreFont->output(30, 0, $Level->{level});
  
  $boxWidth = ($ScreenWidth - $boxLeft) / $::NumGuys;
  foreach (0 .. $::NumGuys-1) {
    my $player = $::Players[$_];
    $::ScoreFont->output($boxWidth * $_ + $boxLeft, 25, $player->{score}, $player->{color});
    if ($player->{respawnDelay}) {
      $::ScoreFont->output($boxWidth * $_ + $boxLeft,  0, int($player->{respawnDelay} / 100), $player->{color});
    } else {
      $::ScoreFont->output($boxWidth * $_ + $boxLeft,  0, $player->{lives} > 4 ? chr(127) . 'x' . $player->{lives} : chr(127) x $player->{lives});
    }
  }
  
  unless ($self->{gameOver}) {
    my $timeLeft = int($Level->{timeLeft} / 100);
    $::ScoreFont->output( 380, 570, sprintf("%02d", $timeLeft > 0 ? $timeLeft : 0), $timeLeft <= 5 ? [1,0,0] : undef );
  }
  $::ScoreFont->post_output;
}

sub OnGameOver {
  my ($self) = @_;
  
  $Level = new GameOverLevel($Level->{level});
  $Level->Initialize();
  new TextGameObject(text=>::T("GAME OVER"), font=>$::GlossyFont, x=>320, y=>270)->Center();
  $self->{gameOver} = 1;
}


1;
