/*
 *  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 <SDL/SDL.h>

#ifdef __ON_ISO__
#include <SDL/SDL_main.h>
#include <windows.h>
#endif

#include <fstream>
#include <sstream>
#include <iomanip>
#include <dirent.h>
#if defined(__MACOS__) || defined(__APPLE__)
#include <SDL_mixer.h>
#else
#include <SDL/SDL_mixer.h>
#endif
#include "game.h"
#include "fellow.h"
#include "analog_effects.h"
#include "ability.h"
#include "model.h"
#include "image.h"
#include "library.h"
#include "remote.h"

using namespace std;

Game *game;

int main(int argc, char*argv[])
{
  game = new Game(argc == 3, argc == 2);
  return game->run();
}

void audio_post_effect(void *udata, Uint8 *stream, int len);
void music_finished_callback();

Game::Game(bool disable_shaders, bool simple_shaders) :
  scroll_fix_speed(1.0f / 60.0f - 1.0f / 100.0f),
  scroll_fix_depth(0.0f),
  physics_time(0.0f),
  current_level(NULL),
  dave(NULL),
  poweroff_on(true),
  poweron_on(true),
  show_editor(false),
  remote(NULL),
  editor_start_time(1.0e30f),
  play_showed_for(0),
  animation_speed(1.0f),
  got_item_at(0.0f),
  credits(false),
  current_level_number(0),
  rendering_editor(false)
{
  game = this;

  if (SDL_Init(SDL_INIT_EVERYTHING) == -1)
    exit(1);

  width = 800;
  height = 600;

  SDL_SetVideoMode(width, height, 32, SDL_OPENGL);

  if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 512)) {
    cerr << "Could not open audio!\n";
    exit(1);
  }
  cerr<<"-------------------------------openaudio\n";
  //Mix_ReserveChannels(16);
  Mix_SetPostMix(audio_post_effect, NULL);
  Mix_HookMusicFinished(music_finished_callback);

  const GLubyte *str = glGetString(GL_EXTENSIONS);
  ext.fbo        = strstr((const char *)str, "GL_EXT_framebuffer_object")      != NULL;
  ext.vbo        = strstr((const char *)str, "GL_ARB_vertex_buffer_object")    != NULL;
  ext.shaders    = strstr((const char *)str, "GL_ARB_vertex_program"  )        != NULL &&
                   strstr((const char *)str, "GL_ARB_fragment_program")        != NULL &&
                   strstr((const char *)str, "GL_ARB_shader_objects"  )        != NULL &&
                   strstr((const char *)str, "GL_ARB_shading_language_100")    != NULL;
  ext.gen_mipmap = strstr((const char *)str, "GL_SGIS_generate_mipmap")        != NULL;
  ext.antialias  = strstr((const char *)str, "GL_EXT_framebuffer_multisample") != NULL;
  load_config();
  save_config();
  if (disable_shaders)
    ext.shaders = false;
  ext.simple_shaders |= simple_shaders;
  bool bad = false;
  //for (int i = 0; i < sizeof(Extensions)/sizeof(bool); ++i, ++p)
  //  if (*p == false)
  //    bad = true;
  bad = !ext.fbo || !ext.vbo || !ext.shaders;
  if (bad) {

#ifdef __ON_ISO__
    MessageBox(NULL,
#else
    cerr <<
#endif
      "Your graphics card and/or driver don't support OpenGL 2.0 fully! "
      "Some effects will be disabled!"
#ifdef __ON_ISO__
    , "Dave the Ordinary Spaceman by Lasi Interactive", MB_ICONERROR | MB_OK);
#else
    ;
#endif
  }

#ifdef __ON_ISO__
  int ati = MessageBox(NULL, "Do you have an ATI video card?", "Dave the Ordinary Spaceman by Lasi Interactive",
		       MB_ICONQUESTION | MB_YESNO);
  if (ati == IDYES) {
    MessageBox(NULL,
	       "In that case I will disable teletext mipmapping because it seems to be buggy on ATI. "
	       "This will get fixed in some future release, please check our home pages.",
	       "Dave the Ordinary Spaceman by Lasi Interactive",
	       MB_ICONINFORMATION | MB_OK);
    ext.gen_mipmap = false;
  }
#endif

  running = true;
  y_capacity = 37.5;
  if (SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1) == 0)
    cout << "Käytetään vsyncciä\n";

#ifdef __ON_ISO__
/*
    MessageBox(NULL,
      "Moi!"
      , "Dave the Ordinary Spaceman by Lasi Interactive", MB_ICONEXCLAMATION | MB_OK);
*/
#endif

  Uint32 flags = SDL_OPENGL;
// #ifdef __ON_ISO__
//   flags |= SDL_FULLSCREEN;
// #endif
  if (config["fullscreen"] == "on")
    flags |= SDL_FULLSCREEN;

  screen = SDL_SetVideoMode(width, height, 32, flags);cerr<<"näyttömoodi asetettu\n";

  SDL_ShowCursor(SDL_DISABLE);

#if !defined(__MACOS__) && !defined(__APPLE__)
  glewInit();
