/* 
 *  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 "analog_effects.h"
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <math.h>
#include <iostream>
#include <sstream>
#include <signal.h>
#include "library.h"
#include "image.h"
#include "game.h"
#include "model.h"
#include "fellow.h"

#if defined(__MACOS__) || defined(__APPLE__)
#include <SDL/SDL_main.h>
#include <CoreFoundation/CoreFoundation.h>
#endif

using namespace std;

#ifndef M_PI
#define M_PI 3.14159265358
#endif

GLFramebuffer    *Effect::framebuffer = NULL;
GLColorTexture2D *Effect::ping = NULL,
                 *Effect::pong = NULL,
                 *Effect::current_tex = NULL;

GLColorTexture2D *Effect::not_current_tex()
{
  assert(ping);
  assert(pong);
  return current_tex==ping ? pong : ping;
}

bool Effect::head_or_tails()
{
  return current_tex == ping;
}

void Effect::init()
{
  if (framebuffer == NULL) {
    framebuffer = new GLFramebuffer();
    GLDepthRenderbuffer *depth = new GLDepthRenderbuffer(800, 600);
    framebuffer->set_depth(*depth);
    ping = new GLColorTexture2D(GL_RGB8, 1?800:1024, 1?600:1024);
    pong = new GLColorTexture2D(GL_RGB8, 1?800:1024, 1?600:1024);
    ping->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    pong->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    current_tex = ping;
    flip();
  }
  assert(framebuffer);
  assert(ping);
  assert(pong);
}

void Effect::flip()
{
  current_tex = not_current_tex();
  current_tex->parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  assert(framebuffer);
  framebuffer->set_color(*current_tex, 0);
}

void Effect::bind_fbo_texture()
{
  assert(current_tex);
  current_tex->bind(0);
}

void Effect::bind_other_fbo_texture(int unit)
{
  not_current_tex()->bind(unit);
}

void Effect::fbo_chain_begin()
{
  init();
  flip();
  assert(framebuffer);
  framebuffer->bind();
#if !defined(__MACOS__) && !defined(__APPLE__)
  if (game->get_ext()->antialias)
    glRenderbufferStorageMultisampleEXT(/*GLenum target*/GL_FRAMEBUFFER_EXT, /*GLsizei samples*/4, /*GLenum internalformat*/GL_RGBA, 800, 600);
#endif
}

void Effect::fbo_chain_end()
{
  assert(framebuffer);
  framebuffer->unbind();
}

void Effect::fbo_gen_mipmaps()
{
  bind_fbo_texture();
  glGenerateMipmapEXT(GL_TEXTURE_2D);
  current_tex->parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
}

void Effect::fbo_reset()
{
  assert(framebuffer);
  assert(ping);
  assert(pong);
  delete framebuffer;
  delete ping;
  delete pong;
  framebuffer = NULL;
  ping = pong = NULL;
  init();
}

void Effect::fbo_accum_begin()
{
  init();
  assert(framebuffer);
  framebuffer->bind();
}

void Effect::fbo_accum_end()
{
  assert(framebuffer);
  framebuffer->unbind();
}

void Effect::draw_fbo(const bool flip)
{
  glDisable(GL_LIGHTING);
  glEnable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
  bind_fbo_texture();
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  draw_fullscreen_quad(flip);
}

Effect::Effect()
{
}

Effect::~Effect()
{
}

void Effect::render()
{
}





Power::Power()
{
  init = -1.0e100;
  mode = POWERON;
  launched = false;
  doing = false;
}

Power::~Power()
{
}

