/*
 *  Copyright (C) 2008 Kristian Kristola
 *
 *  This program is distributed under the terms of the
 *  GNU General Public License.
 *
 *  This file is part of Dave the Ordinary Spaceman.
 *
 *  Dave the Ordinary Spaceman is free software: you can redistribute
 *  it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation, either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  Dave the Ordinary Spaceman is distributed in the hope that it
 *  will be useful, but WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Dave the Ordinary Spaceman. If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include <cassert>
#include <sstream>
#include <set>
#if defined(__MACOS__) || defined(__APPLE__)
#include <SDL_mixer.h>
#else
#include <SDL/SDL_mixer.h>
#endif
#include "fellow.h"
#include "library.h"
#include "image.h"
#include "level.h"
#include "game.h"
#include "ability.h"
#include "teletext.h"
#include "remote.h"

//debug
#include <SDL/SDL.h>
#include "game.h"

Level::Block::Type *Fellow::DAVE;
Level::Block::Type *Fellow::OERVELOE;
Level::Block::Type *Fellow::HAUKI;
Level::Block::Type *Fellow::BOSS_BIO;
Level::Block::Type *Fellow::HATTIAMPERER;

int Fellow::noise_chan = -1;

void Fellow::load_types()
{
  assert(DAVE == NULL);
#define TYPE(N, str) Type::insert(str, Type::DYNAMIC); \
                     N = Type::get(str)
  TYPE(DAVE         , "16_32_dave" );
  TYPE(OERVELOE     , "32_oerveloe");
  TYPE(HAUKI        , "32_hauki");
  TYPE(BOSS_BIO     , "32_boss_bio");
  TYPE(HATTIAMPERER , "8_16_hattiampeeri");
}

Fellow::Fellow(const Type *t)
  : size(2.0f, 4.0f),
    velocity(0.0f, 1.0f),
    heading(1.0f),
    accel_left(0),
    accel_right(0),
    jumped_at(-1000.0f),
    touched_floor_at(-1000.0f),
    started_waiting_for_jumping_at(-1000.0f),
    valiaikainen_purkkaviritys(0.0f, 0.0f),
    active_ability(0),
    purgga_flag(false),
    score(0),
    voltage(0.0f),
    parts(0),
    cass(0),
    particles(NULL)
{
  type = t;
  if (type == OERVELOE)
    size = Vector(4.0f, 4.0f);
  if (type == BOSS_BIO)
    size = Vector(8.0f, 8.0f);
  event(INITIALIZE, NULL);

  //Level::Block::Type::insert(name, Level::Block::Type::DYNAMIC, Level::Block::Type::CODE);
  //set_type(Level::Block::Type::get(name));
}

Fellow::~Fellow()
{
}

void Fellow::move(const Vector &velocity)
{
  position += velocity;
}

Vector &Fellow::get_position()
{
  return position;
}

void Fellow::set_position(const Vector &p)
{
  position = p;
  Level::Block::position = position;
}

void Fellow::render()
{
  if (alive == DEAD)
    return;

  glPushMatrix();
  glTranslatef(int(8.0f * position.x),
	       int(8.0f * position.y),
	       0);
  if (death_animation == GO_DOWN)
    glScalef(1.0f, 1.0f - death_animation_state(), 1.0f);

  if (fabs(velocity.x) < 0.7f)
    set_animation_number(0);
  else
    set_animation_number(1);

  if (!is_touching_floor())
    if (velocity.y < -0.7)
      set_animation_number(3);
    else if (velocity.y > 0.7)
      set_animation_number(2);

  bool done = false;
  if (death_animation != BLOCK)
    if (!effect()/*alive == DYING*/)
      done = true;

  render_credit();

  if (done) {
    glPopMatrix();
    return;
  }

  set_flags(MIRRORED, heading < 0.0);
  Level::Block::render();

  glPopMatrix();
}

void Fellow::begin_physics_sequence()
{
}