#endif
  cerr<<"glew käynnissä\n";

  Level::Block::Type::initialize();
  editor.load();cerr<<"editori ladattu\n";

  // Ladataan kentät
  const char *l[] = {"liipantönkkä"};
  for (unsigned int i=0; i<sizeof(l)/sizeof(char *); i++)
    levels[l[i]] = new Level(l[i]);

  current_level = levels[l[0]];
  cerr<<"kentät muka ladattu\n";

  /*
  ifstream ll("levels/avaruus.dave", ios_base::in | ios_base::binary);
  if (ll.good())
    current_level->load(ll);
  ll.close();
  */
  switch_level(0);
  cerr<<"kenttä ladattu\n";

  if (config["autostart_editor"].size() == 1) {
    show_editor = true;
    int n = config["autostart_editor"][0] - '1';
    assert(0 <= n && n <= 2);
    switch_level(n);
  }

  // Pistetään Dave kenttään
  Fellow::load_types();
  dave = current_level->add_fellow(new Fellow(Fellow::DAVE));
  cerr<<"dave ladattu\n";
  dave->set_position(current_level->find_start_position(dave->get_type()));
  dave->add_ability(new Ability(Ability::LASER_PULSE));
  dave_kuopio = current_level->add_fellow(new Fellow(Fellow::DAVE));
  dave_kuopio->set_alive(false);
  dave_kuopio->add_ability(new Ability(Ability::LASER_PULSE));
  dave_kuopio->set_flags(Level::Block::MONOCHROME, true);
  if (ext.shaders)
    kuopio_prog = new GLProgram("graphics/kuopio_vert.glsl", "graphics/kuopio_frag.glsl");
  cerr<<"dave lisätty\n";
  current_level->reset();
  cerr<<"vihulaiset lisätty\n";

  // Alustetaan testi teksti
  text = new Teletext();
  text->set_position(Vector(10, 235));
  text->set_colors((float[4]){1,0,0,1}, (float[4]){0,0,0,0});
  text->set_scale(0.5f, 0.5f);
  text->set_text("Press F1!");

  if (ext.fbo) {
    poweron  = new Power();
    poweroff = new Power();
    poweron ->set_mode(Power::POWERON);
    poweroff->set_mode(Power::POWEROFF);
    poweron->launch();
    pixels2cubes = new Pixels2Cubes();
  }
  if (ext.fbo && ext.shaders) {
    motion_blur = new MotionBlur();
    gaussian_blur = new GaussianBlur();
    tv = new TV();
    noise = new PalNoise();
    tape = new TapeDistortion();
  }
  cerr<<"efektit ladattu\n";

  remote = new Remote();

  img_rewind = new Image("graphics/rewind.png", true);
  img_stop   = new Image("graphics/stop.png"  , true);
  img_play   = new Image("graphics/play.png"  , true);
  img_record = new Image("graphics/rec.png"   , true);
}

Game::~Game()
{
}

#warning koodaa muut
static float last_frame_showed_at;

int Game::run()
{
  bool last = false;
  current_level->completed = false;
  game_completed = false;
  while (main_menu(current_level->completed)) {
    fbo_reset();
    quit_at = 1.0e20;
    if (ext.fbo && ext.shaders) {
      poweroff_on = true;
      poweroff->unlaunch();
      poweron->unlaunch();
      poweron->launch();
      if (ext.fbo && ext.shaders)
	tv->zoom_in();
    }
    if (current_level->completed) {
      current_level->play_music(false);
      current_level->play_intro();
    }
    current_level->completed = false;
    if (last) {
      game_completed = true;
      continue;
    }
    while (running && current_level->completed == false) {
      if (handle()) {
	render();
	//glFinish();
	glFlush();
	SDL_Delay(5);
	//sync();
	SDL_GL_SwapBuffers();
      } else {
	SDL_Delay(5);
      }
    }
    running = true;
    if (current_level->completed)
      if (current_level_number < 3) {
	switch_level(++current_level_number);
	get_current_level()->reset();
	dave->set_position(current_level->find_start_position(dave->get_type()));
	dave->set_velocity(Vector());
        dave->go_left(false);
        dave->go_right(false);
      } else
	last = true;
    fbo_reset();
    if (ext.fbo && ext.shaders) {
      tv->zoom_out();
    }
  }
  SDL_Quit();
  return 0;
}

inline bool Game::sync() const
{
  ////////while (0.001f * SDL_GetTicks() < physics_time);
//#define DIFF() (0.001f * SDL_GetTicks() - physics_time)
  //while (DIFF() < 0);
  ////cerr << "Erotus: " << DIFF() << endl;
}