void Power::render()
{
  const float time = 0.001 * SDL_GetTicks();
  if (launched == false)
    init = time;
  float d = time - init;

  if (game->get_ext()->fbo && game->get_ext()->shaders) {
    //float *gb = &game->get_gaussian()->amount;
    static bool doing;
    if ((doing || 1) && mode == POWERON)
      game->get_gaussian()->amount = 0.0f;    ostringstream o; //o << d;
    if (mode == POWERON && launched && d >= 0.0f && d < 7.0f)
      game->get_gaussian()->amount = (7.0f - d) / 7.0f, doing = true;

    const float mt = /*0.18f*/10.0f, rmt = 1.0f / mt;
    if (game->item_time() < mt) { // This doesn't belong here!!!
      game->get_gaussian()->amount = (rmt - rmt * game->item_time()) / rmt;
      game->get_gaussian()->mosaic = 5.0f; //(rmt - rmt * game->item_time()) / rmt * 2.0f + 1.0f;
    } else
      game->get_gaussian()->mosaic = 1.0f;
  }

  // Optimoidaan piirtoja pois jos rendauksesta ei oikiasti olis mitään hyötyä
  if (mode == POWERON && launched && d > 7.0f) {
    doing = false;
    return;
  }
  else if (mode == POWEROFF && launched && d > 1.0f) {
    fbo_accum_begin();
    glClearColor(0,0,0,0);
    glClear(GL_COLOR_BUFFER_BIT);
    fbo_accum_end();
    return;
  }
  else if (mode == POWEROFF && !launched)
    return;


  fbo_chain_begin();

  const int scrsize = sizeof(screen) / sizeof(float[2]);

  d *= 2.8f;
  float D = 0.0035f;
  const int phase = int(d) <3 ? int(d) : 3;

  if (phase < 2) {   // Loodasta viivaksi
    float (*right)[2] = &screen[0];  // 02:43:17 <@Apaksi> Ihme Juttu.
    float (*top)  [2] = &screen[20]; // 02:43:34 <@Hox> C:n kielioppi on aika Ihme Juttu. :P
    float (*left) [2] = &screen[40]; // C On.
    float (*botm) [2] = &screen[60];
    const float q = float(scrsize/4);
    D += exp(14.0f * -d);
#define SHRINK(side) pow(fabs(side[i][1] / 100.0f), 2.0f)
    for (int i=0; i<scrsize/4; i++)
      right[i][0] = 100.0f,
      right[i][1] = D * (200.0f * (i/q) - 100.0f),
      right[i][0] *= 1.0f - 1.0f * (1.0f-D) * SHRINK(right);
    for (int i=0; i<scrsize/4; i++)
      top[i][0] = (-200.0f * (i/q) + 100.0f) * SHRINK(top),
      top[i][1] = D * 100.0f;
    for (int i=0; i<scrsize/4; i++)
      left[i][0] = -100.0f,
      left[i][1] = D * (-200.0f * (i/q) + 100.0f),
      left[i][0] *= 1.0f - 1.0f * (1.0f-D) * SHRINK(left);
    for (int i=0; i<scrsize/4; i++)
      botm[i][0] = (200.0f * (i/q) - 100.0f) * SHRINK(botm),
      botm[i][1] = D * -100.0f;
  }

  // Viivasta palloksi
  if(0.0f <= d && d < 2.0f){
    const float S = d / 2.0f;
    for (int i=0; i<scrsize; i++) {
      const float f = 2.0f - pow(2, 2*S);
      screen[i][0] *= f>0 ? f : 0;
    }
  }

  // Pallosta pois
  if (1.0f < d && d < 3.0f) {
    for (int i=0; i<scrsize; i++) {
      const float a = 2.0f*M_PI / scrsize * i - 0.25*M_PI;
      screen[i][0] = D * 100.0f * (3.0f/4.0f) * cos(a);
      screen[i][1] = D * 100.0f *               sin(a);
    }
  }

  // Musta ruutu
  if (phase == 3)
    memset(screen, 0, sizeof(screen));

  // Kokonainen loota
  if (mode == POWERON) {
    float (*right)[2] = &screen[0];
    float (*top)  [2] = &screen[20];
    float (*left) [2] = &screen[40];
    float (*botm) [2] = &screen[60];
    const float q = float(scrsize/4);
    for (int i=0; i<scrsize/4; i++)
      right[i][0] = 100.0f,
      right[i][1] = 200.0f * (i/q) - 100.0f;
    for (int i=0; i<scrsize/4; i++)
      top[i][0] = -200.0f * (i/q) + 100.0f,
      top[i][1] = 100.0f;
    for (int i=0; i<scrsize/4; i++)
      left[i][0] = -100.0f,
      left[i][1] = -200.0f * (i/q) + 100.0f;
    for (int i=0; i<scrsize/4; i++)
      botm[i][0] = 200.0f * (i/q) - 100.0f,
      botm[i][1] = -100.0f;
  }

  // Rendaus
  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
#if 1
  glClear(GL_COLOR_BUFFER_BIT);
#else
  glEnable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
  lib["black.png"]->bind();
  draw_fullscreen_quad(true);
#endif

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

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-100, 100, -100, 100, -1, 1);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  bind_other_fbo_texture();

  for (int pass=0; pass</*12*/2; pass++) {
    if (!pass)
      glDisable(GL_BLEND);
    else {
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    }

    const float opacity = 1.0f - (d-0.2f)/(1.5f-0.2f);  // 0.2..1.5 --> 1..0

    const float on_brightness = d<0.5f ? 2.0f * d : 1.0f; // 0..0.5 --> 0..1
    const float remain_white = 2.0f - 2.0f / 3.0f;
    float on_opacity;
    if (d < 0.5f + remain_white)
      on_opacity = 1.0f;
    else
      //lineaarinen:
      //on_opacity = 1.0f - (d-0.5f) / (15.0f-0.5f);      // 0.5..15 --> 1..0
      //titraus:
      on_opacity = 2.0f * (1.0f - 1.0f / (1.0f + exp(-0.30f * (d - (0.5f + remain_white)))));

    if (mode == POWEROFF)
      if (d >= 0.5f) {
	if (pass)
	  continue;
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      }

    if (mode == POWERON) {
      if (pass == 0) {       // Piirretään kuva normaalisti
      }
      else if (pass == 1) {  // Blendauskerros
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      }
      else if (pass == 2)
	break;
    }

    float alpha = 1.0f - D;

    glColor4f(1.0f, 1.0f, 1.0f, alpha);
    if (d >= 0.5f) {
      glColor4f(1,1,1, opacity);
      glDisable(GL_TEXTURE_2D);
    }
    else
      glEnable(GL_TEXTURE_2D);

    if (mode == POWERON)
      if (pass == 0)
      {glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND);}
      else
      {glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);}

    if (mode == POWERON)
      if (pass == 0)
	glColor4f(1,1,1,1);
      else
	glColor4f(on_brightness,
		  on_brightness,
		  on_brightness,
                  on_opacity);

    glBegin(GL_POLYGON);

    glTexCoord2f(0.5f, 0.5f);
    glVertex2f(0.0f, 0.0f);

    for (int i=0; i<scrsize+1; i++) {

      if (mode == POWEROFF) {
	glColor4f(1.0f, 1.0f, 1.0f, alpha);
	if (d >= 0.5f)
	  glColor4f(1,1,1, opacity);
      }

      float tx, ty;
      const int ii = i % scrsize;
      const int sc = ii / (scrsize/4);
      const float pos = float(ii - sc * (scrsize/4)) / float(scrsize/4);
      switch (sc) {
      case 0: tx = 1.0f      ; ty = 1.0f - pos; break;
      case 1: tx = 1.0f - pos; ty = 0.0f      ; break;
      case 2: tx = 0.0f      ; ty =        pos; break;
      case 3: tx =        pos; ty = 1.0f      ; break;
      }
      //if (head_or_tails())
      //  ty = 1.0f - ty;

      glTexCoord2f(tx, 1.0f - ty);
      glVertex2fv(&screen[ii][0]);
    }
    glEnd();
  }

  fbo_chain_end();
}

