/*
 *  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 <utility>
#include <sstream>
#include <map>
#include <sstream>
#include <SDL/SDL.h>
#include "remote.h"
#include "model.h"
#include "game.h"
#include "fellow.h"
#include "library.h"
#include "vector.h"

static Storyboard/*<256>*/ VCR   (Storyboard::REWIND  , 256);   // Records all the normal movements of everything in the level
static Storyboard/*<256>*/ record(Storyboard::PLAYBACK, 1  );   // Records the movements of Dave while record button is pressed

Remote::Remote() :
  Model("graphics/remote/runko.3DS"),
  pressed(NOTHING),
  paused(false),
  screen_texture(GL_RGB8, 512, 512),
  grid("graphics/lcd_grid.png", false)
{
  set_texture(lib["remote/mappi3.png"]);
  scene = new Scene();
  scene->set_fill_background(false);
  scene->add_model(this);
  set_position(0.0f, 0.0f, /*-23.0f*/ -40.0f);
  //set_rotation(90.0f, 0.0f, 0.0f);

  screen = new Model("graphics/remote/nayttooikee2.3DS");
  add_model(screen);
  screen->set_texture(new TextureProxy(&screen_texture));
  gaps = new Model("graphics/remote/nreunat.3DS");
  add_model(gaps);
  gaps->set_texture(lib["remote/napit.png"]);
  button_count = 27;
  buttons = new Model [button_count] ();
  for (int i=0; i<button_count; i++) {
    ostringstream s;
    s << "graphics/remote/n" << i + 1 << "ya.3DS";
    buttons[i].load(s.str());
    buttons[i].set_texture(lib["remote/napit.png"]);
    add_model(&buttons[i]);
  }

  grid.nearest();

  //calculate_ao();
}

Remote::~Remote()
{
}

float Remote::titration(float x)
{
  return 1.0f - exp(-pow(x * 3.0f, 2.0f));
}

void Remote::render_remote()
{
  const float t = 0.001f * SDL_GetTicks();
  const float amp = 0.5f + 0.5f * sin(0.2f * t);
  float *pos = get_position();
  float *rot = get_rotation();
#if 1
  Uint8 *keys = SDL_GetKeyState(NULL);
  const float s = 0.1f;
  if (keys[SDLK_LSHIFT]) {
    if (keys[SDLK_UP]) pos[1] += s;
    if (keys[SDLK_DOWN]) pos[1] -= s;
    if (keys[SDLK_LEFT]) pos[0] -= s;
    if (keys[SDLK_RIGHT]) pos[0] += s;
    if (keys[SDLK_COMMA]) pos[2] -= s;
    if (keys[SDLK_PERIOD]) pos[2] += s;
    if (keys[SDLK_w]) rot[0] -= s;
    if (keys[SDLK_s]) rot[0] += s;
    if (keys[SDLK_a]) rot[1] -= s;
    if (keys[SDLK_d]) rot[1] += s;
    if (keys[SDLK_q]) rot[2] -= s;
    if (keys[SDLK_e]) rot[2] += s;
    if (keys[SDLK_SPACE])
      cerr << "pos: " << pos[0] << ", " << pos[1] << ", " << pos[2] << endl
           << "rot: " << rot[0] << ", " << rot[1] << ", " << rot[2] << endl;
  }
#endif

  // Animation of the key models
  for (int i=0; i<button_count; i++)
    buttons[i].set_position(0.0f, 0.0f, 0.0f);
  static map<Function, int> index;
  if (index.empty()) {
    index[REWIND]       = 15;
    index[PAUSE]        = 16;
    index[FAST_FORWARD] = 17;
    index[STOP]         = 18;
    index[PLAY]         = 19;
    index[RECORD]       = 20;
  }
  if (pressed != NOTHING) {
    assert(index.count(pressed));
    buttons[index[pressed]].set_position(0.0f, -0.3f, 0.0f);
  }

  //pos: 11.6, 3.7, -22.4001
  //rot: 63.9996, -0.400001, 0
  //set_position(11.6f, 3.7f, -22.4001f);
  //set_rotation(63.9996f   + 2.0f * sin(0.5f * t),
  //             -0.400001f + 1.5f * sin(0.3f * t),
  //             0.0f       + 1.0f * sin(0.1f * t));

  //pos: 15.1, 16.1, -22.8001
  //rot: 80.5993, 0, 0

  float state0[] = {9.39998f, 7.29997f, -13.6001f,       // Displaying only HUD
                    90.3678f   + 2.0f * sin(0.5f * t),
                    1.49279f   + 1.5f * sin(0.3f * t),
                    3.67141f   + 1.0f * sin(0.1f * t)};
  float state1[] = {3.19928f + 11.0f, 10.9989f, -29.999f,       // Displaying the full remote
                    33.308f     + 2.0f * sin(0.5f * t),
                    0.456365f   + 1.5f * sin(0.3f * t),
                    0.810477f   + 1.0f * sin(0.1f * t)};

  state = 0.001f * SDL_GetTicks() - changed_at;
  state *= 2.3f;
  if (state < 0.0f) state = 0.0f;
  if (state > 1.0f) state = 1.0f;
  state = titration(state);
  if (pressed == NOTHING)
    state = 1.0f - state;
  float i[6];
  for (int j=0; j<6; j++)
    i[j] = (1.0f - state) * state0[j] + state * state1[j];

  static float side;
  const float tside = game->get_dave()->get_heading() > 0.0f ? 0.0f : 1.0f;
  const float w = 0.2f;
  side = (1.0f - w) * side + w * tside;
#if 1
  if (!keys[SDLK_LSHIFT])
#endif
    set_position(i[0] - 19.0f * side, i[1] - 13.0f * (1.0f - fabsf(2.0f * side - 1.0f)), i[2] - 0.0f),
    set_rotation(i[3] - 0.0f, (side > 0.0f) ? (360.0f - i[4]) : i[4], i[5]);

  scene->render();
}