bool Game::handle()
{
  const float real_time = 0.001f * SDL_GetTicks();
  static float prev_real_time;
  static float time;
  dt = real_time - prev_real_time;
  const float too_much = 0.2f;
  if (dt > too_much)
    dt = too_much;
  time += dt;
  prev_real_time = real_time;

  if (!show_editor) {
    const float physics_frequency = 100.0f;
    const float physics_dt = 1.0f / physics_frequency;
    current_level->begin_physics_sequence();
    if (physics_time >= time)
      return false;
    while (physics_time < time) {
      if (remote->get_pressed() != Remote::REWIND)
	current_level->handle(physics_dt, physics_time);
      remote->cycle(physics_dt, physics_time);
      physics_time += physics_dt;
    }
    //23:03:54 <@Hipo> Jepa.

    if (ext.shaders)
      if (fabs(dave->get_velocity().y) > 45.0f) {
	//motion_blur->chroma = (fabs(dave->get_velocity().x) + 10.0f) / 20.0f;
	motion_blur->chroma = (fabs(dave->get_velocity().y) - 45.0f) / 2.0f;
	motion_blur->amount = 0.1f;
      } else {
	motion_blur->chroma = 1.0f;
	motion_blur->amount = 0.7f - powf((sinf(physics_time*1.0f)*0.5f+0.5f) * 0.5f, 2.0f);  // 0.4f
        if (remote->is_paused())
          motion_blur->amount = 0.15f;
      }
  }

  static SDL_Event previous_mouse_event;

  SDL_Event ev;
  while (SDL_PollEvent(&ev)) {
    switch (ev.type) {

    case SDL_QUIT:
      if (!show_editor) {
        quit_at = time;
        if (ext.fbo) {
          poweroff_on = false;
          poweroff->launch();
        }
      }
      break;

    case SDL_KEYDOWN:
      switch (ev.key.keysym.sym) {
      case SDLK_F11:
	//while (1);
	break;
      case SDLK_ESCAPE:
        if (!show_editor && quit_at > 1e20) {
          quit_at = time;
          if (ext.fbo) {
            poweroff_on = false;
            poweroff->launch();
          }
	  //Mix_FadeOutMusic(1000);
        }
	break;
      case SDLK_LEFT:  dave->go_left(true);  break;
      case SDLK_RIGHT: dave->go_right(true); break;
      case SDLK_DOWN:                        break;
      case SDLK_UP:                          break;
      case SDLK_LALT:
	dave->jump(physics_time);
	Mix_PlayChannel(-1, current_level->ch_random, 0);
	break;
      case SDLK_LCTRL:
      case SDLK_RCTRL:
	dave->use(physics_time);
	break;
      case SDLK_1: dave->select_ability(Ability::LASER_PULSE); break;
      case SDLK_2: dave->select_ability(Ability::ROCKET_PACK); break;
      case SDLK_3: dave->select_ability(Ability::SLING); break;
      case SDLK_4: dave->select_ability(Ability::CLUSTER_BOMB); break;
      case SDLK_5: dave->select_ability(Ability::LASER); break;
      case SDLK_6: dave->select_ability(Ability::RC_MISSILE); break;
    //case SDLK_7: dave->select_ability(6); break;
    //case SDLK_8: dave->select_ability(7); break;
    //case SDLK_9: dave->select_ability(8); break;
    //case SDLK_0: dave->select_ability(9); break;
      case SDLK_TAB:
        break;
        show_editor = !show_editor;
        editor_start_time = 0.001f * SDL_GetTicks();
        if (show_editor)
          editor.set_level(current_level);
        else {
          physics_time = time;
          editor.cleanup();
        }
	current_level->reset();
        SDL_ShowCursor(show_editor ? SDL_ENABLE : SDL_DISABLE);
        break;
      case SDLK_F1:
        {
          bool r = true;
          while (r) {
            lib["keyinfo.png"]->bind();
            glColor4f(1,1,1,1);
            draw_fullscreen_quad(false);
            SDL_GL_SwapBuffers();
            SDL_Event e;
            while (SDL_PollEvent(&e))
              if (e.type == SDL_KEYDOWN)
                r = false;
            SDL_Delay(50);
          }
        }
        break;
      case SDLK_F2:
        SDL_WM_ToggleFullScreen(screen);
        break;
      case SDLK_F3:
        Mix_FadeOutMusic(2000);
        break;
      case SDLK_SPACE:
        if (!ext.fbo)
          break;
        if (poweroff_on) {
          poweroff_on = false;
	  poweroff->launch();
	  if (ext.fbo && ext.shaders)
	    tv->zoom_out();
	} else {
	  poweroff_on = true;
	  poweroff->unlaunch();
	  poweron->unlaunch();
	  poweron->launch();
	  if (ext.fbo && ext.shaders)
	    tv->zoom_in();
	}
        break;
      case SDLK_z: remote->press(Remote::REWIND      ); break;
      case SDLK_x: remote->press(Remote::PLAY        ); break;
      case SDLK_c: remote->press(Remote::PAUSE       ); break;
      case SDLK_v: remote->press(Remote::STOP        ); break;
      case SDLK_b: remote->press(Remote::FAST_FORWARD); break;
      case SDLK_r: remote->press(Remote::RECORD      ); break;
      default:
	break;
      }
      break;

    case SDL_KEYUP:
      switch (ev.key.keysym.sym) {
      case SDLK_LEFT:  dave->go_left(false);  break;
      case SDLK_RIGHT: dave->go_right(false); break;
      case SDLK_DOWN:                         break;
      case SDLK_UP:                           break;
      case SDLK_LALT:
        dave->stop_jumping();
        break;
      case SDLK_z:
      case SDLK_x:
      case SDLK_c:
      case SDLK_v:
      case SDLK_b:
      case SDLK_r:
        remote->release();
	get_dave_kuopio()->set_alive(false);
        break;
      default:
	break;
      }
      break;

    case SDL_MOUSEBUTTONDOWN:
    case SDL_MOUSEMOTION:
    case SDL_MOUSEBUTTONUP:
      previous_mouse_event = ev;
      break;

    }

    if (show_editor)
      editor.handle(&ev);
  }
  if (show_editor)
    editor.cycle();

  const Uint8 *keys = SDL_GetKeyState(NULL);
  if (show_editor) {
    const float f = 20.0f * dt;
    Vector d;
    if (keys[SDLK_LEFT ]) d.x = -f;
    if (keys[SDLK_RIGHT]) d.x =  f;
    if (keys[SDLK_DOWN ]) d.y = -f;
    if (keys[SDLK_UP   ]) d.y =  f;
    dave->get_position() += d;
    if (!d.is_zero())
      editor.handle(&previous_mouse_event);
  }


  void pr(Game *);
  /////ei näitä////if (SDL_GetKeyState(NULL)[SDLK_7]) {scroll_fix_speed -= 0.00005f; pr(this);}
  /////////////////if (SDL_GetKeyState(NULL)[SDLK_8]) {scroll_fix_speed += 0.00005f; pr(this);}
  //if (SDL_GetKeyState(NULL)[SDLK_9]) {scroll_fix_depth -= 0.05f;   pr(this);}
  //if (SDL_GetKeyState(NULL)[SDLK_0]) {scroll_fix_depth += 0.05f;   pr(this);}

  if (quit_at < time - 1.0f)
    running = false;
  return true;
}