void Power::launch()
{
  launched = true;
  init = 0.001f * SDL_GetTicks();
}

void Power::unlaunch()
{
  launched = false;
}

void Power::set_mode(Mode m)
{
  mode = m;
}





JupetJaLoota::JupetJaLoota()
{
  ralli = lib["ralli.png"];
}

void JupetJaLoota::render()
{
  fbo_accum_begin();
  ralli->bind();
  //draw_fullscreen_quad(true);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  const float a = 0.07f * SDL_GetTicks();
  glRotatef(a, sin(0.1*a), sin(0.11*a), sin(0.134*a));
  glMatrixMode(GL_TEXTURE);
  glPushMatrix();
  glLoadIdentity();

  glBegin(GL_QUADS);
  const bool i = true;
  glTexCoord2f(0.0f, i ? 0.0f : 1.0f);
  glVertex2f(-1.0f, -1.0f);
  glTexCoord2f(1.0f, i ? 0.0f : 1.0f);
  glVertex2f(1.0f, -1.0f);
  glTexCoord2f(1.0f, i ? 1.0f : 0.0f);
  glVertex2f(1.0f, 1.0f);
  glTexCoord2f(0.0f, i ? 1.0f : 0.0f);
  glVertex2f(-1.0f, 1.0f);
  glEnd();

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

  fbo_accum_end();
}




/*
class Testi : public Effect
{
  void render()
  {
    fbo_chain_begin();

    fbo_chain_end();
  }
};
*/





void end()
{
  SDL_Quit();
  exit(0);
}

void crash(int signum)
{
  signal(SIGABRT, crash);
  cerr << "kräsh!\n";
  SDL_Quit();
  exit(1);
}

