/*!
  \file
  \brief 描画コンポーネント

  \author Satofumi KAMIMURA

  $Id: CanvasSurface.cpp 1966 2011-08-16 08:57:00Z satofumi $

  \todo setAlpha() を実装する
*/

#include "CanvasSurface.h"
#include "Font.h"
#include "TextSurface.h"
#define NO_SDL_GLEXT
#include <GL/glew.h>
#include <SDL_opengl.h>
#include <vector>
#include <cassert>

using namespace qrk;
using namespace std;


namespace
{
    typedef struct
    {
        GLuint buffer;
        GLenum mode;
        size_t size;
        GLfloat point_size;
        GLfloat line_width;
        GLint stipple_factor;
        GLushort stipple_pattern;

        Surface* surface;
        Point<long> position;
    } draw_t;

    typedef struct
    {
        GLubyte r;
        GLubyte g;
        GLubyte b;
        GLubyte a;
        GLfloat x;
        GLfloat y;
        GLfloat z;
    } gl_points_t;

    typedef vector<draw_t> Draws;
}

struct CanvasSurface::pImpl
{
    Rect<long> rect_;
    float alpha_;
    Color draw_color_;
    GLfloat point_size_;
    GLfloat line_width_;
    int line_stipple_factor_;
    unsigned short line_stipple_pattern_;
    Font* font_;
    vector<draw_t> draws_;
    draw_t clear_draw_;


    pImpl(const Rect<long>& rect)
        : rect_(rect), alpha_(1.0), draw_color_(Color(1.0, 1.0, 1.0, 1.0)),
          point_size_(1.0f), line_width_(1.0f),
          line_stipple_factor_(1), line_stipple_pattern_(0xffff), font_(NULL)
    {
        clearBuffers();
        setClearColor(Color(0.0f, 0.0f, 0.0f));
    }


    ~pImpl(void)
    {
        clearBuffers();
        glDeleteBuffers(1, &clear_draw_.buffer);
    }


    GLuint createBuffer(void)
    {
        GLuint id;
        glGenBuffers(1, &id);

        return id;
    }


    void clearBuffers(void)
    {
        for (Draws::iterator it = draws_.begin(); it != draws_.end(); ++it) {
            if (it->surface) {
                delete it->surface;
            } else {
                glDeleteBuffers(1, &it->buffer);
            }
        }
        draws_.clear();
    }


    void registerPoint(vector<gl_points_t>& intertwined, GLfloat x, GLfloat y)
    {
        gl_points_t point;
        point.r = min(255.0 * draw_color_.r, 255.0);
        point.g = min(255.0 * draw_color_.g, 255.0);
        point.b = min(255.0 * draw_color_.b, 255.0);
        point.a = min(255.0 * draw_color_.a, 255.0);
        point.x = x;
        point.y = y;
        point.z = 0.0f;

        intertwined.push_back(point);
    }


    void setClearColor(const Color& color)
    {
        vector<Point<long> > points;
        points.push_back(Point<long>(0, 0));
        points.push_back(Point<long>(0, rect_.h));
        points.push_back(Point<long>(rect_.w, 0));
        points.push_back(Point<long>(rect_.w, rect_.h));

        // クリア用の矩形を作成する
        Color stored_color = draw_color_;
        draw_color_ = color;
        registerPrimitive(clear_draw_, points, GL_QUAD_STRIP);
        draw_color_ = stored_color;
    }


