/**
 * keylift is registered as a GLUT callback for when a user
 * releases a depressed key.
 *
 * @param key The key that was lifted.
 * @param x The x coordinate of the mouse at the time the key was released.
 * @param y The y coordinate of the mouse at the time the key was released.
 */
void keylift( unsigned char key, int x, int y ) {

  Cameras *camList = Engine::instance()->cams();

  if ( camList->numCameras() < 1 ) return;
  Camera &cam = *(camList->active());

  switch ( key ) {
  case 'w':
    cam.stop( Camera::DIR_FORWARD );
    break;
  case 's':
    cam.stop( Camera::DIR_BACKWARD );
    break;
  case 'a':
    cam.stop( Camera::DIR_LEFT );
    break;
  case 'd':
    cam.stop( Camera::DIR_RIGHT );
    break;
  case 'q':
    cam.stop( Camera::DIR_UP );
    break;
  case 'e':
    cam.stop( Camera::DIR_DOWN );
    break;
  }
}
TEST(CamerasTest, rotated) {
    Cameras cameras;

    size_t id = cameras.addCameraFovX(
        "dummy",
        vec3(0.0f, 0.0f, 0.0f),
        vec3(1.0f, 0.0f, 0.0f),
        vec3(0.0f, 1.0f, 0.0f),
        half_pi<float>());

    ASSERT_EQ(1, cameras.numCameras());

    auto r0 = cameras.shoot(id, vec2(0.0f, 0.0f), 0.01f, 0.01f, 1, 0.0f, 0.0f);
    EXPECT_VEC3_EQ(vec3(0.577f, -0.577f, -0.577f), r0.direction, 0.001f);
}
/**
 * keyboard_ctrl is registered as a GLUT callback.
 * It is responsible for catching when special keys are pressed.
 *
 * @param key The key pressed.
 * @param x The x coordinate of the mouse when the key was pressed.
 * @param y The y coordinate of the mouse when the key was pressed.
 */
void keyboard_ctrl( int key, int x, int y ) {

  Scene *theScene = Engine::instance()->rootScene();
  Cameras *camList = Engine::instance()->cams();
  
  switch ( key ) {
  //Cycle between active Objects ...
  case GLUT_KEY_LEFT:
    theScene->prev();
    break;
  case GLUT_KEY_RIGHT:
    theScene->next();
    break;

    //Change the Draw Mode ...
  case GLUT_KEY_F1:
    theScene->active()->Mode( GL_POINTS );
    break;
  case GLUT_KEY_F2:
    theScene->active()->Mode( GL_LINE_STRIP );
    break;
  case GLUT_KEY_F3:
    theScene->active()->Mode( GL_TRIANGLE_STRIP );
    break;
  case GLUT_KEY_F4:
    theScene->active()->Mode( GL_TRIANGLES );
    break;
  }

  // If there are no Cameras, don't muck around with this section.
  if ( camList->numCameras() < 1 ) return;

  switch ( key ) {
  case GLUT_KEY_PAGE_UP:
    camList->prev();
    break;

  case GLUT_KEY_PAGE_DOWN:
    camList->next();
    break;
  }
}
TEST(CamerasTest, basic_tests) {
    Cameras cameras;

    size_t id = cameras.addCameraFovX(
        "dummy",
        vec3(0.0f, 0.0f, 0.0f),
        vec3(0.0f, 0.0f, -1.0f),
        vec3(0.0f, 1.0f, 0.0f),
        half_pi<float>());

    ASSERT_EQ(1, cameras.numCameras());
    EXPECT_EQ("dummy", cameras.name(id));

    EXPECT_FLOAT_EQ(half_pi<float>(), cameras.fovx(id, 1.0f));
    EXPECT_FLOAT_EQ(half_pi<float>(), cameras.fovx(id, 4.0f / 3.0f));
    EXPECT_FLOAT_EQ(half_pi<float>(), cameras.fovy(id, 1.0f));
    EXPECT_FLOAT_EQ(1.2870022f, cameras.fovy(id, 4.0f / 3.0f));

    const float w = 800;
    const float h = 600;
    const float wInv = 1.0f / w;
    const float hInv = 1.0f / h;
    const float a = w / h;

    auto r0 = cameras.shoot(id, vec2(0.0f, 0.0f), wInv, hInv, a, 400.0f, 300.0f);
    auto r1 = cameras.shoot(id, vec2(0.0f, 0.0f), wInv, hInv, a, 0.0f, 0.0f);
    auto r2 = cameras.shoot(id, vec2(0.0f, 0.0f), wInv, hInv, a, 800.0f, 600.0f);
    auto r3 = cameras.shoot(id, vec2(0.0f, 0.0f), 0.01f, 0.01f, 1, 0.0f, 0.0f);

    EXPECT_VEC3_EQ(vec3(0.0f, 0.0f, 0.0f), r0.origin, 0.00001f);
    EXPECT_VEC3_EQ(vec3(0.0f, 0.0f, -1.0f), r0.direction, 0.00001f);

    EXPECT_VEC3_EQ(vec3(0.0f), r1.origin, 0.000001f);
    EXPECT_VEC3_EQ(vec3(-0.685994f, -0.514496f, -0.514496f), r1.direction, 0.000001f);

    EXPECT_VEC3_EQ(vec3(0.0f), r2.origin, 0.000001f);
    EXPECT_VEC3_EQ(vec3(0.685994f, 0.514496f, -0.514496f), r2.direction, 0.0001f);

    EXPECT_VEC3_EQ(vec3(0.0f), r3.origin, 0.00001f);
    EXPECT_VEC3_EQ(vec3(-0.577f, -0.577f, -0.577f), r3.direction, 0.001f);
}
/**
 * keyboard is a callback registered with GLUT.
 * It handles (surprise!) keyboard input.
 *
 * @param key The key pressed by the user.
 * @param x The x coordinate of the mouse when the key was pressed.
 * @param y The y coordinate of the mouse when the key was pressed.
 */