#if 0
int main(int argc, char **argv)
{
  if (SDL_Init(SDL_INIT_VIDEO)) {
    cerr << "Ei pysty.\n";
    exit(1);
  }
  if (!SDL_SetVideoMode(800, 600, 32, SDL_OPENGL /*| SDL_FULLSCREEN*/)) {
    cerr << "Ei pysty.\n";
    exit(1);
  }

  SDL_ShowCursor(SDL_DISABLE);

#if !defined(__MACOS__) && !defined(__APPLE__)
  glewInit();
#endif

  atexit(end);
  signal(SIGABRT, crash);

  JupetJaLoota jupet_ja_loota;
  Power poweron;
  Power poweroff;

  poweron.set_mode( Power::POWERON);
  poweroff.set_mode(Power::POWEROFF);

  bool poweroff_on = true;
  bool poweron_on = true;
  bool quit = false;

  poweron.launch();

  while (!quit) {
    SDL_Event ev;
    while (SDL_PollEvent(&ev))
      if (ev.type == SDL_KEYUP)
	/*
	if (ev.key.keysym.sym == SDLK_SPACE) {
	  poweroff_on = poweroff_on ? false : true;
	  if (poweroff_on)
	    poweroff.unlaunch();
	  else
	    poweroff.launch();
	}
	else if (ev.key.keysym.sym == SDLK_RETURN) {
	  poweron_on = poweron_on ? false : true;
	  if (poweron_on)
	    poweron.launch();
	  else
	    poweron.unlaunch();
	}
	*/
	if (ev.key.keysym.sym == SDLK_SPACE)
	  if (poweroff_on)
	    poweroff_on = false,
	      poweroff.launch();
	  else
	    poweroff_on = true,
	      poweroff.unlaunch(),
	      poweron.unlaunch(),
	      poweron.launch();
	else if (ev.key.keysym.sym == SDLK_ESCAPE)
	  quit = true;

    jupet_ja_loota.render();
    poweron.render();
    poweroff.render();
    Effect::draw_fbo();
    SDL_GL_SwapBuffers();
  }

  SDL_Quit();
  return 0;
}
#endif