void Remote::render_screen_to_texture()
{
  screen_fbo.set_color(screen_texture, 0);
  screen_fbo.bind();
  const float black[4] = {0.0f, 0.0f, 0.0f, 1.0f};
  const float green[4] = {0.5f, 1.0f, 0.5f, 1.0f};
  glClearColor(0.5f, 1.0f, 0.5f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  // Score
  hud_score.set_colors(black, green);
  ostringstream o;
  static int current_score;
  if (current_score < game->get_dave()->get_score()) current_score += 2;
  if (current_score > game->get_dave()->get_score()) current_score = game->get_dave()->get_score();
  o << current_score;
  hud_score.set_text(o.str());
  hud_score.set_position(Vector(0.0f, 100.0f));
  hud_score.set_scale(3.0f, 3.0f);
  hud_score.render();

  // Active ability text
  hud_active_ability.set_colors(black, green);
  Ability *abi = game->get_dave()->get_active_ability();
  hud_active_ability.set_text(abi ? abi->type_name() : "");
  hud_active_ability.set_position(Vector(0.0f, 180.0f));
  hud_active_ability.set_scale(1.8f, 2.0f);
  hud_active_ability.render();

  // Ability symbols
  static Image *sym_cluster_bomb;
  static Image *sym_laser;
  static Image *sym_laser_pulse;
  static Image *sym_rc_missile;
  static Image *sym_rocket_pack;
  static Image *sym_sling;
  if (sym_cluster_bomb == NULL) {
    sym_cluster_bomb = lib["item_rakettireppu.png"];
    sym_laser        = lib["item_rakettireppu.png"];
    sym_laser_pulse  = lib["item_rakettireppu.png"];
    sym_rc_missile   = lib["item_rakettireppu.png"];
    sym_rocket_pack  = lib["item_rakettireppu.png"];
    sym_sling        = lib["item_sling.png"       ];
  }
  const vector<Ability *> *const inv = game->get_dave()->get_inventory();
  for (vector<Ability *>::const_iterator i=inv->begin(); i!=inv->end(); ++i) {
    Vector pos;
    switch ((*i)->get_type()) {
      case Ability::CLUSTER_BOMB: sym_cluster_bomb->bind(); pos.x = 0.0f; break;
      case Ability::LASER:        sym_laser       ->bind(); pos.x = 1.0f; break;
      case Ability::LASER_PULSE:  sym_laser_pulse ->bind(); pos.x = 2.0f; break;
      case Ability::RC_MISSILE:   sym_rc_missile  ->bind(); pos.x = 3.0f; break;
      case Ability::ROCKET_PACK:  sym_rocket_pack ->bind(); pos.x = 4.0f; break;
      case Ability::SLING:        sym_sling       ->bind(); pos.x = 5.0f; break;
    }
    glDisable(GL_LIGHTING);
    glColor4f(0.0f, 0.0f, 0.0f, 1.0f);  // We want everything to be black (only alpha) because it's an LCD
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glBegin(GL_QUADS);
    const float high_y = 1.0f;
    const float high_x = 1.0f;
    const bool i = false;
    const float size = 0.115f;
    pos *= 0.2f;
    pos += Vector(-1.3f, -0.86f);
    glTexCoord2f(0.0f  , i ? 0.0f   : high_y);  glVertex2f(-size + pos.x, -size + pos.y);
    glTexCoord2f(high_x, i ? 0.0f   : high_y);  glVertex2f( size + pos.x, -size + pos.y);
    glTexCoord2f(high_x, i ? high_y : 0.0f  );  glVertex2f( size + pos.x,  size + pos.y);
    glTexCoord2f(0.0f  , i ? high_y : 0.0f  );  glVertex2f(-size + pos.x,  size + pos.y);
    glEnd();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  }

  glDisable(GL_LIGHTING);
  glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, 100.0, 0.0, 100.0, -1.0, 1.0);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  // Health?

  // Volume?

  // Voltage
  hud_voltage.set_colors(black, green);
  ostringstream oo;
  //oo << setiosflags(ios::fixed) << setprecision(2)
  //   << "U=+" << game->get_dave()->get_voltage() << "V";
  static char buf[32];
  sprintf(buf, "U=+%3.2fV", game->get_dave()->get_voltage());
  oo << buf;
  hud_voltage.set_text(oo.str());
  hud_voltage.set_position(Vector(0.0f, 65.0f));
  hud_voltage.set_scale(3.0f, 3.0f);
  hud_voltage.render();

  // Parts
  hud_parts.set_colors(black, green);
  ostringstream asdf;
  asdf << "P=" << game->get_dave()->get_parts();
  hud_parts.set_text(asdf.str());
  hud_parts.set_position(Vector(0.0f, 50.0f));
  hud_parts.set_scale(2.0f, 2.0f);
  hud_parts.render();

  // Cassettes
  hud_cass.set_colors(black, green);
  ostringstream bsdf;
  bsdf << "C=" << game->get_dave()->get_cass();
  hud_cass.set_text(bsdf.str());
  hud_cass.set_position(Vector(50.0f, 50.0f));
  hud_cass.set_scale(2.0f, 2.0f);
  hud_cass.render();

  // Oxygen
  const Ability *const a = game->get_dave()->get_active_ability();
  if (a && a->get_type() == Ability::ROCKET_PACK) {
    glDisable(GL_TEXTURE_2D);
    const float high_y = 1.0f;
    const float high_x = 1.0f;
    const bool i = false;
    const float size = 3.0f;
    float height = a->get_charge() * 0.25f;
    Vector pos(60.0f, 2.0f);
    glBegin(GL_QUADS);
    glVertex2f(-size + pos.x, pos.y);
    glVertex2f( size + pos.x, pos.y);
    glVertex2f( size + pos.x, height + pos.y);
    glVertex2f(-size + pos.x, height + pos.y);
    glEnd();
    height = 80.0f;
    glBegin(GL_LINE_LOOP);
    glVertex2f(-size + pos.x, pos.y);
    glVertex2f( size + pos.x, pos.y);
    glVertex2f( size + pos.x, height + pos.y);
    glVertex2f(-size + pos.x, height + pos.y);
    glEnd();
    glEnable(GL_TEXTURE_2D);
  }

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

  // Clock?


  // LCD grid
  if (game->get_ext()->gen_mipmap) {
    grid.bind();
    glDisable(GL_LIGHTING);
    glColor4f(0.5f, 1.0f, 0.5f, 1.0f);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    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 - fix_y) * f * 0.75f;
    const float high_x =  1.0f          * f;
    const bool i = false;
    glTexCoord2f(0.0f  , i ? 0.0f   : high_y);  glVertex2f(-1.0f, -1.0f);
    glTexCoord2f(high_x, i ? 0.0f   : high_y);  glVertex2f( 1.0f, -1.0f);
    glTexCoord2f(high_x, i ? high_y : 0.0f  );  glVertex2f( 1.0f,  1.0f);
    glTexCoord2f(0.0f  , i ? high_y : 0.0f  );  glVertex2f(-1.0f,  1.0f);
    glEnd();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  }
  screen_fbo.unbind();
  if (game->get_ext()->gen_mipmap) {
    screen_texture.bind(0);
    glGenerateMipmapEXT(GL_TEXTURE_2D);
    screen_texture.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    screen_texture.unbind(0);
  }
}