// Laskee uuden paikan sällille
void Fellow::handle(Level *level, const float dt, const float time)
{
  const bool paused = game->get_remote()->is_paused() && this != game->get_dave();
  const bool game_paused = game->get_remote()->is_paused();

  if (is_alive() && this == game->get_dave()) {
    if (!game_paused)
      Mix_ResumeMusic();
    if (Fellow::noise_chan >= 0) {
      Mix_HaltChannel(Fellow::noise_chan);
      Fellow::noise_chan = -1;
    }
  }

  if (position.x < 0.0f || position.y < 0.0f ||
      position.x > game->get_current_level()->get_size_x() ||
      position.y > game->get_current_level()->get_size_y())
    die(time, -30);

  //if (game->get_remote()->is_paused() && this != game->get_dave())
  //  return;

  if (!paused)
    Fellow::time = time;
  if (previous_time > time || time - previous_time > 0.1f)
    previous_time = time;
  if (alive == DYING) {
    if (death_animation_state() == 1.0f) {
      alive = DEAD;
      //level->remove(this);
      if (particles) {
        delete [] particles;
        particles = NULL;
      }
    }
  }
handle_stop_jumping:
  for (list<float>::iterator i=stop_jumping_at.begin(); i!=stop_jumping_at.end(); ++i)
    if (time >= *i) {
      stop_jumping();
      stop_jumping_at.erase(i);
      goto handle_stop_jumping;
    }
  if (alive != ALIVE)
    return;

  if (think()) {
    collide(level, dt, time);
    return;
  }

  if (!paused) {
    acceleration = Vector(0.0f, -130.0f);   // gravity
    float accel_modifier_x = 0.0f;
    if (is_touching_slope() != Vector(0.0f, 0.0f))
      if (is_touching_slope().x < 0.0f)
        if (accel_right == 0.0f)
          accel_modifier_x = -1.0f;
        else;
      else
        if (accel_left == 0.0f)
          accel_modifier_x = 1.0f;
    if (is_touching_floor())
      acceleration.x += 140.0f * (-accel_left + accel_right + accel_modifier_x);
#if 1  // 1=allow walking in air without rocket pack
    else
      acceleration.x += 60.0f * (-accel_left + accel_right + accel_modifier_x);
#endif
    if (is_jumping())
      acceleration.y += 1000.0f;  // 2400.0f
    if (-accel_left + accel_right == 0.0f)
      if (is_touching_floor() /*&& !is_touching_slope()*/)
        velocity.x = 0.9f * velocity.x;   // 0.7f
    acceleration += external_force;

    const float max_horiz = type != DAVE ? 5.0f : 20.0f;
    const float max_up    = 30.0f;
    const float max_down  = 500.0f;
    //if (velocity.magnitude() < min)
    //  velocity.normalize(),
    //    velocity *= min;
    if (type == DAVE || is_touching_floor()) {
      if (velocity.x >  max_horiz) velocity.x =  max_horiz;
      if (velocity.x < -max_horiz) velocity.x = -max_horiz;
      if (velocity.y >  max_up)    velocity.y =  max_up;
      if (velocity.y < -max_down)  velocity.y = -max_down;
    }
#if 0
    position += dt * velocity + 0.5f * acceleration * dt * dt;
    velocity += dt * acceleration;
#else
    velocity += dt * acceleration;
    position += dt * velocity;
#endif
  }

  // Colliding this Fellow against the static blocks of the current level
  collide(level, dt, time);

  //// Colliding this Fellow against all moving stuff of the current level (other fellows, items, etc.)
  //for (list<Level::Block *>::iterator i = level->items_begin(); i != level->items_end(); ++i) {
  //  if ((position - (*i)->position).magnitude() < 8.0f * 8.0f)
  //    i->col_is_intersecting(position, size)
  //}
}

void Fellow::go_left(const bool go) 
{
  accel_left = go ? 1.0f : 0.0f;
  if (accel_left != 0.0f && accel_right == 0.0f)
    heading = 1.0f;
  if (accel_left == 0.0f && accel_right != 0.0f)
    heading = -1.0f;
}

void Fellow::go_right(const bool go)
{
  accel_right = go ? 1.0f : 0.0f;
  if (accel_right != 0.0f && accel_left == 0.0f)
    heading = -1.0f;
  if (accel_right == 0.0f && accel_left != 0.0f)
    heading = 1.0f;
}

void Fellow::jump(const float time)
{
  //started_waiting_for_jumping_at = time;
  if (!is_jumping() && is_touching_floor()) {
    jump_really(time);
  }
}

void Fellow::stop_jumping()
{
  jumped_at = -1000.0f;
}

void Fellow::stop_jumping_in_future(const float time)
{
  stop_jumping_at.push_back(time);
  assert(stop_jumping_at.size() < 100);
}

void Fellow::jump_really(const float time)
{
  //if (!is_jumping())
  jumped_at = time;
  touched_floor_at -= 1.0f;
}