Pixels2Cubes::Pixels2Cubes() : phase(1)
{
  const unsigned int blocks = 1875;
  vertex_count = blocks * 8 * 8 * 8;
  index_count  = blocks * 8 * 8 * 6 * 6;
  vertices = new GLfloat [3 * vertex_count];
  indices = new GLuint [index_count];
  texcoords = new GLfloat [2 * vertex_count];
  colors = new GLfloat [3 * vertex_count];

  unsigned int v = 0;
  unsigned int i = 0;
  unsigned int t = 0;
  unsigned int c = 0;

  vertices[v++] = -400.0f; vertices[v++] = -300.0f; vertices[v++] = -3.0f;
  vertices[v++] =  400.0f; vertices[v++] = -300.0f; vertices[v++] = -3.0f;
  vertices[v++] = -400.0f; vertices[v++] =  300.0f; vertices[v++] = -3.0f;
  vertices[v++] =  400.0f; vertices[v++] =  300.0f; vertices[v++] = -3.0f;
  texcoords[t++] = 0.00001f; texcoords[t++] = 0.00001f;
  texcoords[t++] = 1.00001f; texcoords[t++] = 0.00001f;
  texcoords[t++] = 0.00001f; texcoords[t++] = 1.00001f;
  texcoords[t++] = 1.00001f; texcoords[t++] = 1.00001f;
  colors[c++] = 1.0f; colors[c++] = 1.0f; colors[c++] = 1.0f;
  colors[c++] = 1.0f; colors[c++] = 1.0f; colors[c++] = 1.0f;
  colors[c++] = 1.0f; colors[c++] = 1.0f; colors[c++] = 1.0f;
  colors[c++] = 1.0f; colors[c++] = 1.0f; colors[c++] = 1.0f;
  indices[i++] = 0;
  indices[i++] = 1;
  indices[i++] = 2;
  indices[i++] = 1;
  indices[i++] = 3;
  indices[i++] = 2;

  const float step = 2.0f;
  for   (float x=0.0f; x<800.0f; x+=step)
    for (float y=0.0f; y<600.0f; y+=step) {
      const float depth = /*0.0f;*/float(int(x/2+y/2) % 3) * -1.5f * 1.0f;
      const unsigned int v0 = v / 3;
      const float rx = x - 400.0f;
      const float ry = y - 300.0f;
      const float rix = rx - 16.0f;
      const float riy = rx - 46.0f;
      const float e = -0.0f;

      const float r = (rx*rx + ry*ry) / (150.0f * 150.0f);
      if (r > 1.0f)
	continue;
      //const float ri = rix*rix + riy*riy;

      /* 0 front bottom left  */ vertices[v++] = rx;            vertices[v++] = ry;            vertices[v++] = depth;
      /* 1 front bottom right */ vertices[v++] = rx + step + e; vertices[v++] = ry;            vertices[v++] = depth;
      /* 2 front top    left  */ vertices[v++] = rx;            vertices[v++] = ry + step + e; vertices[v++] = depth;
      /* 3 front top    right */ vertices[v++] = rx + step + e; vertices[v++] = ry + step + e; vertices[v++] = depth;
      /* 4 back  bottom left  */ vertices[v++] = rx;            vertices[v++] = ry;            vertices[v++] = depth - step;
      /* 5 back  bottom right */ vertices[v++] = rx + step + e; vertices[v++] = ry;            vertices[v++] = depth - step;
      /* 6 back  top    left  */ vertices[v++] = rx;            vertices[v++] = ry + step + e; vertices[v++] = depth - step;
      /* 7 back  top    right */ vertices[v++] = rx + step + e; vertices[v++] = ry + step + e; vertices[v++] = depth - step;

      if (rx == 16.0f && ry == 46.0f) {
	const float f = ((step + e) * (800.0f/600.0f) - (step + e)) / 2.0f;
	vertices[3*(v0 + 0)] -= f;
	vertices[3*(v0 + 1)] += f;
	vertices[3*(v0 + 2)] -= f;
	vertices[3*(v0 + 3)] += f;
	vertices[3*(v0 + 4)] -= f;
	vertices[3*(v0 + 5)] += f;
	vertices[3*(v0 + 6)] -= f;
	vertices[3*(v0 + 7)] += f;

	texcoords[t++] = 0.0f; texcoords[t++] = 1.0f;
	texcoords[t++] = 1.0f; texcoords[t++] = 1.0f;
	texcoords[t++] = 0.0f; texcoords[t++] = 0.0f;
	texcoords[t++] = 0.0f; texcoords[t++] = 0.0f;
	texcoords[t++] = 0.0f; texcoords[t++] = 0.0f;
	texcoords[t++] = 1.0f; texcoords[t++] = 0.0f;
	texcoords[t++] = 0.0f; texcoords[t++] = 0.0f;
	texcoords[t++] = 0.0f; texcoords[t++] = 0.0f;
	for (int j=0; j<8; j++) {
	  colors[c++] = 1.0f;
	  colors[c++] = 1.0f;
	  colors[c++] = 1.0f;
	}
      } else {
	for (int j=0; j<8; j++) {
	  texcoords[t++] = (x + 0.12345f) / 800.0f;
	  texcoords[t++] = (y + 0.12345f) / 600.0f;
	}
	for (int j=0; j<4; j++) {
	  colors[c++] = 1.0f;
	  colors[c++] = 1.0f;
	  colors[c++] = 1.0f;
	}
	for (int j=0; j<4; j++) {
	  colors[c++] = r + 0.3f;
	  colors[c++] = r + 0.3f;
	  colors[c++] = r + 0.3f;
	}
      }/*
      if (ri < 2)
	for (int j=0; j<8; j++) {
	  vertices[3*(v0 + j) + 2] -= -2.0f * (step + e);
	  }*/

      indices[i++] = v0 + 0;  // front
      indices[i++] = v0 + 1;
      indices[i++] = v0 + 2;
      indices[i++] = v0 + 1;
      indices[i++] = v0 + 3;
      indices[i++] = v0 + 2;

      indices[i++] = v0 + 4;  // back
      indices[i++] = v0 + 6;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 6;
      indices[i++] = v0 + 7;

      indices[i++] = v0 + 2;  // left
      indices[i++] = v0 + 4;
      indices[i++] = v0 + 0;
      indices[i++] = v0 + 2;
      indices[i++] = v0 + 6;
      indices[i++] = v0 + 4;

      indices[i++] = v0 + 3;  // right
      indices[i++] = v0 + 1;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 3;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 7;

      indices[i++] = v0 + 2;  // top
      indices[i++] = v0 + 3;
      indices[i++] = v0 + 6;
      indices[i++] = v0 + 3;
      indices[i++] = v0 + 7;
      indices[i++] = v0 + 6;

      indices[i++] = v0 + 0;  // bottom
      indices[i++] = v0 + 4;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 0;
      indices[i++] = v0 + 5;
      indices[i++] = v0 + 1;
    }
  vertex_count = v / 3;
  index_count = i;

  glGenBuffersARB(1, &vertex_buf);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertex_buf);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertex_count * 3 * sizeof(GLfloat), vertices, GL_STATIC_DRAW_ARB);

  glGenBuffersARB(1, &texcoord_buf);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, texcoord_buf);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertex_count * 2 * sizeof(GLfloat), texcoords, GL_STATIC_DRAW_ARB);

  glGenBuffersARB(1, &color_buf);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, color_buf);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertex_count * 3 * sizeof(GLfloat), colors, GL_STATIC_DRAW_ARB);

  glGenBuffersARB(1, &index_buf);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, index_buf);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, index_count * sizeof(GLuint), indices, GL_STATIC_DRAW_ARB);

  delete [] vertices;
  delete [] texcoords;
  delete [] colors;
  delete [] indices;
}