void pr(Game *g)
{
  cerr << "speed: " << g->scroll_fix_speed << "     depth: " << g->scroll_fix_depth << endl;
}

void Game::render()
{
  if (ext.fbo)
    fbo_accum_begin();

  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glDisable(GL_LIGHTING);
  glDisable(GL_ALPHA_TEST);
  glDisable(GL_COLOR_MATERIAL);
  glDisable(GL_STENCIL_TEST);

  glEnable(GL_TEXTURE_2D);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  const float right = y_capacity * 8.0f * ((float)width/height);
  const float top   = y_capacity * 8.0f;
  glOrtho(0, right, 0, top, -1, 1);
  //glFrustum(0, right, 0, top, 1, 20);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  scroll = dave->get_position();
  if (!show_editor) {
    if (scroll.x < y_capacity * (width/height) / 2.0f) scroll.x = y_capacity * (width/height) / 2.0f;
    if (scroll.y < y_capacity                  / 2.0f) scroll.y = y_capacity                  / 2.0f;
    if (scroll.x > current_level->get_size_x() - y_capacity * (width/height) / 2.0f) scroll.x = current_level->get_size_x() - y_capacity * (width/height) / 2.0f;
    if (scroll.y > current_level->get_size_y() - y_capacity                  / 2.0f) scroll.y = current_level->get_size_y() - y_capacity                  / 2.0f;
  }
  glTranslatef(right/2.0f + int(-8.0f * scroll.x /* / 2.0f */) /* * 2.0f */,
	       top/2.0f   + int(-8.0f * scroll.y /* / 2.0f */) /* * 2.0f */,
	       0);

  if (show_editor || (!show_editor && get_editor_anim() > 0.0f))
    glTranslatef(-right/6 * get_editor_anim(), 0, 0);

  // Skrollauksen korjaustekijä
  const float scroll_fix_max = 1.0f / 60.0f;
  static float fix_position;
  fix_position += scroll_fix_speed;
  while (fix_position >= scroll_fix_max)
    fix_position -= scroll_fix_max;
  const Vector fix = scroll_fix_depth * fix_position * dave->get_velocity();
  glTranslatef(fix.x, fix.y, 0.0f);

  glDisable(GL_BLEND);

  //glScalef(0.1f, 0.1f, 1.0f);

  if (ext.shaders && dave_kuopio->is_alive()) {
    kuopio_prog->use();
    kuopio_prog->set_float("amount", sin(0.005f * SDL_GetTicks()) * sin(0.012f * SDL_GetTicks()) * 0.5f + 0.5f);
    kuopio_prog->unuse();
  }

  current_level->render_fellow_start_positions = show_editor;
  current_level->render();
  dave->get_active_ability()->render();

  if (ext.fbo)
    fbo_accum_end();

  // Renderöinnin ajo efektimielessä loppuu tähän ja nyt rendataan loput ketjun efektit

  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  if (ext.fbo && ext.shaders) {
    if (dave->is_dead()) {
      noise->set_bottom(0);
      noise->set_top(600);
      noise->set_opacity(1.0f);
      noise->render();
    }
    else if (dave->is_dying()) {
      //noise->set_bottom(80);
      //noise->set_top(200);
      noise->set_bottom(0);
      noise->set_top(600);
      noise->set_opacity(dave->death_animation_state());
      noise->render();
    }
    if (remote->get_pressed() == Remote::REWIND) {
      float r = (sin(physics_time) * 0.5f + 0.5f) + 0.3f * (float(rand()) / RAND_MAX);
      tape->set_bend(1.0f + (r - 0.5f));
      if (r > 0.3f)
        r = 0.0f;
      tape->set_offset(r);
    } else
      if (!tape->is_normalizing())
        tape->normalize();
  }

  fbo_accum_begin(); {
    Image *i = img_play;
    animation_speed = 1.0f;
    if (remote->get_pressed() == Remote::REWIND) animation_speed = -3.0f;
    if (remote->vcr_empty() || remote->is_paused()) animation_speed = 0.0f;
    if (animation_speed == -3.0f) i = img_rewind;
    else if (animation_speed == 0.0f) i = img_stop;
    if (remote->get_pressed() == Remote::RECORD) i = img_record;
    if (i == img_play)
      play_showed_for++;
    else
      play_showed_for = 0;
    if (play_showed_for < 30 || remote->get_pressed() == Remote::PLAY) {
      i->bind();
      glMatrixMode(GL_PROJECTION);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0.0, 800.0, 0.0, 600.0, -1.0, 1.0);
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();

      glBegin(GL_QUADS);
      glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
      glTexCoord2f(0.0f, 1.0f); glVertex2f(50.0f, 500.0f);
      glTexCoord2f(0.0f, 0.0f); glVertex2f(50.0f, 580.0f);
      glTexCoord2f(1.0f, 0.0f); glVertex2f(130.0f, 580.0f);
      glTexCoord2f(1.0f, 1.0f); glVertex2f(130.0f, 500.0f);
      glEnd();

      glPopMatrix();
      glMatrixMode(GL_PROJECTION);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
    }
  } fbo_accum_end();

  if (ext.fbo && ext.shaders) {
    motion_blur->render();
    gaussian_blur->render();
  }
  if (ext.fbo && ext.shaders) {
    tape->render();
  }
  if (ext.fbo) {
    poweron->render();
    poweroff->render();
    pixels2cubes->set_phase(fmod(physics_time, 5.0f) / 5.0f);
    //pixels2cubes->render();
  }

  /* burg
  if (ext.fbo)
    fbo_accum_begin();
  for (list<Fellow *>::const_iterator i = current_level->fellows_begin(); i != current_level->fellows_end(); ++i)
    (*i)->effect();
  if (ext.fbo)
    fbo_accum_end();
  */

  if (ext.fbo && ext.shaders) {
    tv->render();
  }

  remote->render_screen_to_texture();
  fbo_accum_begin();
  remote->render_remote();
  fbo_accum_end();

  // Copy the result on the screen
  if (ext.fbo) {
    Effect::draw_fbo(true);
  }

  if (show_editor)
    editor.render();
  if (!show_editor && get_editor_anim() > 0.0f)
    editor.render();

  // FPS
  static float lastTicks = 0.0f;
  static unsigned int frames = 0;
  float ticks = SDL_GetTicks();

  if( ticks - lastTicks > 1000.0f ) {

    std::stringstream fpsText;
#if 1
	fpsText << "FPS:";
	fpsText << frames / (ticks - lastTicks) * 1000;
#else
        //fpsText << dave->get_position() << endl << scroll;
#endif

	//text->set_text(fpsText.str());

    lastTicks = ticks;
    frames  = 0;
  }

  text->render(true);
  frames++;
}