#if 1
void Fellow::collide(Level *level, const float dt, const float time)
{
  const bool paused = game->get_remote()->is_paused();
  const bool isdave = this == game->get_dave();
  Vector final_good_position = position;

  int iter = 0;
again:
  if (iter++ >= 20)
    return;

  const Vector original_velocity_intersecting = velocity;

  static Level::areas         area_feedback;
  static Level::unique_blocks uniq_feedback;
  Level::unique_blocks::iterator uniq;
  Level::areas::reverse_iterator area;
  uniq_feedback.clear();
  area_feedback.clear();

  level->register_potentially_colliding_area(time, position, type->size, this, &uniq_feedback, &area_feedback);

  for (area = area_feedback.rbegin(); area != area_feedback.rend(); ++area) {

    if (uniq_feedback[area->second])
      continue;
    else
      uniq_feedback[area->second] = true;

    const float common_area = area->first;
    Level::Block *const b = area->second->get_parent();
    Level::Block::Type *const t = b->get_type();
    if (common_area > 0.0f) {
      const Vector col_surf_normal = b->collide(position, type->size).get_normalized();

#if 0
      if (program == 1 && b->program == 0 && true) {
        cerr << "-paranormaali " << -col_surf_normal << endl
             << "b->col_is_intersecting(position, type->size) --> " << b->col_is_intersecting(position, type->size) << endl;
      }
#endif


      // The actual handling of events that the collisions cause
      const bool one = this->handle_collision_events(b,  col_surf_normal, original_velocity_intersecting);
      Vector orig_vel;
      Fellow *pf = dynamic_cast<Fellow *>(b);
      if (pf)
        orig_vel = pf->velocity;
      const bool two = b->handle_collision_events(this, -col_surf_normal, orig_vel);
      if (one /*|| two*/)
        goto again;

      goto again;
    }
  }
}

bool Fellow::handle_collision_events(Level::Block *against, const Vector &col_surf_normal, const Vector &original_velocity)
{
  const bool paused = game->get_remote()->is_paused();
  const bool isdave = type == DAVE;

  if (col_surf_normal * Vector(0.0f, 1.0f) > 0.0f) {
    touched_floor_at = time;
      if (col_surf_normal.x != 0.0f)
        valiaikainen_purkkaviritys = col_surf_normal;
      else
        valiaikainen_purkkaviritys = Vector(0.0f, 0.0f);
    //if (is_waiting_for_jumping()) {
    //  jump_really(time);
    //}
  }

  if (!(paused && isdave))
    if (typeid(*against) == typeid(Fellow))
      if (static_cast<Fellow *>(against)->alive != ALIVE) {
        velocity = original_velocity;
        return true;
      }

  if (against->get_type()->invisible == 1 &&
    (this == game->get_dave() || this == game->get_dave_kuopio())) {
      velocity = original_velocity;
      return false;//true;
  }

  if (!paused && against->get_type()->end) {
    game->get_current_level()->completed = true;
  }

  if (!paused && against->get_type()->family == Type::STATIC_COAT && this == game->get_dave()) {
    // Dave got an item
    const string &name = against->get_type()->name;
    if (name == "battery") {
      against->die(time, 1);
    }
    else if (name == "rakettireppu") {against->die(time, 15); add_ability(new Ability(Ability::ROCKET_PACK));}
    else if (name == "sling")        {against->die(time, 15); add_ability(new Ability(Ability::SLING));}
    else if (name == "flyback")      {against->die(time, 15); inc_parts();}
    else if (name == "cassette")     {against->die(time, 15); inc_cass();}
    Mix_PlayChannel(-1, game->get_current_level()->ch_tilu, 0);
    velocity = original_velocity;
    game->got_an_item();
    return true;
  }

  if (program || against->program) {
    // Defining some special behaviour for different 'programs'.
    // nothing       = collide normally
    // "goto revert" = collide physically but don't raise events
    // "return true" = don't collide at all
    if (against->program == 1 /*|| program == 1*/)
      goto revert; //return false;
    else if (program == 1 || program == 2 || against->program == 2)
      return true;
  }

  if      (col_surf_normal == Vector(-1.0f,  0.0f)) event(COLLIDE_RIGHT, against);
  else if (col_surf_normal == Vector( 1.0f,  0.0f)) event(COLLIDE_LEFT, against);
  else if (col_surf_normal == Vector( 0.0f, -1.0f)) event(COLLIDE_UP, against);

  if (!(paused && isdave))
    if (typeid(*against) == typeid(Ability::Ammo) ||
        typeid(*against) == typeid(Fellow)) {
      velocity = original_velocity;
      return true;
    }

  if (!paused && !isdave && typeid(*against) == typeid(Ability::Ammo)) {
    die(time, 11);
    dynamic_cast<Ability::Ammo *>(against)->die();
    return true;
  }

revert:

  float prog_factor = 0.005f;

  // Reflection
  Vector mirrored_velocity = velocity - 2.0f * (col_surf_normal * velocity) * col_surf_normal;
  // Attenuation
  mirrored_velocity = mirrored_velocity - (mirrored_velocity * col_surf_normal) * (1.0f - prog_factor) * col_surf_normal;

  if (col_surf_normal * velocity <= 0.0f)  // If our velocity was into an obstacle --> mirror velocity
    velocity = mirrored_velocity;


  // Travelling out of obstacle with big steps unless the obstacle is triangular
  const Vector normal = col_surf_normal;
  Vector reversing_direction;
  if ((normal.x == 0.0f || normal.y == 0.0f) && against->get_type()->family == Level::Block::Type::STATIC) {
    const float epsilon = 0.0001f;
    Vector step = position - Vector(floor(position.x), floor(position.y));
    if (normal.x == 1.0f) step.x = 1.0f - step.x;
    if (normal.y == 1.0f) step.y = 1.0f - step.y;
    reversing_direction = col_surf_normal.multiply(step);
    reversing_direction *= 1.0f + epsilon;
    do {
      position += reversing_direction;
      reversing_direction = normal;
    } while (against->col_is_intersecting(position, type->size));
  } else {
    reversing_direction = 0.005f * normal;
    do
      position += reversing_direction;
    while (against->col_is_intersecting(position, type->size));
  }

  return false;
}