Pixels2Cubes::~Pixels2Cubes()
{
  glDeleteBuffersARB(1, &vertex_buf);
  glDeleteBuffersARB(1, &texcoord_buf);
  glDeleteBuffersARB(1, &color_buf);
  glDeleteBuffersARB(1, &index_buf);
}

void Pixels2Cubes::render()
{
  fbo_chain_begin();

  glEnable(GL_TEXTURE_2D);
  bind_other_fbo_texture();

  glEnable(GL_DEPTH_TEST);

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

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  float tmph = exp(-pow(2.0f - 2.0f * phase, 2.0f));
  if (tmph < 0.0f) tmph = 0.0f;
  gluPerspective(80.0 /* + 72.0 * ((1.0f-fabs(tmph-0.5f)*2.0f))*/, 800.0/600.0, 0.2, 800.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(17.0, 45.0*(fabs(tmph-0.5f)*2.0f) - 0.2, (600.0 - 600.0 * pow(tmph, 0.2f)) - 1.0 - 12.0/*(-30*(1.0f-fabs(tmph-0.5f)*2.0f))*/ * ((1.0f-fabs(tmph-0.5f)*2.0f)),
	    17.0, 46.0                        - 0.2, 0.0                               - 1.0,
	    (1.0f-(fabs(tmph-0.5f)*2.0f))/2.0,  1.0, 0.0);
  glTranslatef(0.0f, 30.0f * (1.0f - tmph), 0.0f);
  // oikia kohta: 17.0, 46.0

  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_BLEND);
  glEnable(GL_CULL_FACE);
  /*
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  Glfloat ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
  Glgloat diffuse[] = { 0.8f, 0.8f, 0.8, 1.0f };
  Glfloat specular[] = { 0.5f, 0.5f, 0.5f, 1.0f };
  Glfloat position[] = { -1.5f, 1.0f, -4.0f, 1.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);
  */

  /*
  GLfloat fogColor[4]= {0.0f, 0.0f, 0.0f, 1.0f};
  glFogi(GL_FOG_MODE, GL_LINEAR);
  glFogfv(GL_FOG_COLOR, fogColor);
  glFogf(GL_FOG_DENSITY, 0.35f);
  glHint(GL_FOG_HINT, GL_DONT_CARE);
  glFogf(GL_FOG_START, 1.0f);
  glFogf(GL_FOG_END, 100.0f);
  glEnable(GL_FOG);
  */

  glColor4f(1, 1, 1, 1);

  glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertex_buf);
  glVertexPointer(3, GL_FLOAT, 0, 0);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, texcoord_buf);
  glTexCoordPointer(2, GL_FLOAT, 0, 0);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, color_buf);
  glColorPointer(3, GL_FLOAT, 0, 0);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, index_buf);
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, 0);
  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);

  glDisable(GL_DEPTH_TEST);
  glDisable(GL_FOG);
  glDisable(GL_LIGHT0);
  glDisable(GL_LIGHTING);
  glDisable(GL_CULL_FACE);

  fbo_chain_end();
}

void Pixels2Cubes::set_phase(const float p)
{
  phase = p;
}

MotionBlur::MotionBlur() : chroma(1.0f), amount(0.3f), prog(NULL), blurred_fbo(NULL), blurred_texture(NULL)
{
  if (game->get_ext()->shaders && game->get_ext()->fbo) {
    prog = new GLProgram("graphics/motion_blur_vert.glsl", "graphics/motion_blur_frag.glsl");
    prog->use();
    prog->set_int("current", 0);
    prog->set_int("blurred", 1);
    prog->unuse();

    blurred_fbo = new GLFramebuffer();
    blurred_texture = new GLColorTexture2D(GL_RGB8, 800, 600);
  }
}