Level *Game::get_current_level()
{
  return current_level;
}

const Game::Extensions *Game::get_ext() const
{
  return &ext;
}

float Game::get_editor_anim() const
{
  // Return value: 0   editor not shown at all
  //               0.5 halfway
  //               1   editor fully shown
  const float d = 0.333f;
  const float time = 0.001f * SDL_GetTicks();
  if (time < editor_start_time)
    return 0.0f;
  const float e = time - editor_start_time;
  if (e >= d)
    return show_editor ? 1.0f : 0.0f;
  else if (0.0f <= e && e <= d)
    return show_editor ? (e/d) : (1.0f - e/d);
  else
    return 0.0f;
}

bool Game::main_menu(bool fin)
{
  const bool gfin = game_completed;
  render();
  Mix_Music *vaapukka = Mix_LoadMUS("audio/Kirahvi_nimelta_Tuike_-_Vaapukkamehulaulu.ogg");
  Mix_PlayMusic(vaapukka, -1);
  if (fin)
    Mix_SetMusicPosition(224.0);
  Mix_VolumeMusic(SDL_MIX_MAXVOLUME);

  if (teletext.empty()) {
    palette['0'] = (Color){1.0f, 1.0f, 1.0f, 1.0f};
    palette['1'] = (Color){1.0f, 0.0f, 0.0f, 1.0f};
    palette['2'] = (Color){0.0f, 1.0f, 0.0f, 1.0f};
    palette['3'] = (Color){0.0f, 0.0f, 1.0f, 1.0f};
    palette['4'] = (Color){1.0f, 1.0f, 0.0f, 1.0f};
    palette['5'] = (Color){0.0f, 1.0f, 1.0f, 1.0f};
    palette['6'] = (Color){1.0f, 0.0f, 1.0f, 1.0f};
    palette['7'] = (Color){0.5f, 0.5f, 0.5f, 1.0f};
    palette['8'] = (Color){0.0f, 0.0f, 0.0f, 1.0f} + 0.07f;
    palette['9'] = (Color){0.0f, 0.0f, 0.0f, 0.0f};

    set<int> pages;
    struct dirent *dir;
    DIR *d = opendir("graphics");
    if (d) {
      while ((dir = readdir(d)) != NULL) {
        const char *name = dir->d_name;
        if (strlen(name) == 10 &&
          name[0] == 'p' &&
          name[1] == 'a' &&
          name[2] == 'g' &&
          name[3] == 'e' &&
          name[7] == '.' &&
          name[8] == 't' &&
          name[9] == 'v') {
            const char str[4] = {name[4], name[5], name[6], '\0'};
            pages.insert(atoi(str));
        }
      }
      closedir(d);
    }

    pages.insert(-1);
    //int pages[] = {100, -1, 596, 106, 105};
    for (set<int>::const_iterator i=pages.begin(); i!=pages.end(); ++i) {
      page = *i;
      teletext[page] = new Teletext();
      teletext[page]->set_text(string(41 * 26, ' '));
      ostringstream fname;
      fname << "graphics/page" << page << ".tv";
      ifstream main_text(fname.str().c_str(), ios_base::in | ios_base::binary);
      string::size_type pos = 0;
      for   (int y=0; y<25; y++) {
	const int w = 40;
	char c[w], f[w], b[w];
	for (int x=0; x<w; x++) c[x] = main_text.get();                                main_text.get();
	for (int x=0; x<w; x++) cerr << c[x]; cerr << endl;
	for (int x=0; x<w; x++) main_text >> f[x], assert('0' <= f[x] && f[x] <= '9'); main_text.get();
	for (int x=0; x<w; x++) main_text >> b[x], assert('0' <= b[x] && b[x] <= '9'); main_text.get();
	for (int x=0; x<w; x++) {
	  float *fore = palette[f[x]], *back = palette[b[x]];
	  teletext[page]->set_color(  fore[0], fore[1], fore[2], fore[3]);
	  teletext[page]->set_bgcolor(back[0], back[1], back[2], back[3]);
	  teletext[page]->set_char(string(1, c[x]), pos++);
	}
	teletext[page]->set_char(string(1, '\n'), pos++);
      }
    }
  }

  page_final = page_goal = page_num = page = 100;
  digit = 0;

  const int hyvaa = 3;
  const int hiiva = hyvaa;
  const int yksin = 1;
  const int syo = 0;
  while (hiiva == hyvaa && yksin + syo) {

    SDL_Event e;
    bool handled = false;
    while (SDL_PollEvent(&e)) {
      switch (e.type) {
      case SDL_QUIT:
	return false;
      case SDL_KEYDOWN:
	{
	  const Uint32 s = e.key.keysym.sym;
	  const bool pressed = SDLK_0   <= s && s <= SDLK_9 ||
	                       SDLK_KP0 <= s && s <= SDLK_KP9;
	  const Action a = handle_teletext(&e);
	  if      (a == QUIT)       return false;
	  else if (a == START_GAME) return true;
	  if (s == SDLK_ESCAPE)
	    if (!fin)
	      return false;
	    else
	      return true;
	  if (gfin) {
	    if (credits)
	      return false;
	    credits = true;
	    return true;
	  }
	  if (fin)
	    return true;
	  if (credits)
	    credits = false;

	  handled = true;
	  if (a != STOP) {
	    if (pressed && digit == 0)
	      page_goal = 0;
	    int digits[3] = {
	      (page_goal       - page_goal % 100) / 100,
	      (page_goal % 100 - page_goal % 10 ) / 10 ,
	       page_goal % 10
	    };
	    if (s == SDLK_0 || s == SDLK_KP0) digits[digit] = 0;
	    if (s == SDLK_1 || s == SDLK_KP1) digits[digit] = 1;
	    if (s == SDLK_2 || s == SDLK_KP2) digits[digit] = 2;
	    if (s == SDLK_3 || s == SDLK_KP3) digits[digit] = 3;
	    if (s == SDLK_4 || s == SDLK_KP4) digits[digit] = 4;
	    if (s == SDLK_5 || s == SDLK_KP5) digits[digit] = 5;
	    if (s == SDLK_6 || s == SDLK_KP6) digits[digit] = 6;
	    if (s == SDLK_7 || s == SDLK_KP7) digits[digit] = 7;
	    if (s == SDLK_8 || s == SDLK_KP8) digits[digit] = 8;
	    if (s == SDLK_9 || s == SDLK_KP9) digits[digit] = 9;
	    if (pressed) {
	      page_goal = 100 * digits[0] +
		          10  * digits[1] +
		          1   * digits[2];
	      if (++digit > 2)
		digit = 0;
	    }
	  }
	}
	break;
      }
    }
    if (!handled) {
      Action a = handle_teletext(NULL);
      if      (a == QUIT)       return false;
      else if (a == START_GAME) return true;
    }

    if (ext.fbo && ext.shaders)
      fbo_accum_begin();

    glClearColor(0.07, 0.07, 0.07, 0.0);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    if (digit == 0)
      for (int i=0; i<25; i++)
	if (page_num != page_goal) {
	  page_num++;
	  if (page_num > 899)
	    page_num = 100;
	}
    if (page_num == page_goal)
      page = page_num;
    page_final = teletext.count(page) ? page : -1;
    if (fin)
      page_final = 999;
    if (gfin)
      page_final = 998;

    ostringstream number;
    number << (digit==0 ? page_num : page_goal);
    string number_str = number.str();
    if (digit == 1) number_str = string(1, number_str[0]) + "  ";
    if (digit == 2) number_str = string(1, number_str[0]) + string(1, number_str[1]) + " ";
    if (page_num != page_goal || digit > 0)
      teletext[page_final]->set_colors(palette['1'], palette['8']);
    else
      teletext[page_final]->set_colors(palette['4'], palette['8']);
    teletext[page_final]->replace(number_str, 8);
    teletext[page_final]->set_position(Vector(0, 246));
    teletext[page_final]->render();
    teletext[page_final]->set_scale(1.0f, 1.0f);

    // Credits
    if (credits) {
      static bool prev_credits;
      static float start;
      if (!prev_credits) {
	static Mix_Music *noman;
	if (!noman)
	  noman = Mix_LoadMUS("audio/Delicious_Orange_-_No-Man.ogg");
	Mix_PlayMusic(noman, -1);
	start = 0.001f * SDL_GetTicks();
      }

      glClearColor(0,0,0,0);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      lib["credits.png"]->bind();

      glEnable(GL_TEXTURE_2D);
      glDisable(GL_LIGHTING);
      glDisable(GL_CULL_FACE);
      glDisable(GL_BLEND);

      glMatrixMode(GL_PROJECTION);
      glPushMatrix();
      glLoadIdentity();
      glOrtho(0.0, 800.0, 0.0, 600.0, -1.0, 1.0);
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();
      glMatrixMode(GL_TEXTURE);
      glPushMatrix();
      glLoadIdentity();

      float y = 0.001f * SDL_GetTicks() - start;
      y -= 0.0f;
      y *= 33.0f;
      if (y > 2000.0f)
	credits = false;
      const float sx = 1.35f;
      const float sy = 1.35f;

      glBegin(GL_QUADS);
      glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f         - 5.0f, (-2048.0f + y) * sy);
      glTexCoord2f(1.0f, 1.0f); glVertex2f(1024.0f * sx - 5.0f, (-2048.0f + y) * sy);
      glTexCoord2f(1.0f, 0.0f); glVertex2f(1024.0f * sx - 5.0f, (0.0f     + y) * sy);
      glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f         - 5.0f, (0.0f     + y) * sy);
      glEnd();

      glMatrixMode(GL_TEXTURE);
      glPopMatrix();
      glMatrixMode(GL_PROJECTION);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
      glPopMatrix();

      prev_credits = credits;
      //gaussian_blur->amount = 0.01f;
      //gaussian_blur->mosaic = 2.1f;
    }

    if (ext.fbo && ext.shaders) {
      fbo_accum_end();
      if (ext.gen_mipmap)
	fbo_gen_mipmaps();
    }

    //if (credits && ext.fbo && ext.shaders) gaussian_blur->render();

    if (ext.fbo && ext.shaders) {
      tv->render();
    }

    if (ext.fbo && ext.shaders)
      Effect::draw_fbo(true);

    //SDL_Delay(1);
    SDL_GL_SwapBuffers();
  }
  return true;
}