void Remote::press(Remote::Function button)
{
  //if (game->get_ext()->fbo && game->get_ext()->shaders)
  //  game->get_tv()->zoom_out();

  if (button != PAUSE)
    if (game->get_dave()->get_voltage() == 0.0f)
      return;

  pressed = button;
  changed_at = 0.001f * SDL_GetTicks();

  switch (pressed) {
  case PLAY:
    record.begin();
    break;
  case RECORD:
    record.clear();
    break;
  case PAUSE:
    if (!paused && game->get_dave()->get_voltage() == 0.0f)
      break;
    paused = !paused;
    if (!paused)
      Mix_ResumeMusic();
    break;
  default:
    break;
  }
}

void Remote::release()
{
  pressed = NOTHING;
  changed_at = 0.001f * SDL_GetTicks();
  //if (game->get_ext()->fbo && game->get_ext()->shaders)
  //  game->get_tv()->zoom_in();
}

void Remote::cycle(float dt, float time)
{
  Level *l = game->get_current_level();
  const Level *cl = l;
  static unsigned int c;
  if (c++ % 3 == 0) {
    VCR.record_new_frame();
    for (list<Fellow *>::const_iterator i=cl->fellows_begin(); i!=cl->fellows_end(); ++i)
      **i >> VCR.record_entry();
  }

  if (paused) {
    if (game->get_dave()->get_voltage() == 0.0f) {
      press(PAUSE);
      return;
    }
    game->get_dave()->sub_voltage(0.01f);
  }

  switch (pressed) {
  case NOTHING:
    break;
  case POWER:
    release();
    break;
  case GO_TO_TELETEXT:
    release();
    break;
  case EXIT_TELETEXT:
    release();
    break;
  case N0: release(); break;
  case N1: release(); break;
  case N2: release(); break;
  case N3: release(); break;
  case N4: release(); break;
  case N5: release(); break;
  case N6: release(); break;
  case N7: release(); break;
  case N8: release(); break;
  case N9: release(); break;
  case REWIND:
    if (game->get_dave()->get_voltage() == 0.0f)
      break;
    game->get_dave()->sub_voltage(0.01f);
    if (!VCR.empty()) {
      for (list<Fellow *>::iterator i=l->fellows_begin(); i!=l->fellows_end(); ++i)
	**i << VCR.pop_entry();
      VCR.pop_frame();
    }
    break;
  case PAUSE:
    release();
    break;
  case FAST_FORWARD:
    break;
  case STOP:
    release();
    break;
  case PLAY:
    if (game->get_dave()->get_voltage() == 0.0f)
      break;
    game->get_dave()->sub_voltage(0.01f);
    if (!record.empty()) {
      *game->get_dave_kuopio() << pair<const char *, bool>(record.pop_entry(), !record.at_first());
      record.pop_frame();
    }
    break;
  case RECORD:
    if (game->get_dave()->get_voltage() == 0.0f)
      break;
    game->get_dave()->sub_voltage(0.01f);
    record.record_new_frame();
    *game->get_dave() >> record.record_entry();
    break;
  case VOLUME_UP:
    break;
  case VOLUME_DOWN:
    break;
  case CHANNEL_UP:
    release();
    break;
  case CHANNEL_DOWN:
    release();
    break;
  case MUTE:
    release();
    break;
  }
}

