/*
 *  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 <map>
#include <SDL/SDL.h>
#include <SDL/SDL_keysym.h>
#if defined(__MACOS__) || defined(__APPLE__)
#include <SDL_mixer.h>
#else
#include <SDL/SDL_mixer.h>
#endif
#include "ability.h"
#include "game.h"
#include "fellow.h"
#include "library.h"
#include "image.h"

using namespace std;

extern Game *game;

Ability::Ability(Ability::Type t) : sight_angle(0.0f), block_ammo(NULL), charging(false), charge(0.0f)
{
  type = t;
}

Ability::~Ability()
{
}

void Ability::use(Fellow *user, const float time, int param1, float param2)
{
  Uint8 *keys = SDL_GetKeyState(NULL);
  switch (type) {
  case LASER_PULSE: {
    if (game->get_dave()->get_voltage() == 0.0f)
      break;
    game->get_dave()->sub_voltage(2.5f);
    Ammo *a = new Ammo(this, time, user);
    game->get_current_level()->add_item(a);
    a->set_position(3, user->get_position() + Vector(user->get_heading()>0.0f ? 0.0f : 2.0f, 2.0f));
    a->set_velocity(Vector(-50.0f * user->get_heading(), 0.0f));
    Mix_PlayChannel(-1, game->get_current_level()->ch_lazor, 0);
    } break;
  case ROCKET_PACK: {
    Vector force;
    if (keys[SDLK_DOWN])  force.y -= 170.0f;
    //if (keys[SDLK_UP])    if (user->get_velocity().y < 5.0f) force.y += 200.0f;
    //if (keys[SDLK_LEFT])  force.x -= 80.0f;
    //if (keys[SDLK_RIGHT]) force.x += 80.0f;
    static int chan;
    if (keys[SDLK_UP] && !charging) {
      chan = Mix_PlayChannel(-1, game->get_current_level()->ch_charge, 0);
    }
    if (keys[SDLK_UP]) {
      charging = true;
      charge += 0.6f;
      if (charge > 320.0f)
        charge = 320.0f;
    }
    if (!keys[SDLK_UP] && charging) {  // key released just now
      charging = false;
      Mix_HaltChannel(chan);
      Mix_PlayChannel(-1, game->get_current_level()->ch_pack, 0);
    }
    if (!keys[SDLK_UP] && charge > 0.0f) {
      force.y += 5.0f * charge + 200.0f;
      charge -= 6.0f;
      if (charge < 0.0f) charge = 0.0f;
    }
    user->external_force = force;
    } break;
  case SLING: {
    const float h = game->get_dave()->get_heading();
    if ((keys[SDLK_UP] || keys[SDLK_DOWN]) && sight_angle == -31337.0f) sight_angle = h > 0.0f ? M_PI : 0.0f;
    if      (keys[SDLK_UP  ]) sight_angle -= h * 0.025f;
    else if (keys[SDLK_DOWN]) sight_angle += h * 0.025f;
    else                      sight_angle = -31337.0f;
    if (keys[SDLK_LCTRL] || keys[SDLK_RCTRL]) {
      Fellow *f = dynamic_cast<Fellow *>(block_ammo);
      if (f) {
        const float h = sight_angle < M_PI/2.0f || sight_angle > M_PI*1.5f ? -1.0f : 1.0f;
        f->set_alive(true);
        f->set_position(user->get_position() + Vector(h<0 ? 2.0f : -1.0f, 2.0f) + Vector(0.1f * -h, 0.0f));
        f->set_velocity(Vector(90.0f * cos(sight_angle),
                               90.0f * sin(sight_angle)) + user->get_velocity());
        block_ammo = NULL;
      }
    }
    } break;
  default:
    break;
  }
}

void Ability::render() const
{
  if (type == SLING && sight_angle != -31337.0f) {
    // Rendering the sight
    glActiveTextureARB(GL_TEXTURE0);
    glDisable(GL_LIGHTING);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    Vector pos = 8.0f * game->get_dave()->get_position() - 8.0f * game->get_scroll();
    pos.x += 8.0f * game->get_capacity() / 2.0f * game->get_aspect_ratio();
    pos.y += 8.0f * game->get_capacity() / 2.0f;
    pos.y += 16.0f;
    pos.x += 35.0f * cos(sight_angle);
    pos.y += 35.0f * sin(sight_angle) * game->get_aspect_ratio();
    glTranslatef(pos.x, pos.y, 0.0f);
    lib["sight.png"]->bind();
    glBegin(GL_QUADS);
    const float fix_y = 0.0f; // 1.0f / (600.0f * 1.0f);
    const float f = 90.0f;
    const float high_y = 1.0f;
    const float high_x = 1.0f;
    const bool i = false;
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    const float size = 16.0f;
    glTexCoord2f(0.0f  , i ? 0.0f   : high_y);  glVertex2f(0.0f, 0.0f);
    glTexCoord2f(high_x, i ? 0.0f   : high_y);  glVertex2f(size, 0.0f);
    glTexCoord2f(high_x, i ? high_y : 0.0f  );  glVertex2f(size, size);
    glTexCoord2f(0.0f  , i ? high_y : 0.0f  );  glVertex2f(0.0f, size);
    glEnd();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
  }
}

void Ability::load(Level::Block *x)
{
  block_ammo = x;
}

Ability::Ammo::Ammo(Ability *const host, const float time, Fellow *creator)
  : life(0.0f),
    ability(host),
    creator(creator)
{
  string name = "32_16_coll_laser_pulse";
  Level::Block::Type::insert(name, Level::Block::Type::DYNAMIC_COAT, Level::Block::Type::CODE);
  set_type(Level::Block::Type::get(name));
}

Fellow *Ability::Ammo::get_creator() const
{
  return creator;
}

Ability::Ammo::~Ammo()
{
}

void Ability::Ammo::handle(const float dt, const float time)
{
  life += dt;
  position += dt * velocity;

  game->get_current_level()->
    register_potentially_colliding_area(time, position, type->size, this, NULL, NULL);
}

void Ability::Ammo::render() const
{
  glDisable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  const float scale1 = 8.0f;
  const float scale2 = 3.0f;
  const float scale3 = 0.04f;
  const float scale4 = life * 0.1f + 0.65f;
  const float time_to_grow = 0.4f;
  const float max_length = 10.0f;
  const float len      = life > time_to_grow ? max_length :        life / time_to_grow * max_length;
  const float tail_opc = life > time_to_grow ? 0.0f       : 1.0f - life / time_to_grow * 1.0f;
  const float w = 1.0f;
  struct Hue {float shape; GLubyte hue[4];} hues[] = {
    {w * -0.50f  , {40 , 25 , 25 , 0  }},
    {w *  0.0f   , {252, 100, 8  , 100}},
    {w *  0.15f  , {252, 128, 8  , 150}},
    {w *  0.30f  , {252, 252, 8  , 200}},
    {w *  0.70f  , {252, 252, 252, 255}}
  };
  for (size_t i=0; i<sizeof(hues)/sizeof(Hue); i++) {
    if (hues[i].hue[3] == 0)
      glBlendFunc(GL_ONE, GL_ONE);
    else
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBegin(GL_TRIANGLE_FAN);
#define CLAMP(x) (unsigned int)((x)>255.0f ? 255.0f : (x))
    glColor4ub(CLAMP(hues[i].hue[0] + (1.0f-tail_opc)*010.0f),
               CLAMP(hues[i].hue[1] * tail_opc),
               CLAMP(hues[i].hue[2] * tail_opc),
               (unsigned int)(255.0f * tail_opc));
    glVertex2f(scale1 * position.x + scale2 *  (0.0f        + hues[i].shape) * scale3 * velocity.x, scale1 * position.y + scale2 * scale4 *  0.0f);
    glVertex2f(scale1 * position.x + scale2 *   1.0f                         * scale3 * velocity.x, scale1 * position.y - scale2 * scale4 * (1.0f - hues[i].shape));
    //glColor4ubv(&hues[i].hue[0]);
    glColor4ub(CLAMP(hues[i].hue[0] + (1.0f-tail_opc)*230.0f),
               CLAMP(hues[i].hue[1] + (1.0f-tail_opc)*140.0f),
               CLAMP(hues[i].hue[2] + (1.0f-tail_opc)*030.0f),
               hues[i].hue[3]);
    glVertex2f(scale1 * position.x + scale2 *  (1.0f + len)                  * scale3 * velocity.x, scale1 * position.y - scale2 * scale4 * (1.0f - hues[i].shape));
    glVertex2f(scale1 * position.x + scale2 * ((2.0f + len) - hues[i].shape) * scale3 * velocity.x, scale1 * position.y + scale2 * scale4 *  0.0f);
    glVertex2f(scale1 * position.x + scale2 *  (1.0f + len)                  * scale3 * velocity.x, scale1 * position.y + scale2 * scale4 * (1.0f - hues[i].shape));
    glColor4ub(CLAMP(hues[i].hue[0] + (1.0f-tail_opc)*010.0f),
               CLAMP(hues[i].hue[1] * tail_opc),
               CLAMP(hues[i].hue[2] * tail_opc),
               (unsigned int)(255.0f * tail_opc));
    glVertex2f(scale1 * position.x + scale2 *   1.0f                         * scale3 * velocity.x, scale1 * position.y + scale2 * scale4 * (1.0f - hues[i].shape));
    glEnd();
  }
  glColor4f(1,1,1,1);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void Ability::Ammo::set_velocity(const Vector &v)
{
  velocity = v;
}

void Ability::Ammo::die()
{
  game->get_current_level()->remove(this);
}