Game::Action Game::handle_teletext(SDL_Event *ev)
{
  Teletext *text = teletext[page_final];


  ostringstream clock;
#if 0
  clock << setw(2) << setiosflags(ios::fixed) << setprecision(2)
        << 0 << ':'
        << ((SDL_GetTicks() / 1000 / 60) % 60) << ':'
        << ((SDL_GetTicks() / 1000) % 60);
#else
  char asdf[256];
  sprintf(asdf,
          "%02i:%02i:%02i",
          0,
          (SDL_GetTicks() / 1000 / 60) % 60,
          (SDL_GetTicks() / 1000) % 60);
  clock << asdf;
#endif

  text->set_colors(palette['0'], palette['9']);

  text->set_colors(palette['0'], palette['9']);
  text->replace("   DAVE ###   1.1.2009 ##:##:## 1/1", 0);
  text->replace(clock.str(), 25);


  switch (page) {

  case 100:  // Main page
    {
      /*
      string hiiva;
      for (int i=0; i<10; i++)
	hiiva += 'a' + (rand()%26);

      text->set_colors(palette['4'], palette['9']);
      text->replace(hiiva, 16*41 + 25);
      text->replace(hiiva, 17*41 + 25);
      text->replace(hiiva, 18*41 + 25);
      text->replace(hiiva, 19*41 + 25);
      */
    }
    break;

  case 105:  // Start game
    //Mix_FadeOutMusic(2/*000*/);
    if (ext.fbo && ext.shaders)
      tv->zoom_in();
    if (1) {  // 0 == resume, 1 == new game
      switch_level(0);
      get_current_level()->reset();
      dave->set_position(current_level->find_start_position(dave->get_type()));
      dave->set_velocity(Vector());
      dave->go_left(false);
      dave->go_right(false);
    }
    return START_GAME;

  case 110:  // Configurations main page
    {
      static int cursor = 10;
      string sval;
      static int value = 0;
      if (ev) {
        if (ev->type == SDL_KEYDOWN) {
          if (ev->key.keysym.sym == SDLK_DOWN) cursor++;
          if (ev->key.keysym.sym == SDLK_UP) cursor--;
          if (cursor <  10) cursor = 21;
          if (cursor == 16) cursor = 19;
          if (cursor == 18) cursor = 15;
          if (cursor >  21) cursor = 10;
          sval = text->get_text().substr(cursor*41 + 35, 4);
          if (sval.substr(0, 4) == "auto") value = 0;
          if (sval.substr(0, 2) == "on"  ) value = 1;
          if (sval.substr(0, 3) == "off" ) value = 2;
          if (ev->key.keysym.sym == SDLK_LEFT) value--;
          if (ev->key.keysym.sym == SDLK_RIGHT) value++;
          if (value < 0) value = 2;
          if (value > 2) value = 0;
        }
      }

      text->set_colors(palette['2'], palette['3']);
      for (int line=10; line<22; line++)
        if (line < 16 || line > 18)
          text->replace(".", line*41 + 34);
      text->set_colors(palette['1'], palette['3']);
      text->replace(">", cursor*41 + 34);

      if (value == 0) sval = "auto";
      if (value == 1) sval = "on  ";
      if (value == 2) sval = "off ";
      text->set_colors(palette['4'], palette['3']);
      text->replace(sval, cursor*41 + 35);
    }
    break;

  case 140:  // Credits
    credits = true;
    page_goal = 100;
    break;

  case 200:
    return QUIT;

  }
  return NOTHING;
}