MotionBlur::~MotionBlur()
{
  if (prog)
    delete prog;
}

void MotionBlur::render()
{
  if (!game->get_ext()->shaders || !game->get_ext()->fbo)
    return;

  // Pass 1: Mix old blurred image with current image just rendered --> result to our own private FBO
  blurred_fbo->set_color(*blurred_texture, 0);
  blurred_fbo->bind();
  glDisable(GL_BLEND);
  glEnable(GL_TEXTURE_2D);
  bind_fbo_texture();
  blurred_texture->bind(1);
  glEnable(GL_TEXTURE_2D);
  assert(prog);
  prog->use();
  static float interlace_turn;
  interlace_turn = interlace_turn == 0.0f ? 1.0f : 0.0f;
  //chroma = 0.5f * (1.0f + sin(1.0f * 0.001f * SDL_GetTicks()));
  //chroma = 1.0f;
  prog->set_float("interlace_turn", interlace_turn);
  prog->set_float("chroma", chroma);
  prog->set_float("mblur_amount", amount);
  prog->set_float("posz_amount", sin(0.4f * game->get_physics_time())*0.5f+0.5f);
  draw_fullscreen_quad(true);
  prog->unuse();
  glActiveTextureARB(GL_TEXTURE1); glDisable(GL_TEXTURE_2D);
  glActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D);

  // Pass 2: Copy blurred image to the "screen"
  fbo_chain_begin();
  blurred_texture->bind(0);
  glEnable(GL_TEXTURE_2D);
  draw_fullscreen_quad(true);
  fbo_chain_end();
}

GaussianBlur::GaussianBlur() : amount(0.0f), mosaic(1.0f), prog(NULL)
{
  if (!game->get_ext()->shaders || !game->get_ext()->fbo)
    return;
  prog = new GLProgram("graphics/gaussian_blur_vert.glsl", "graphics/gaussian_blur_frag.glsl");
  prog->use();
  prog->set_int("image", 0);
  prog->unuse();
}

GaussianBlur::~GaussianBlur()
{
  if (prog)
    delete prog;
}

void GaussianBlur::render()
{
  if (!game->get_ext()->shaders || !game->get_ext()->fbo)
    return;
  if (amount == 0.0f)
    return;
  if (amount < 0.0f) amount = 0.0f;
  if (amount > 1.0f) amount = 1.0f;
  int passes = int(amount * 10.0f);
  for (int i=0; i<passes; i++) {
    fbo_chain_begin();
    bind_other_fbo_texture();
    prog->use();
    prog->set_int("pass", i % 2);
    const float kern = fmodf(amount * 10.0f, 1.0f) * 4.0f;
    prog->set_float("s3",               1.0f       );
    prog->set_float("s2", kern > 1.0f ? 1.0f : 0.0f);
    prog->set_float("s4", kern > 2.0f ? 1.0f : 0.0f);
    prog->set_float("s1", kern > 3.0f ? 1.0f : 0.0f);
    prog->set_float("sa", ceilf(kern));
    prog->set_float("mosaic", mosaic);
    prog->set_float("time", 0.001f * SDL_GetTicks());
    glEnable(GL_TEXTURE_2D);
    draw_fullscreen_quad(true);
    prog->unuse();
    fbo_chain_end();
  }
}

TV::TV() : zoom(0.0f), started_zooming_at(-1000.0f), zooming_in(false)
{
  room = new Scene();
  tv = new Model("graphics/telq.3ds");
  tv->set_texture(          lib["toelloe.png"            ]);
  tv->set_self_illumination(&proxy);
  tv->set_environment(      lib["tv_environment.png"     ]);
  tv->set_reflectivity(     lib["tv_reflectivity.png"    ]);
  tv->calculate_ao();
  room->add_model(tv);
  room->set_camera_pos(0.0f, 50.0f, 130.0f);
}

TV::~TV()
{
  delete tv;
  delete room;
}