    void registerPrimitive(draw_t& draw,
                           const vector<Point<long> >& points, GLenum mode)
    {
        draw.mode = mode;
        draw.line_width = line_width_;
        draw.point_size = point_size_;
        draw.stipple_factor = line_stipple_factor_;
        draw.stipple_pattern = line_stipple_pattern_;
        draw.buffer = createBuffer();
        draw.size = points.size();
        draw.surface = NULL;

        vector<gl_points_t> intertwined;
        for (vector<Point<long> >::const_iterator it = points.begin();
             it != points.end(); ++it) {
            registerPoint(intertwined, it->x, it->y);
        }

        glBindBuffer(GL_ARRAY_BUFFER, draw.buffer);
        size_t array_size =
            intertwined.size() * sizeof(intertwined[0]);
        glBufferData(GL_ARRAY_BUFFER, array_size,
                     &intertwined[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }


    bool registerSurface(draw_t& draw, const char* text,
                         const Point<long>& position)
    {
        if (! font_) {
            return false;
        }
        draw.surface = new TextSurface(*font_, text);
        draw.position = position;

        return true;
    }


    void drawPrimitive(const draw_t& draw)
    {
        glLineWidth(draw.line_width);
        glPointSize(draw.point_size);
        glLineStipple(draw.stipple_factor, draw.stipple_pattern);

        glBindBuffer(GL_ARRAY_BUFFER, draw.buffer);
        glInterleavedArrays(GL_C4UB_V3F, 0, NULL);
        glDrawArrays(draw.mode, 0, draw.size);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }


    void drawSurface(Surface* surface, const Point<long>& position)
    {
        Rect<long> draw_rect = surface->rect();
        draw_rect.x = position.x;
        draw_rect.y = position.y;

        surface->draw(NULL, &draw_rect);
    }


    void draw(const Rect<long>* src, const Rect<long>* dest)
    {
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glEnable(GL_LINE_STIPPLE);

        // 位置にオフセットを加えて描画する
        Point<long> offset(dest->x, dest->y);
        if (src) {
            offset.x += src->x;
            offset.y += src->y;
        }
        glPushMatrix();
        glTranslatef(offset.x, offset.y, 0.0);

        // !!! src のオフセットを加味する
        // !!! clip する領域の右側と下側が、場合によって狭くなるはず
        (void)src;

        // !!! 領域外に書き込みを行う場合には clip は必須だが、
        // !!! clip すると適切に動作しないのでコメントアウト
        //Screen::setClipArea(*dest);

        drawPrimitive(clear_draw_);

        for (Draws::const_iterator it = draws_.begin();
             it != draws_.end(); ++it) {
            if (it->surface) {
                drawSurface(it->surface, it->position);
            } else {
                drawPrimitive(*it);
            }
        }

        glPopMatrix();

        glDisable(GL_LINE_STIPPLE);
        glDisableClientState(GL_COLOR_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);

        //Screen::disableClipArea();
    }
};


CanvasSurface::CanvasSurface(const qrk::Rect<long>& rect)
    : pimpl(new pImpl(rect))
{
}


CanvasSurface::~CanvasSurface(void)
{
}


bool CanvasSurface::isValid(void) const
{
    return true;
}


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


void CanvasSurface::setAlpha(float alpha)
{
    pimpl->alpha_ = alpha;
}


float CanvasSurface::alpha(void) const
{
    return pimpl->alpha_;
}


void CanvasSurface::setRotateAngle(const Angle& angle)
{
    (void)angle;
    // !!!
}


void CanvasSurface::draw(const Rect<long>* src, const Rect<long>* dest)
{
    pimpl->draw(src, dest);
}


void CanvasSurface::setFont(Font& font)
{
    pimpl->font_ = &font;
}


Font* CanvasSurface::font(void) const
{
    return pimpl->font_;
}


void CanvasSurface::setClearColor(const qrk::Color& color)
{
    pimpl->setClearColor(color);
}


void CanvasSurface::clear(void)
{
    pimpl->clearBuffers();
}


void CanvasSurface::setColor(const qrk::Color& color)
{
    pimpl->draw_color_ = color;
}


void CanvasSurface::setPointSize(float size)
{
    pimpl->point_size_ = size;
}


void CanvasSurface::setLineWidth(float width)
{
    pimpl->line_width_ = width;
}


void CanvasSurface::setLineStipple(int factor, unsigned short pattern)
{
    pimpl->line_stipple_factor_ = factor;
    pimpl->line_stipple_pattern_ = pattern;
}


void CanvasSurface::drawPoints(const std::vector<qrk::Point<long> >& points)
{
    if (points.empty()) {
        return;
    }
    draw_t draw;
    pimpl->registerPrimitive(draw, points, GL_POINTS);
    pimpl->draws_.push_back(draw);
}


void CanvasSurface::drawLineStrip(const std::vector<qrk::Point<long> >& points)
{
    if (points.empty()) {
        return;
    }
    draw_t draw;
    pimpl->registerPrimitive(draw, points, GL_LINE_STRIP);
    pimpl->draws_.push_back(draw);
}


void CanvasSurface::drawLineLoop(const std::vector<qrk::Point<long> >& points)
{
    if (points.empty()) {
        return;
    }
    draw_t draw;
    pimpl->registerPrimitive(draw, points, GL_LINE_LOOP);
    pimpl->draws_.push_back(draw);
}


void CanvasSurface::drawCircle(const qrk::Point<long>& center, float radius)
{
    (void)center;
    (void)radius;
    // !!!
}


void CanvasSurface::drawQuadStrip(const std::vector<qrk::Point<long> >& points)
{
    if (points.empty()) {
        return;
    }

    draw_t draw;
    pimpl->registerPrimitive(draw, points, GL_QUAD_STRIP);
    pimpl->draws_.push_back(draw);
}


void CanvasSurface::drawText(const char* text, const Point<long>& position)
{
    if (strlen(text) == 0) {
        return;
    }

    draw_t draw;
    if (pimpl->registerSurface(draw, text, position)) {
        pimpl->draws_.push_back(draw);
    }
}