void audio_post_effect(void *udata, Uint8 *stream, int len)
{
  //return;
  if (!stream || !len || !game || !game->get_remote())
    return;
  assert((len & 3) == 0);
  static int repeat;
  static int hold;
  bool paused = game->get_remote()->is_paused();
  static bool was_paused;
  const bool rewinding = game->get_remote()->get_pressed() == Remote::REWIND;
  static bool was_rewinding;
  const bool playback = !rewinding;
  const bool was_playback = !was_rewinding;
  static const unsigned int BIG_SIZE = 1024*1024;
  static Uint8 *big; if (!big) big = new Uint8 [BIG_SIZE];
  static unsigned int big_pos;  // Point in the buffer, beginning from which the stuff should be played next
  static unsigned int big_len;  // How many bytes should be played
  int times;
  static bool accel;  // false --> slowing down, true --> accelerating
  static bool force_pause;

  if (!was_rewinding && rewinding) {         // Rewinding started
    force_pause = true;
  } else if (was_rewinding && !rewinding) {  // Rewinding finished
    force_pause = true;
  }

  paused |= force_pause;

  if (!was_paused && paused) {   // Pause started
    repeat = 0;
    hold = 0;
    big_pos = 0;
    big_len = 0;
  } else if (was_paused && !paused) {  // Resumed from pause
    repeat = 0;//100;
    hold = 0;
    big_pos = 0;
    big_len = 0;
    accel = false;//true;
  } else if (paused) {
    hold++;
    if      (repeat <  2) times = 7;
    else if (repeat == 3) times = 6;
    else if (repeat == 4) times = 5;
    else if (repeat == 5) times = 4;
    else if (repeat == 6) times = 3;
    else if (repeat == 7) times = 2;
    else if (repeat == 8) times = 1;
    else                  times = 0;
    if (hold > times)
      hold = 0, repeat++;
    repeat += repeat / 10;
  } else if (accel == false) {
    repeat = 0;
  } else if (accel) {
  }

  if (repeat > 70) {  // Slowdown at pause finished
    if (force_pause == false) {
      Mix_PauseMusic();//cerr<<"-------------------------------pausemusic\n";
      goto ret;
    } else {
      force_pause = false;
      Level *l = game->get_current_level();
      const float pos = l->get_music_position();
      Mix_HaltMusic(),cerr<<"-------------------------------haltmusic\n";
      l->play_music(rewinding);
    //l->set_music_position((l->get_music_duration() - pos) / (rewinding ? 3.0f : 1.0f/3.0f));
      l->set_music_position((float(rand()) / RAND_MAX) * l->get_music_duration() / (rewinding ? 3.0f : 1.0f));
      goto ret;
    }
  }

  if (repeat) {
    // Appending current buffer to the big buffer
    if (big_pos + len >= BIG_SIZE)
      big_pos = big_len = 0;       // This should never happen but if it happens, reset the buffer
    memcpy(big + big_pos, stream, len);
    big_len += len;
    // Repeating samples. repeat==1 --> AABBCCDD, repeat==2 --> AAABBBCCCDDD, repeat==3 --> AAAABBBBCCCCDDDD etc.
    // TODO: dithering and interpolating
    for (Uint8 *p = stream + len - 4; p > stream; p -= 4) {
      Uint8 *q = big + big_pos + (((p - stream) / (repeat + 1)) & ~3);
      for (int i=0; i<4; i++)
        p[i] = q[i];
    }
    const unsigned int forward = len / (repeat + 1);
    big_pos += forward;
    big_pos &= ~3;
    big_len -= forward;
    big_len &= ~3;
  }

ret:
  was_paused = paused;
  was_rewinding = rewinding;
}

