#include "viewerGL.h"
#include "polygon_pack.h"


static void
ApplyMatrix(float *v, float *m)
{
    float t[4];

    t[0] = v[0];
    t[1] = v[1];
    t[2] = v[2];
    t[3] = v[3];

    for (int i = 0; i < 4; i++) {
      v[i] = t[0]*m[i] + t[1]*m[i+4] + t[2]*m[i+8] + t[3]*m[i+12];
    }
}
#if 0
static void
ApplyNormalMatrix(float *v, float *m)
{
    float t[4];

    t[0] = v[0];
    t[1] = v[1];
    t[2] = v[2];

    for (int i = 0; i < 3; i++) {
      v[i] = t[0]*m[i] + t[1]*m[i+4] + t[2]*m[i+8];
    }
}
#endif

ViewerGL::ViewerGL(TaskManager *m, int b, int w, int h, int _num)
{
    spe_num = _num;
    manager = m;

    quit_flag = false;
    start_time = 0;
    this_time = 0;
    frames = 0;

  video_init(b, w, h);
}

void
ViewerGL::video_init(int bpp, int width, int height)
{
    SDL_Surface *screen;
    int rgb_size[3];
    int value = 1;
    Uint32 sdl_flag = SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK;
    Uint32 video_flag = SDL_OPENGL;
    
    if (SDL_Init(sdl_flag) < 0) {
      fprintf(stderr,"Couldn't initialize SDL: %s\n", SDL_GetError());
      exit(1);
    }
    
    /* See if we should detect the display depth */
    if ( bpp == 0 )
      {
	if ( SDL_GetVideoInfo()->vfmt->BitsPerPixel <= 8 )
	  {
	    bpp = 8;
	  }
	else
	  {
	    bpp = 32;
	  }
      }
    
    /* Initialize the bpp */
    switch (bpp)
      {
      case 8:
	rgb_size[0] = 3;
	rgb_size[1] = 3;
	rgb_size[2] = 2;
	break;
      case 15:
      case 16:
	rgb_size[0] = 5;
	rgb_size[1] = 5;
	rgb_size[2] = 5;
	break;
      default:
	rgb_size[0] = 8;
	rgb_size[1] = 8;
	rgb_size[2] = 8;
	break;
      }
    
    screen = SDL_SetVideoMode(width, height, bpp, video_flag);
    if (screen == NULL) {
      fprintf(stderr, "Couldn't set GL mode: %s\n", SDL_GetError());
      SDL_Quit();
      exit(1);
    }
    this->width = screen->w;
    this->height = screen->h;
    this->bpp = screen->format->BitsPerPixel;
    
    //各パラメータがちゃんと取れているか確認
    printf("Screen BPP: %d\n", SDL_GetVideoSurface()->format->BitsPerPixel);
    printf("\n");
    printf( "Vendor     : %s\n", glGetString( GL_VENDOR ) );
    printf( "Renderer   : %s\n", glGetString( GL_RENDERER ) );
    printf( "Version    : %s\n", glGetString( GL_VERSION ) );
    printf( "Extensions : %s\n", glGetString( GL_EXTENSIONS ) );
    printf("\n");
    
    SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &value );
    printf( "SDL_GL_RED_SIZE: requested %d, got %d\n", rgb_size[0],value);
    SDL_GL_GetAttribute( SDL_GL_GREEN_SIZE, &value );
    printf( "SDL_GL_GREEN_SIZE: requested %d, got %d\n", rgb_size[1],value);
    SDL_GL_GetAttribute( SDL_GL_BLUE_SIZE, &value );
    printf( "SDL_GL_BLUE_SIZE: requested %d, got %d\n", rgb_size[2],value);
    SDL_GL_GetAttribute( SDL_GL_DEPTH_SIZE, &value );
    printf( "SDL_GL_DEPTH_SIZE: requested %d, got %d\n", bpp, value );
    SDL_GL_GetAttribute( SDL_GL_DOUBLEBUFFER, &value );
    printf( "SDL_GL_DOUBLEBUFFER: requested 1, got %d\n", value );
    
    //OpenGLの設定
    glViewport( 0, 0, width, height );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
    
    //正射影
    //glOrtho( 0.0, width, height, 0.0, OPENGL_PARAM::near, OPENGL_PARAM::far );
    glOrtho( 0.0, width, height, 0.0, OPENGL_PARAM::far, OPENGL_PARAM::near );
    
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
    
    //アルファブレンディング
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    //光源
    glLightfv(GL_LIGHT0, GL_AMBIENT, OPENGL_PARAM::lightAmbient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, OPENGL_PARAM::lightDiffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, OPENGL_PARAM::lightSpecular);
    glLightfv(GL_LIGHT0, GL_POSITION, OPENGL_PARAM::lightPosition);
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0);
    glDepthFunc(GL_LESS);
    //glShadeModel(GL_SMOOTH);
}

void
ViewerGL::mainLoop()
{
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_BLEND);
    
    while(!quit_flag) {
      run_loop();
    }   
}

void
ViewerGL::run_loop()
{
    clear_screen();

    quit_flag = quit_check();
    if (quit_flag == true) {
      this_time = get_ticks();
      run_finish();
      return;
    }
    
    clean_pixels();
    
    sgroot->updateControllerState();
    sgroot->allExecute(width, height);
    light_xyz_stock = sgroot->getLightVector();
    light_switch_stock = sgroot->getLightSwitch();
    light_sysswitch_stock = sgroot->getLightSysSwitch();
    pickup_vertex();
    
    psx_sync_n();
    frames++;
}