bool Remote::vcr_empty() const
{
  return VCR.empty();
}

void Remote::clear_record()
{
  record.clear();
}

Storyboard::Storyboard(Purpose m, size_t initial_entries) : purpose(m), initial_entries(initial_entries)
{
}

Storyboard::~Storyboard()
{
}

void Storyboard::record_new_frame()
{
  frames.push_back(Frame(initial_entries));
  while (frames.size() > 1000)
    frames.pop_front();
  current_frame = frames.begin();
}

char *Storyboard::record_entry()
{
  return frames.back().record_entry();
}

char *Storyboard::pop_entry()
{
  if (purpose == REWIND)
    return frames.back().pop_entry();
  else if (purpose == PLAYBACK)
    return current_frame->pop_entry();
  else
    throw;
}

void Storyboard::pop_frame()
{
  if (purpose == REWIND)
    frames.pop_back();
  else if (purpose == PLAYBACK)
    if (current_frame != frames.end()) {
      ++current_frame;
      if (current_frame == frames.end())
	current_frame = frames.begin();
    }
    else;
  else
    throw;
}

void Storyboard::clear()
{
  frames.clear();
}

void Storyboard::begin()
{
  current_frame = frames.begin();
}

Storyboard::Frame::Frame(size_t initial_entries) : position(0)
{
  entries.reserve(initial_entries);
}

Storyboard::Frame::~Frame()
{
}

char *Storyboard::Frame::record_entry()
{
  entries.push_back(Entry());
  return entries.back();
}

char *Storyboard::Frame::pop_entry()
{
  assert(entries.size());
  if (position >= entries.size())
    position = 0;
  return entries[position++];
}