void music_finished_callback()
{
  game->get_current_level()->play_music(false, true);  // Start the music again when it ends
}

void Game::save_config() const
{
  ofstream f("dave.conf", ios_base::out | ios_base::binary);
  for (map<string, string>::const_iterator i=config.begin(); i!=config.end(); ++i) {
    f << i->first;
    int si = 20 - i->first.size();
    if (si < 1)
      si = 1;
    for (; si; si--)
      f << ' ';
    f << i->second << endl;
  }
}

void Game::load_config()
{
  // Parsing file
  config.clear();
  ifstream f;
  f.open("dave.conf", ios_base::in | ios_base::binary);
  if (!f.good()) {
    f.close();
    f.clear();
    f.open("dave.conf.sample", ios_base::in | ios_base::binary);
  }
  if (!f.good()) {
    cerr << "Could not load config file!" << endl;
    exit(1);
  }
  while (f.good()) {
    string key, value;
    char c = 0;
    int word = 0;
    while (f.good()) {
      c = f.get();
      if (c == 10 || c == 13)
        break;
      if (c == ' ' || c == '\t') {
        word = 1;
        continue;
      }
      if (word == 0)
        key += c;
      else
        value += c;
    }
    if (key.size() && (key[0] == '#' || key[0] == ';'))
      continue;
    if (!key.size() || !isalpha(key[0]))
      continue;
    config[key] = value;
    cerr << "read: [" << key << "] = \"" << value << "\"\n";
  }

  // Applying configuration
  if (config["fbo"            ] == "off") ext.fbo = false;
  if (config["vbo"            ] == "off") ext.vbo = false;
  if (config["shaders"        ] == "off") ext.shaders = false;
  if (config["simple_shaders" ] == "on" ) ext.simple_shaders = true;
  if (config["teletext_mipmap"] == "off") ext.gen_mipmap = false;
  if (config["antialias"      ] == "off") ext.antialias = false;
}

string Game::get_level_filename() const
{
  switch (current_level_number) {
    case 0: return "levels/practice.dave";
    case 1: return "levels/avaruus.dave";
    case 2: return "levels/ruohikko.dave";
    case 3: return "levels/videonauhuri.dave";
    default:
      throw;
  }
}

void Game::switch_level(int num)
{
  current_level_number = num;
  ifstream ll(get_level_filename().c_str(), ios_base::in | ios_base::binary);
  if (ll.good())
    current_level->load(ll);
  ll.close();
  current_level->reset();
  if (dave)
    if (num == 0) {
      dave->add_voltage(1000.0f);
      dave->add_ability(new Ability(Ability::ROCKET_PACK));
    } else if (num == 1) {
      dave->sub_voltage(10000.0f);
      dave->select_ability(Ability::ROCKET_PACK);
      dave->remove_ability();
      dave->select_ability(Ability::LASER_PULSE);
    }
}