void keyboard( unsigned char key, int x, int y ) {

  Scene *theScene = Engine::instance()->rootScene();
  Cameras *camList = Engine::instance()->cams();

#ifdef WII
  // Hacky, for the wii reset, below.
  Camera *camptr = dynamic_cast< Camera* >( (*_camList)["AutoCamera2"] );
#endif

  switch ( key ) {

  case 033: // Escape Key	  
    /*
     cleanup();
     Disabled for now; not crucial.
     Intend to fix later when I profile a bit more with valgrind.
     */
    glutLeaveMainLoop();
    break;

  case ';': // Print Info
    fprintf( stderr, "Active Object: %s\n",
             theScene->active()->Name().c_str() );
    break;

  case '~':
#ifdef WII
    CalibrateGyro( Wii );
    if (camptr) camptr->resetRotation();
#endif
    break;
    
  case '+':
    std::stringstream camName;
    camName << "AutoCamera" << camList->numCameras() + 1;
    camList->addCamera( camName.str() );
    break;
  }

  if ( camList->numCameras() < 1 ) return;

  /* A shorthand variable with local scope that refers to "The active Camera." */
  Camera &cam = *(camList->active());
  
  switch ( key ) {
  case '-':
    camList->popCamera();
    break;
  case ';':
    fprintf( stderr, "Camera Position: (%f,%f,%f)\n", cam.x(), cam.y(),
             cam.z() );
    break;
    
  case 'w':
    cam.move( Camera::DIR_FORWARD );
    break;
  case 's':
    cam.move( Camera::DIR_BACKWARD );
    break;
  case 'a':
    cam.move( Camera::DIR_LEFT );
    break;
  case 'd':
    cam.move( Camera::DIR_RIGHT );
    break;
  case 'q':
    cam.move( Camera::DIR_UP );
    break;
  case 'e':
    cam.move( Camera::DIR_DOWN );
    break;
    
    //Perspectives
  case 'z':
    cam.changePerspective( Camera::PERSPECTIVE );
    break;
  case 'x':
    cam.changePerspective( Camera::ORTHO );
    break;
  case 'c':
    cam.changePerspective( Camera::ORTHO2D );
    break;
  case 'v':
    cam.changePerspective( Camera::FRUSTUM );
    break;
  case 'b':
    cam.changePerspective( Camera::IDENTITY );
    break;

  }
}