void TV::render()
{
  const float speed = 5.0f;
  zoom = (0.001f * SDL_GetTicks() - started_zooming_at) / speed;
  zoom = zoom < 0.0f ? 0.0f : zoom > 1.0f ? 1.0f : zoom;
  if (!zooming_in)
    zoom = 1.0f - zoom;
  if (zoom == 1.0f && !game->showing_credits())
    return;

  // Hoopo
  //zoom *= 1.5f;
  //Lagrange interpolation polynomial for 3 points
  const float x0 = 0.0f;
  const float y0 = 0.0f;
  const float x1 = 0.9f;
  const float y1 = 1.1f;
  const float x2 = 1.0f;
  const float y2 = 1.0f;
  const float x = zoom;
  zoom = y0*(x-x1)*(x-x2) / ((x0-x1)*(x0-x2)) + y1*(x-x0)*(x-x2) /
    ((x1-x0)*(x1-x2)) + y2*(x-x0)*(x-x1) / ((x2-x0)*(x2-x1));


  fbo_chain_begin();

  const float a = 16.0 * 0.001 * SDL_GetTicks();
  const float b = 0.3f * 180.0f / M_PI * cos(a / 180.0f * M_PI * 0.4f);
  const float c = 32.0 * 0.001 * SDL_GetTicks();
  const float d = 0.4f * 180.0f / M_PI * cos(c / 180.0f * M_PI * 0.4f);
  const float f = 50.0 * 0.001 * SDL_GetTicks();
  const float g = 0.21f * 180.0f / M_PI * cos(f / 180.0f * M_PI * 0.4f);
  const float iz = 1.0f - zoom;
  tv->set_rotation(0.1 * d * iz, 0.1 * b * iz, 0.1 * g * iz);
  room->set_camera_pos(0.0f - zoom * 18.0f, 50.0f, 130.0f + g * 0.1f + 0.0f - zoom * 36.0f);

  room->render();

  fbo_chain_end();
}

void TV::zoom_in()
{
  started_zooming_at = 0.001f * SDL_GetTicks();
  zooming_in = true;
}

void TV::zoom_out()
{
  started_zooming_at = 0.001f * SDL_GetTicks();
  zooming_in = false;
}

void TV::TextureProxy::bind() const
{
  bind_other_fbo_texture(1);
  glEnable(GL_TEXTURE_2D);
}

PalNoise::PalNoise()
{
  noise = lib["pal_noise.png"];
  prog = new GLProgram("graphics/noise_vert.glsl", "graphics/noise_frag.glsl");
}

PalNoise::~PalNoise()
{
  delete noise;
  delete prog;
}

void PalNoise::render()
{
  fbo_accum_begin();
  //bind_other_fbo_texture();
  //glDisable(GL_BLEND);
  //draw_fullscreen_quad(true);
  prog->use();
  float random[16];
  for (int i=0; i<16; i++)
    random[i] = float(rand()) / float(RAND_MAX);
  prog->set_matrix4("random", random);
  prog->set_int("noise", 0);
  noise->bind();
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  draw_fullscreen_quad();
  prog->unuse();

  if (game->get_dave()->is_dead() && game->get_dave()->get_voltage() == 0.0f) {
    lib["gameover.png"]->nearest();
    lib["gameover.png"]->bind();
    draw_fullscreen_quad();
  }

  fbo_accum_end();
}

void PalNoise::set_bottom(int x)
{
  assert(prog);
  prog->use();
  prog->set_int("bottom", x);
  prog->unuse();
}

void PalNoise::set_top(int x)
{
  prog->use();
  prog->set_int("top", x);
  prog->unuse();
}

void PalNoise::set_opacity(float x)
{
  prog->use();
  prog->set_float("opacity", x);
  prog->unuse();
}

TapeDistortion::TapeDistortion()
{
  set_bend(0.0f);
  offset = 0.0f;
  if (game->get_ext()->shaders)
    prog = new GLProgram("graphics/tape_vert.glsl", "graphics/tape_frag.glsl");
}

TapeDistortion::~TapeDistortion()
{
  if (game->get_ext()->shaders)
    delete prog;
}

void TapeDistortion::normalize()
{
  started_normalizing_at = 0.001f * SDL_GetTicks();
}

void TapeDistortion::render()
{
  if (!game->get_ext()->shaders)
    return;
  const float t = 0.001f * SDL_GetTicks();
  if (started_normalizing_at < t) {
    bend = 1.0f - (t - started_normalizing_at);
    offset = bend / 8.0f;
    if (bend < 0.0f)
      bend = 0.0f;
  }
  if (bend == 0.0f)
    return;
  fbo_chain_begin();
  prog->use();
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_TEXTURE_2D);
  bind_other_fbo_texture();
  const float extra = float(rand()) / RAND_MAX > 0.8f ? 5.0f : 0.0f;
  prog->set_int("image", 0);
  prog->set_float("time", t);
  prog->set_float("bend", float(rand()) / RAND_MAX * (bend + extra));
  prog->set_float("offset", offset);
  draw_fullscreen_quad(true);
  prog->unuse();
  fbo_chain_end();
}