#else
//////////////////////// Kokonaan uus tapa /////////////////////////
void Fellow::collide(Level *level, const float dt, const float time)
{
  Vector final_good_position = position;

  // Testataan ollaanko seinäpalikan kanssa sisäkkäin

  int iter = 0;
  set<const Level::Block *> skip;

 again:

  if (iter++ >= 20)
    return;
  //assert(iter++ < 1000);

  const Vector fel_size = size;
  if (level->col_is_intersecting(position, fel_size)) {

    //  selvitetään palikat joihin törmättiin, ja palikoiden prioriteettijärjestys leikkauspinta-alojen ja
    //  palikoiden keskipisteiden suuntaisten nopeuskomponenttien perusteella (pinta-ala
    //  primäärinen tekijä)

    const Vector original_position_intersecting = position;
    const Vector original_velocity_intersecting = velocity;
    const multimap<Level::BlockInfo, Level::Block *, Level::BlockInfo::comp> colliding_blocks =
      level->col_get_nearest_blocks(original_position_intersecting - size/2.0f, &original_position_intersecting, &size, &velocity, this);

    // törmätään suurimman prioriteetin blokkiin ja määritellään tämän törmäyksen
    // perusteella (keskipisteiden välisen janan kulma) uusi nopeusvektori ->5

    const Level::BlockInfo *info = NULL;
    Level::Block *d;
    for (multimap<Level::BlockInfo, Level::Block *, Level::BlockInfo::comp>::const_iterator i=colliding_blocks.begin(); i!=colliding_blocks.end(); ++i) {
      info = &i->first;
      d = i->second;
      if (skip.count(d) > 0) {
	info = NULL;
	continue;
      }
      break;
    }

    if (info && info->common_area > 0.0f) {
      const Vector col_surf_normal = d->collide(original_position_intersecting, size).get_normalized();

      // Progressiivinen pomppu
      //const float max_vel = 15.0f;
      // Tämän vois korvata kaava.txt:ssä olevalla
      //const float prog_factor = 1.0f - (velocity.magnitude() / max_vel * 0.07f + 0.60f);
      float prog_factor = 0.005f;    //0.175f

      /*
      float M[4];
      const float pf = prog_factor;
      if (col_surf_normal.x == 0.0f && col_surf_normal.y != 0.0f) {       // Peiluu x-akselin suhteen
	M[0] =  1.0f; M[2] =  0.0f;
	M[1] =  0.0f; M[3] = -1.0f * pf;
      }
      else if (col_surf_normal.x != 0.0f && col_surf_normal.y == 0.0f) {  // Peiluu y-akselin suhteen
	M[0] = -1.0f * pf; M[2] =  0.0f;
	M[1] =  0.0f     ; M[3] =  1.0f;
      }
      else {
        cerr << "Yritettiin tyrmätä blokkiin jonka kanssa yhteistä pinta-alaa on: " << info->common_area << endl;
        const_cast<Level::Block *>(d)->set_type(Level::Block::Type::get("cd"));
	extern Game *game;
	game->render();
	SDL_GL_SwapBuffers();
	SDL_Delay(3000);
	assert(!"Ei näin");
      }

      Vector mirrored_velocity(M[0]*velocity.x + M[2]*velocity.y,
                               M[1]*velocity.x + M[3]*velocity.y);
      */

      // Reflection
      Vector mirrored_velocity = velocity - 2.0f * (col_surf_normal * velocity) * col_surf_normal;
      // Attenuation
      mirrored_velocity = mirrored_velocity - (mirrored_velocity * col_surf_normal) * (1.0f - prog_factor) * col_surf_normal;


      if (col_surf_normal * velocity <= 0.0f)  // Peilataan ainoastaan jos oltiin matkalla seinän sisälle
        velocity = mirrored_velocity;

      // Kitka
      //velocity *= 0.96f;

      // Lippujen asetus
      if (col_surf_normal * Vector(0.0f, 1.0f) > 0.0f) {
        touched_floor_at = time;
	if (col_surf_normal.x != 0.0f)
	  valiaikainen_purkkaviritys = col_surf_normal;
	else
	  valiaikainen_purkkaviritys = Vector(0.0f, 0.0f);
        //if (is_waiting_for_jumping()) {
        //  jump_really(time);
        //}
      }

      if (typeid(*d) == typeid(Fellow))
	if (static_cast<Fellow *>(d)->alive != ALIVE) {
	  velocity = original_velocity_intersecting;
	  skip.insert(d);
	  goto again;
	}

      if (d->get_type()->invisible == 1 &&
        (this == game->get_dave() || this == game->get_dave_kuopio())) {
          velocity = original_velocity_intersecting;
          skip.insert(d);
          goto again;
      }

      if      (col_surf_normal == Vector(-1.0f,  0.0f)) event(COLLIDE_RIGHT, d);
      else if (col_surf_normal == Vector( 1.0f,  0.0f)) event(COLLIDE_LEFT, d);
      else if (col_surf_normal == Vector( 0.0f, -1.0f)) event(COLLIDE_UP, d);

      if (typeid(*d) == typeid(Ability::Ammo) ||
          typeid(*d) == typeid(Fellow)) {
	velocity = original_velocity_intersecting;
	skip.insert(d);
	goto again;
      }

#if 0
      static int asdf;
      if (this == game->get_dave())
      cerr << asdf++
	   << " - norm=" << col_surf_normal
	   << ", area=" << info->common_area
	//<< ", ptr=" << d
	   << ", name=" << d->get_type()->name
	//<< ", dave=" << game->get_dave()
	   << ", position=" << d->get_position()
	   << ", dave's position=" << game->get_dave()->get_position()
	   << endl;
#endif

      const Vector normal = col_surf_normal.get_normalized();
      Vector reversing_direction;
      if (normal.x == 0.0f || normal.y == 0.0f) {
	const float epsilon = 0.0001f;
	Vector step = position - Vector(floor(position.x), floor(position.y));
	if (normal.x == 1.0f) step.x = 1.0f - step.x;
	if (normal.y == 1.0f) step.y = 1.0f - step.y;
	reversing_direction = col_surf_normal.multiply(step);
	reversing_direction *= 1.0f + epsilon;
	do {
	  position += reversing_direction;
	  reversing_direction = normal;
	} while (d->col_is_intersecting(position, fel_size));
      } else {
	reversing_direction = 0.005f * normal;
	do
	  position += reversing_direction;
	while (d->col_is_intersecting(position, fel_size));
      }

      goto again;
    }
  }
}
#endif

