/*!
  \file
  \brief 画面の生成

  \author Satofumi KAMIMURA

  $Id: Screen.cpp 906 2009-05-19 22:36:51Z satofumi $
*/

#include "Screen.h"
#include "SdlVideoInit.h"
#include "Color.h"
#include <SDL_opengl.h>
#include <string>

using namespace qrk;
using namespace std;


namespace
{
  enum {
    DefaultWidth = 640,
    DefaultHeight = 480,
  };


  void initializeOpenGl(void)
  {
    int bpp = SDL_GetVideoInfo()->vfmt->BitsPerPixel;

    // Initialize the display
    int rgb_size[3];
    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;
    }
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, rgb_size[0]);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, rgb_size[1]);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, rgb_size[2]);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  }


  void setupOpenGlView(void)
  {
    glPushAttrib(GL_ENABLE_BIT);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glViewport(-1, -1, +1, +1);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(-1.0, +1.0, -1.0, +1.0, -10.0, +10.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glShadeModel(GL_SMOOTH);
  }
}

struct Screen::pImpl
{
  string error_message_;
  SdlVideoInit sdl_video_;
  SDL_Surface* scr_;
  Rect<long> rect_;
  bool is_fullscreen_;
  string caption_;
  string icon_caption_;
  const SDL_Surface* icon_surface_;
  const Uint8* icon_mask_;

  bool settings_changed_;
  Rect<long> next_rect_;
  size_t next_bpp_;
  Uint32 next_flags_;

  bool show_cursor_;


  pImpl(void)
    : error_message_("Not initialized."),
      scr_(NULL),
      rect_(0, 0, DefaultWidth, DefaultHeight), is_fullscreen_(false),
      icon_surface_(NULL), icon_mask_(NULL),
      settings_changed_(false),
      next_rect_(rect_), next_bpp_(0), next_flags_(0),
      show_cursor_(true)
  {
  }


  static pImpl* object(void)
  {
    static pImpl singleton_object;
    return &singleton_object;
  }


  bool show(Uint32 flags)
  {
    sdl_video_.initialize();

    // アイコンの初期化
    if ((! scr_) && icon_surface_) {
      // アイコンの初期化は、SDL_SetVideoMode() を呼び出す前のみ有効
      SDL_WM_SetIcon(const_cast<SDL_Surface*>(icon_surface_),
                     const_cast<Uint8*>(icon_mask_));
    }

    if (flags & SDL_OPENGL) {
      initializeOpenGl();
    }

    scr_ = SDL_SetVideoMode(next_rect_.w, next_rect_.h,
			    static_cast<int>(next_bpp_), flags);
    if (scr_) {
      // キャプションの登録
      SDL_WM_SetCaption(caption_.c_str(), icon_caption_.c_str());

      is_fullscreen_ = (flags & SDL_FULLSCREEN) ? true : false;

      if (flags & SDL_OPENGL) {
        // OpenGL 設定の初期化
        setupOpenGlView();

        // デフォルトで 2D 描画とする
        enter2D();
      }
    }

    settings_changed_ = false;
    next_flags_ = flags;
    next_bpp_ = 0;
    error_message_ = SDL_GetError();

    return true;
  }
};


Screen::Screen(void) : pimpl(pImpl::object())
{
}


Screen::~Screen(void)
{
}


const char* Screen::what(void) const
{
  return pimpl->error_message_.c_str();
}


bool Screen::show(Uint32 flags)
{
  return pimpl->show(flags);
}


Rect<long> Screen::rect(void) const
{
  return pimpl->rect_;
}


void Screen::setWindowSize(size_t width, size_t height)
{
  pimpl->next_rect_ =
    Rect<long>(0, 0, static_cast<long>(width), static_cast<long>(height));
}


void Screen::setWindowBpp(size_t bpp)
{
  pimpl->next_bpp_ = bpp;
}


void Screen::setCaption(const char* caption, const char* icon_caption)
{
  pimpl->caption_ = caption;
  pimpl->icon_caption_ = icon_caption;
}


void Screen::setIcon(const SDL_Surface* icon, const Uint8* mask)
{
  pimpl->icon_surface_ = icon;
  pimpl->icon_mask_ = mask;
}


void Screen::setFullscreen(bool on)
{
  if (on != pimpl->is_fullscreen_) {
    if (on) {
      pimpl->next_flags_ |= SDL_FULLSCREEN;
    } else {
      pimpl->next_flags_ &= ~SDL_FULLSCREEN;
    }
    // 画面の再生成を強制する
    pimpl->settings_changed_ = true;

    if (! pimpl->scr_) {
      return;
    }

#if defined(WINDOWS_OS)
    SDL_GL_Texture::deleteTextures();
#endif
    show(pimpl->next_flags_);
#if defined(WINDOWS_OS)
    SDL_GL_Texture::recreateTextures();
#endif
  }
}


bool Screen::isFullscreen(void) const
{
  return pimpl->is_fullscreen_;
}


void Screen::toggleScreen(void)
{
  setFullscreen(! isFullscreen());
}


void Screen::showCursor(bool show_cursor)
{
  if ((! pimpl->scr_) || (pimpl->show_cursor_ == show_cursor)) {
    return;
  }
  SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE);
  pimpl->show_cursor_ = show_cursor;
}


bool Screen::isShowCursor(void) const
{
  return pimpl->show_cursor_;
}


void Screen::setClearColor(const Color& color)
{
  if (pimpl->scr_->flags & SDL_OPENGL) {
    glClearColor(color.r, color.g, color.b, color.a);
  }
}


void Screen::clear(void)
{
  if (pimpl->scr_->flags & SDL_OPENGL) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  }
}


void Screen::enter2D(void)
{
  pImpl* pimpl_ = pImpl::object();
  if (! pimpl_->scr_) {
    return;
  }
  int screen_width = static_cast<int>(pimpl_->scr_->w);
  int screen_height = static_cast<int>(pimpl_->scr_->h);

  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glViewport(0, 0, screen_width, screen_height);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  glOrtho(0.0, screen_width - 1.0, screen_height - 1.0, 0.0, 0.0, 1.0);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
}


void Screen::leave2D(void)
{
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glPopAttrib();
}


void Screen::setClipArea(const qrk::Rect<long>& area)
{
  GLdouble clip_left[] = { 1.0, 0.0, 0.0, 0.0 };
  clip_left[3] = -area.x;

  GLdouble clip_right[] = { -1.0, 0.0, 0.0, 0.0 };
  clip_right[3] = area.x + area.w;

  GLdouble clip_top[] = { 0.0, 1.0, 0.0, 0.0 };
  clip_top[3] = -area.y;

  GLdouble clip_bottom[] = { 0.0, -1.0, 0.0, 0.0 };
  clip_bottom[3] = area.y + area.h;

  glClipPlane(GL_CLIP_PLANE0, clip_left);
  glClipPlane(GL_CLIP_PLANE1, clip_right);
  glClipPlane(GL_CLIP_PLANE2, clip_top);
  glClipPlane(GL_CLIP_PLANE3, clip_bottom);

  glEnable(GL_CLIP_PLANE0);
  glEnable(GL_CLIP_PLANE1);
  glEnable(GL_CLIP_PLANE2);
  glEnable(GL_CLIP_PLANE3);
}


void Screen::disableClipArea(void)
{
  glDisable(GL_CLIP_PLANE0);
  glDisable(GL_CLIP_PLANE1);
  glDisable(GL_CLIP_PLANE2);
  glDisable(GL_CLIP_PLANE3);
}
