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

  \author Satofumi KAMIMURA

  $Id: Screen.cpp 1966 2011-08-16 08:57:00Z satofumi $
*/

#include "Screen.h"
#include "SdlVideoInit.h"
#include "Color.h"
#if ! defined(NO_OPENGL)
#define NO_SDL_GLEXT
#include <GL/glew.h>
#include <SDL_opengl.h>
#endif
#include <string>

using namespace qrk;
using namespace std;


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


#if ! defined(NO_OPENGL)
    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);
        //SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 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);
    }
#endif
}

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_;
    int 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_));
        }

        flags |= next_flags_;
        if (flags & SDL_OPENGL) {
#if ! defined(NO_OPENGL)
            initializeOpenGl();
#endif
        }

        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) {
#if ! defined(NO_OPENGL)
                // OpenGL 設定の初期化
                setupOpenGlView();

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

        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);
    }
}


#if ! defined(NO_OPENGL)
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);
}
#endif