void
ViewerGL::pickup_vertex()
{
    float xyz1[4], xyz2[4], xyz3[4];
    float tex_xy1[2], tex_xy2[2], tex_xy3[2];
    float normal1[4],normal2[4],normal3[4];
    GLuint texture;
    
    SceneGraphPtr sg_top = sgroot->getDrawSceneGraph();
    SceneGraphPtr sg = sg_top;
    
    while (sg) {
      if (sg->flag_drawable) {
	if (!sg->texture_info->gl_tex) {
	  sg->texture_info->gl_tex = SDL_GL_LoadTexture(sg->texture_info->texture_image);
	}
	texture = sg->texture_info->gl_tex;
	glBindTexture(GL_TEXTURE_2D, texture);
	
	glEnable(GL_TEXTURE_2D);
	glBegin( GL_TRIANGLES);

	for (int i = 0; i < sg->pp_num; i++) {
	  for (int j = 0; j < sg->pp[i].info.size; j++) {
	    TrianglePack tri = sg->pp[i].tri[j];

	  xyz1[0] = tri.ver1.x;
	  xyz1[1] = tri.ver1.y;
	  xyz1[2] = tri.ver1.z * -1.0f;
	  xyz1[3] = 1.0f;
	  
	  xyz2[0] = tri.ver2.x;
	  xyz2[1] = tri.ver2.y;
	  xyz2[2] = tri.ver2.z * -1.0f;
	  xyz2[3] = 1.0f;
	  
	  xyz3[0] = tri.ver3.x;
	  xyz3[1] = tri.ver3.y;
	  xyz3[2] = tri.ver3.z * -1.0f;
	  xyz3[3] = 1.0f;
	  
	  // sg->matrix = 回転行列*透視変換行列
	  ApplyMatrix(xyz1, sg->matrix);
	  ApplyMatrix(xyz2, sg->matrix);
	  ApplyMatrix(xyz3, sg->matrix);
	  
	  
	  xyz1[0] /= xyz1[2];
	  xyz1[1] /= xyz1[2];
	  xyz2[0] /= xyz2[2];
	  xyz2[1] /= xyz2[2];
	  xyz3[0] /= xyz3[2];
	  xyz3[1] /= xyz3[2];
	  	  
	  tex_xy1[0] = tri.ver1.tex_x;
	  tex_xy1[1] = tri.ver1.tex_y;
	  tex_xy2[0] = tri.ver2.tex_x;
	  tex_xy2[1] = tri.ver2.tex_y;
	  tex_xy3[0] = tri.ver3.tex_x;
	  tex_xy3[1] = tri.ver3.tex_y;

	  normal1[0] = tri.normal1.x;
	  normal1[1] = tri.normal1.y;
	  normal1[2] = tri.normal1.z * -1.0f;

	  normal1[3] = 0.0f;
	  
	  normal2[0] = tri.normal2.x;
	  normal2[1] = tri.normal2.y;
	  normal2[2] = tri.normal2.z * -1.0f;

	  normal2[3] = 0.0f;
	  
	  normal3[0] = tri.normal3.x;
	  normal3[1] = tri.normal3.y;
	  normal3[2] = tri.normal3.z * -1.0f;

	  normal3[3] = 0.0f;
	  
	  ApplyMatrix(normal1,sg->real_matrix);
	  ApplyMatrix(normal2,sg->real_matrix);
	  ApplyMatrix(normal3,sg->real_matrix);
	  
	  obj_draw(xyz1, tex_xy1, normal1);
	  obj_draw(xyz2, tex_xy2, normal2);
	  obj_draw(xyz3, tex_xy3, normal3);
	  }
	}
	glEnd( );
	glDisable(GL_TEXTURE_2D);
      }
      
      if (sg->children != NULL) {
	sg = sg->children;
      } else if (sg->brother != NULL) {
	sg = sg->brother;
      } else {
	while (sg) {
	  if (sg->brother != NULL) {
	    sg = sg->brother;
	    break;
	  } else {
	    if (sg->parent == NULL) {
	      sg = NULL;
	      break;
	    } else {
	      sg = sg->parent;
	    }
	  }
	}
      }
    }
}

void
ViewerGL::obj_draw(float *xyz, float *tex_xyz, float *normal_xyz)
{  


    glTexCoord2f(tex_xyz[0], tex_xyz[1]);
    glVertex3f(xyz[0], xyz[1], xyz[2]);
    glNormal3f(normal_xyz[0], normal_xyz[1], normal_xyz[2]);
}

void
ViewerGL::clean_pixels()
{
    glClearColor( 0.0, 0.0, 0.0, 1.0 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void
ViewerGL::clear_screen()
{
    GLenum gl_error;
    char* sdl_error;
    
    SDL_GL_SwapBuffers( );
    
    /* Check for error conditions. */
    gl_error = glGetError( );
    
    if( gl_error != GL_NO_ERROR )
      {
	fprintf( stderr, "OpenGL error: %d\n", gl_error );
      }
    
    sdl_error = SDL_GetError( );
    
    if( sdl_error[0] != '\0' )
      {
	fprintf(stderr, "SDL error '%s'\n", sdl_error);
	SDL_ClearError();
      }
}

void
ViewerGL::run_finish()
{
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);
    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);
    
    if (this_time != start_time) {
      printf("%f FPS\n", (((float)frames)/(this_time-start_time))*1000.0);
    }
    
    delete sgroot;
    quit();
}

/* end */