Vector Fellow::get_velocity() const
{
  return velocity;
}

bool Fellow::is_jumping() const
{
  //HUOM! Jotta hyppy toimii järkevästi niin täs funktios oleva numero ei saa olla pienempi kuin is_touching_floor():ssa oleva.
  return jumped_at + 0.17f > time;  // 0.17f , 0.22f
}

bool Fellow::is_touching_floor() const
{
  //HUOM! Jotta hyppy toimii järkevästi niin täs funktios oleva numero ei saa olla suurempi kuin is_jumping():ssa oleva.
  return touched_floor_at + 0.17f > time;
}

const Vector &Fellow::is_touching_slope() const
{
  return valiaikainen_purkkaviritys;
}

bool Fellow::is_waiting_for_jumping() const
{
  return started_waiting_for_jumping_at + 0.16f > time;
}

bool Fellow::think()
{
  if (type == BOSS_BIO)
    jump(time);

  switch (program) {
  case 1:  // Lift
  case 2:
    if (original_position == Vector(0,0))
      original_position = position;
    position = original_position + Vector(0.0f, -5.65f * fabs(fmod(1.0f * this->time, 20.0f) - 10.0f));
    return true;
    break;
  }
  return false;
}

void Fellow::event(Fellow::Event e, Level::Block *target)
{
  bool paused;
  bool isdave;
  if (paused = e==INITIALIZE) {  // remote cannot be read if we are initializing
    paused = false;
    isdave = type == DAVE;
  } else {
    paused = game->get_remote()->is_paused();
    isdave = this == game->get_dave();
  }

  if (target && type == DAVE && target->get_type() == DAVE)
    return;

  if (!paused && (type == OERVELOE || type == HAUKI || type == HATTIAMPERER))
    if (e == INITIALIZE)
      go_right(true),
        go_left(false);
    else if (e == COLLIDE_LEFT)
      go_right(true),
        go_left(false);
    else if (e == COLLIDE_RIGHT)
      go_left(true),
        go_right(false);
    else if (e == COLLIDE_UP) if (type != HATTIAMPERER) {
      die(time, target == game->get_dave_kuopio() ? 130 : 50);
      Mix_PlayChannel(-1, game->get_current_level()->ch_piuingg, 0);
      if (typeid(*target) == typeid(Fellow)) {
        Fellow *fel = dynamic_cast<Fellow *>(target);
        fel->jump_really(time);
        fel->stop_jumping_in_future(time + 0.1f);
      }
    }

  if (type == DAVE) {
    if (e == COLLIDE_UP)
      stop_jumping();
    if (dynamic_cast<Fellow *>(target))
      if (!paused && (
          e == COLLIDE_UP ||
          e == COLLIDE_LEFT ||
          e == COLLIDE_RIGHT)) {
        if (target->get_type() != HATTIAMPERER) {
          die(time, -30);
          if (this == game->get_dave_kuopio())   // If copy Dave dies, delete the recorded animation
            game->get_remote()->clear_record();
        } else {  // Dave collided to a hattiamperer
          Ability *ab = get_active_ability();
          if (ab && ab->get_type() == Ability::SLING && !ab->has_block_ammo()) {
            target->die(time, 10);
            ab->load(target);
          }
        }
      }
  }

  if (type == BOSS_BIO) {
  }

  if (!paused && target)
    if (typeid(*target) == typeid(Ability::Ammo))
      hit_by_ammo(dynamic_cast<Ability::Ammo *>(target));
}

void Fellow::hit_by_ammo(Ability::Ammo *ammo)
{
  if (ammo->get_creator() == game->get_dave() && this == game->get_dave())
    return;
  ammo->die();
  die(time, 15);
  Mix_PlayChannel(-1, game->get_current_level()->ch_piuingg, 0);
}

void Fellow::use(const float time)
{
  if (0 <= active_ability && active_ability < (int)inventory.size())
    inventory[active_ability]->use(this, time);
}

void Fellow::add_ability(Ability *a)
{
  assert(a);
  for (vector<Ability *>::iterator i=inventory.begin(); i!=inventory.end(); ++i)
    if ((*i)->get_type() == a->get_type()) {
      delete a;
      return;
    }
  inventory.push_back(a);
}

Ability *Fellow::remove_ability()
{
  Ability *abil = NULL;
  if (0 <= active_ability && active_ability < (int)inventory.size()) {
    abil = inventory[active_ability];
    inventory.erase(inventory.begin() + active_ability);
  }
  return abil;
}

float Fellow::get_heading() const
{
  return heading;
}

void Fellow::die(const float t, int score)
{
  if (alive == ALIVE) {
    Block::die(t, score);
    int a = int(float(rand()) / float(RAND_MAX) * D_ANIM_LAST);
    assert(0 <= a && a <= (int)D_ANIM_LAST);
    death_animation = (DeathAnimation)a;
    death_animation = EXPLODE_CUBES;
    if (type == HATTIAMPERER)
      death_animation = BLOCK;
    if (this == game->get_dave()) {
      if (noise_chan == -1) {
        noise_chan = Mix_PlayChannel(-1, game->get_current_level()->ch_tv_noise, 0);
        Mix_PauseMusic();
      }
    }
  }
}

void Fellow::rise_from_dead()
{
  alive = ALIVE;
  if (credit) {
    delete credit;
    credit = NULL;
  }
  if (particles) {
    delete [] particles;
    particles = NULL;
  }
  Mix_ResumeMusic();
  if (Fellow::noise_chan >= 0) {
    Mix_HaltChannel(Fellow::noise_chan);
    Fellow::noise_chan = -1;
  }
}

float Fellow::death_animation_state() const
{
  if (alive == ALIVE)
    return 0.0f;
  float state = time - died_at;
  switch (death_animation) {     // The duration of the animation
  case GO_DOWN:       state /= 0.25f; break;
  case EXPLODE_CUBES: state /= 1.2f;  break;
  case D_ANIM_LAST:assert(!"Ei näin");break;
  }
  if (state < 0.0f) state = 0.0f;
  if (state > 1.0f) state = 1.0f;
  return state;
}

bool Fellow::effect()
{
  if (alive != DYING)
    return true;
  if (purgga_flag)
    return false;
  if (death_animation == EXPLODE_CUBES) {
    if (particles == NULL) {
      particles = new Particle [32*32];
      for   (int tex_x = 0; tex_x < 32; tex_x++)
        for (int tex_y = 0; tex_y < 32; tex_y++) {
          const float x = (float(tex_x) - 32.0f/2.0f) / (32.0f/2.0f);  // -1..1
          const float y = (float(tex_y) - 32.0f/2.0f) / (32.0f/2.0f);  // -1..1
          Particle *p = &particles[tex_y * 32 + tex_x];
          p->position.x = x * (32.0f/2.0f);
	  p->position.y = y * (32.0f/2.0f);
          p->position.z = -150.0f;
          p->velocity.x = 40.0f * x + 20.0f * (((float)rand()) / RAND_MAX - 0.5f);
          p->velocity.y = 25.0f + 40.0f * y + 20.0f * (((float)rand()) / RAND_MAX - 0.5f);
          p->velocity.z = 80.0f +  20.0f * (((float)rand()) / RAND_MAX - 0.5f);
          p->angular_velocity.x = 700.0f * x + 700.0f * (((float)rand()) / RAND_MAX - 0.5f);
          p->angular_velocity.y = 700.0f * y + 700.0f * (((float)rand()) / RAND_MAX - 0.5f);
          p->angular_velocity.z = 700.0f +  700.0f * (((float)rand()) / RAND_MAX - 0.5f);
        }
    }

    static float cube_verts[8*3];
    static int cube_inds[3*2*6];
    static float cube_normals[6*3];

    int v = 0;
    const float rx = -0.5f, ry = -0.5f;
    const float step = 1.0f;
    const float e = 0.0f;
    const float depth = 1.0f;
    /* 0 front bottom left  */ cube_verts[v++] = rx;            cube_verts[v++] = ry;            cube_verts[v++] = depth;
    /* 1 front bottom right */ cube_verts[v++] = rx + step + e; cube_verts[v++] = ry;            cube_verts[v++] = depth;
    /* 2 front top    left  */ cube_verts[v++] = rx;            cube_verts[v++] = ry + step + e; cube_verts[v++] = depth;
    /* 3 front top    right */ cube_verts[v++] = rx + step + e; cube_verts[v++] = ry + step + e; cube_verts[v++] = depth;
    /* 4 back  bottom left  */ cube_verts[v++] = rx;            cube_verts[v++] = ry;            cube_verts[v++] = depth - step;
    /* 5 back  bottom right */ cube_verts[v++] = rx + step + e; cube_verts[v++] = ry;            cube_verts[v++] = depth - step;
    /* 6 back  top    left  */ cube_verts[v++] = rx;            cube_verts[v++] = ry + step + e; cube_verts[v++] = depth - step;
    /* 7 back  top    right */ cube_verts[v++] = rx + step + e; cube_verts[v++] = ry + step + e; cube_verts[v++] = depth - step;

    int i = 0;
    const int v0 = 0;
    int n = 0;
    cube_inds[i++] = v0 + 0;  // front
    cube_inds[i++] = v0 + 1;
    cube_inds[i++] = v0 + 2;
    cube_inds[i++] = v0 + 1;
    cube_inds[i++] = v0 + 3;
    cube_inds[i++] = v0 + 2;
    cube_normals[n++] = 0.0f;
    cube_normals[n++] = 0.0f;
    cube_normals[n++] = 1.0f;

    cube_inds[i++] = v0 + 4;  // back
    cube_inds[i++] = v0 + 6;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 6;
    cube_inds[i++] = v0 + 7;
    cube_normals[n++] =  0.0f;
    cube_normals[n++] =  0.0f;
    cube_normals[n++] = -1.0f;

    cube_inds[i++] = v0 + 2;  // left
    cube_inds[i++] = v0 + 4;
    cube_inds[i++] = v0 + 0;
    cube_inds[i++] = v0 + 2;
    cube_inds[i++] = v0 + 6;
    cube_inds[i++] = v0 + 4;
    cube_normals[n++] = -1.0f;
    cube_normals[n++] =  0.0f;
    cube_normals[n++] =  0.0f;

    cube_inds[i++] = v0 + 3;  // right
    cube_inds[i++] = v0 + 1;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 3;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 7;
    cube_normals[n++] = 1.0f;
    cube_normals[n++] = 0.0f;
    cube_normals[n++] = 0.0f;

    cube_inds[i++] = v0 + 2;  // top
    cube_inds[i++] = v0 + 3;
    cube_inds[i++] = v0 + 6;
    cube_inds[i++] = v0 + 3;
    cube_inds[i++] = v0 + 7;
    cube_inds[i++] = v0 + 6;
    cube_normals[n++] = 0.0f;
    cube_normals[n++] = 1.0f;
    cube_normals[n++] = 0.0f;

    cube_inds[i++] = v0 + 0;  // bottom
    cube_inds[i++] = v0 + 4;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 0;
    cube_inds[i++] = v0 + 5;
    cube_inds[i++] = v0 + 1;
    cube_normals[n++] =  0.0f;
    cube_normals[n++] = -1.0f;
    cube_normals[n++] =  0.0f;

    glClear(GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluPerspective(75.0, 800.0 / 600.0, 0.5, 300.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    const Vector offset = 6.5f * (position - game->get_scroll()/*dave()->position*/);
    glTranslatef(offset.x, offset.y, 0.0f);

    glEnable(GL_LIGHTING);
    glDisable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    GLfloat ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
    GLfloat diffuse[] = { 0.8f, 0.8f, 0.8, 1.0f };
    GLfloat specular[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    GLfloat position[] = { 0.0f, 0.0f, -1.0f, 0.0f };
    //glTranslatef(-200.0f, 0.0f, 0.0f);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    bind_image();

    const float st = death_animation_state();
    glRotatef(90.0f * st, 0,0,1);

    for   (int tex_x = 0; tex_x < 32; tex_x++)
      for (int tex_y = 0; tex_y < 32; tex_y++) {
        const float x = (float(tex_x) - 32.0f/2.0f) / (32.0f/2.0f);  // -1..1
        const float y = (float(tex_y) - 32.0f/2.0f) / (32.0f/2.0f);  // -1..1
	//const float st2 = 2.0f * st - 1.0f;

        glPushMatrix();
        /*
        glTranslatef(x * (32.0f/2.0f) * (2.0f * st + 1.0f),
		     y * (32.0f/2.0f) * (2.0f * st + 1.0f) - 10.0f * st*st,
		     -200.0f + st * 200.0f + 25.0f + 25.0f * st * sin(3532.0f * x + 6543.0f * y));
	glRotatef(433.0f * st*st, 0.2f * st, 0.4f * st, 0.6f * st);
	*/
	
	Particle *p = &particles[tex_y * 32 + tex_x];
	glTranslatef(p->position.x,
 	             p->position.y,
	             p->position.z);
	glRotatef(p->rotation.x, 1,0,0);
	glRotatef(p->rotation.y, 0,1,0);
	glRotatef(p->rotation.z, 0,0,1);
#define MIN(a, b) ((a) < (b) ? (a) : (b))

	for (int pass=3; pass<4; pass++) {
	  switch (pass) {
	  case 0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	          glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		  glEnable(GL_TEXTURE_2D); break;
	  case 1: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		  glDisable(GL_TEXTURE_2D); break;
	  case 2: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                  glColor4f(0.7f, 0.7f, 0.7f, 1.0f);
		  glEnable(GL_TEXTURE_2D); break;
	  case 3: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	          glLineWidth(3.0);
                  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		  glEnable(GL_TEXTURE_2D); break;
	  }
	  glBegin(GL_TRIANGLES);
	  glTexCoord2f( x * 0.5f + 0.5f + 0.00001f,
		       -y * 0.5f + 0.5f + 0.00001f);
	  for (unsigned int i=0; i<sizeof(cube_inds)/sizeof(int); i++) {
	    //glColor4f(((i/6+0)%3) + 0.6f,
	    //	    ((i/6+1)%3) + 0.6f,
	    //	    ((i/6+2)%3) + 0.6f,
	    //	    1.0f);
	    //glNormal3fv(&cube_normals[3 * (i / 6)]);
	    glVertex3fv(&cube_verts[3 * cube_inds[i]]);
	  }
	  glEnd();
	}
	glPopMatrix();

        //   v = v0 + at  ,     p = p0 + vt + 0.5at^2
        const Vector3D acceleration(0.0f, -40.0f, 0.0f);
        const Vector3D angular_acceleration(0.0f, 0.0f, 0.0f);
        float t = time - previous_time;
        t = 0.05f;
        p->position += p->velocity * t + 0.5f * acceleration * t * t;
        p->velocity += acceleration * t;
        p->rotation += p->angular_velocity * t + 0.5f * angular_acceleration * t * t;
        p->angular_velocity += angular_acceleration * t;
      }

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glLineWidth(1.0);
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    return false;
  }
  return true;
}

void Fellow::select_ability(int x)
{
  if (0 <= x && x < (int)inventory.size())
    active_ability = x;
}

void Fellow::select_ability(Ability::Type type)
{
  for (size_t i=0; i<inventory.size(); i++)
    if (inventory[i]->get_type() == type)
      select_ability(i);
}

void Fellow::operator <<(const char *buffer)
{
  static pair<const char *, bool> pa;
  pa.first = buffer;
  pa.second = false;
  *this << pa;
}

void Fellow::operator <<(const pair<const char *, bool> &pa)
{
  const char *p = pa.first;
#define LOAD(x) memcpy(&x, p, sizeof(x)), p += sizeof(x)
#define PASS(x) p += sizeof(x)
  if (!pa.second)
    LOAD(alive);
  else
    PASS(alive);
  //if (alive == DYING) alive = DEAD;
  if (alive == ALIVE) purgga_flag = false; else purgga_flag = true;
  LOAD(position);
  LOAD(velocity);
  LOAD(acceleration);
  LOAD(heading);
  LOAD(jumped_at);
  LOAD(touched_floor_at);
  LOAD(valiaikainen_purkkaviritys);
  LOAD(time);
  LOAD(died_at);
  if (particles) {
    delete [] particles;
    particles = NULL;
  }
  if (alive == ALIVE && credit) {
    delete credit;
    credit = NULL;
  }
}

void Fellow::operator >>(char *buffer) const
{
  char *p = buffer;
#define SAVE(x) memcpy(p, &x, sizeof(x)); p += sizeof(x)
  SAVE(alive);
  SAVE(position);
  SAVE(velocity);
  SAVE(acceleration);
  SAVE(heading);
  SAVE(jumped_at);
  SAVE(touched_floor_at);
  SAVE(valiaikainen_purkkaviritys);
  SAVE(time);
  SAVE(died_at);
  assert(p - buffer <= 256);
}
