module otya.smilebasic.graphic;
import derelict.sdl2.sdl;
import derelict.sdl2.image;
import derelict.opengl3.gl;
import derelict.opengl3.gl3;
import otya.smilebasic.petitcomputer;
import std.string;

class Graphic
{
    PetitComputer petitcom;
    int width;
    int height;
    this(PetitComputer p)
    {
        petitcom = p;
        paint = new Paint();
        width = 512;
        height = 512;
        //nnnue
        paint.buffer = new uint[width * height];
    }
    int GRPFWidth, GRPFHeight;
    void setSize(int w, int h)
    {
        width = w;
        height = h;
    }
    void getSize(out int w, out int h)
    {
        w = width;
        h = height;
    }
    void initGraphicPages()
    {
        if (GRP.length == 0)
        {
            GRP = new GraphicPage[6];
        }
        for(int i = 0; i < 4; i++)
        {
            GRP[i] = createEmptyPage();
        }
        GRP[4] = createGRPF(petitcom.spriteFile);
        GRP[5] = createGRPF(petitcom.BGFile);
    }
    GraphicPage createGRPF(string file)
    {
        SDL_RWops* stream = SDL_RWFromFile(toStringz(file), toStringz("rb"));
        auto src = IMG_Load_RW(stream, 0);
        SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);//0xff000000, 0x00ff0000, 0x0000ff00,  0xFF);
        SDL_Rect rect;
        rect.x = 0;
        rect.y = 0;
        rect.w = surface.w;
        rect.h = surface.h;
        //        SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
        //        SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);

        ubyte sr, sg, sb, sa;
        SDL_GetRGBA((cast(uint*)src.pixels)[0], src.format, &sr, &sg, &sb, &sa);
        auto color = SDL_MapRGBA(surface.format, sr, sg, sb, sa);
        SDL_SetColorKey(src, SDL_TRUE, (cast(uint*)src.pixels)[0]);
        //SDL_SetColorKey(surface, SDL_TRUE, color);
        int i = SDL_BlitSurface(src, &rect, surface, &rect);
        auto srcpixels = (cast(uint*)src.pixels);
        auto pixels = (cast(uint*)surface.pixels);
        auto aaa = surface.format.Amask;
        //surface.format.Amask = 0xFF;
        for(int x = 0; x < surface.w; x++)
        {
            for(int y = 0; y < surface.h; y++)
            {
                ubyte r, g, b, a;
                SDL_GetRGBA(*pixels, surface.format, &r, &g, &b, &a);
                if(r == sr && g == sg && b == sb)
                {
                    r = 0;
                    g = 0;
                    b = 0;
                    a = 0;
                    *pixels = 0;
                    *pixels = SDL_MapRGBA(surface.format, r, g, b, a);
                }
                pixels++;
            }
        }
        SDL_RWclose(stream);
        SDL_FreeSurface(src);
        return new GraphicPage(surface);
    }
    GraphicPage createEmptyPage()
    {
        auto surface = SDL_CreateRGBSurface(0, width, height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
        auto pixels = (cast(uint*)surface.pixels);
        for(int x = 0; x < surface.w; x++)
        {
            for(int y = 0; y < surface.h; y++)
            {
                ubyte r, g, b, a;
                SDL_GetRGBA(*pixels, surface.format, &r, &g, &b, &a);
                {
                    r = 0;
                    g = 0;
                    b = 0;
                    a = 0;
                    *pixels = 0;
                    *pixels = SDL_MapRGBA(surface.format, r, g, b, a);
                }
                pixels++;
            }
        }
        return new GraphicPage(surface);
    }

    bool[2] visibles = [true, true];
    int[2] showPage = [0, 1];
    int[2] usePage = [0, 1];
    bool visible()
    {
        return visibles[petitcom.displaynum];
    }
    void visible(bool value)
    {
        visibles[petitcom.displaynum] = value;
    }
    @property int useGRP()
    {
        return usePage[petitcom.displaynum];
    }
    @property int showGRP()
    {
        return showPage[petitcom.displaynum];
    }
    @property void useGRP(int page)
    {
        usePage[petitcom.displaynum] = page;
    }
    @property void showGRP(int page)
    {
        showPage[petitcom.displaynum] = page;
    }
    uint gcolor = -1;
    GraphicPage[] GRP;
    GraphicPage[] oldGRP;
    class Paint
    {
        uint[] buffer;
        static const MAXSIZE = 1024; /* バッファサイズ */

        /* 画面サイズは 1024 X 1024 とする */
        static const MINX = 0;
        static const MINY = 0;
        static const MAXX = 511;
        static const MAXY = 511;

        struct BufStr {
            int lx; /* 領域右端のX座標 */
            int rx; /* 領域右端のX座標 */
            int y;  /* 領域のY座標 */
            int oy; /* 親ラインのY座標 */
        };
        BufStr[MAXSIZE] buff; /* シード登録用バッファ */
        BufStr* sIdx, eIdx;  /* buffの先頭・末尾ポインタ */
        uint point(int x, int y)
        {
            return buffer.ptr[x + y * width];
        }
        void pset(int x, int y, uint col)
        {
            buffer.ptr[x + y * width] = col;
        }
        /*
        scanLine : 線分からシードを探索してバッファに登録する

        int lx, rx : 線分のX座標の範囲
        int y : 線分のY座標
        int oy : 親ラインのY座標
        unsigned int col : 領域色
        */
        void scanLine( int lx, int rx, int y, int oy, uint col )
        {
            while ( lx <= rx ) {

                /* 非領域色を飛ばす */
                for ( ; lx < rx ; lx++ )
                    if ( point( lx, y ) == col ) break;
                if ( point( lx, y ) != col ) break;

                eIdx.lx = lx;

                /* 領域色を飛ばす */
                for ( ; lx <= rx ; lx++ )
                    if ( point( lx, y ) != col ) break;

                eIdx.rx = lx - 1;
                eIdx.y = y;
                eIdx.oy = oy;

                if ( ++eIdx == &buff.ptr[MAXSIZE] )
                    eIdx = buff.ptr;
            }
        }

        /*
        paint : 塗り潰し処理(高速版)

        int x, y : 開始座標
        unsigned int paintCol : 塗り潰す時の色(描画色)
        */
        void paint( int x, int y, uint paintCol , out int dx, out int dy, out int dx2, out int dy2)
        {
            int lx, rx; /* 塗り潰す線分の両端のX座標 */
            int ly;     /* 塗り潰す線分のY座標 */
            int oy;     /* 親ラインのY座標 */
            int i;
            uint col = point( x, y ); /* 閉領域の色(領域色) */
            dx = int.max, dy = int.max, dx2 = int.min, dy2 = int.min;
            if ( col == paintCol ) return;    /* 領域色と描画色が等しければ処理不要 */
            sIdx = buff.ptr;
            eIdx = buff.ptr + 1;
            sIdx.lx = sIdx.rx = x;
            sIdx.y = sIdx.oy = y;

            do {
                lx = sIdx.lx;
                rx = sIdx.rx;
                ly = sIdx.y;
                oy = sIdx.oy;

                int lxsav = lx - 1;
                int rxsav = rx + 1;

                if ( ++sIdx == &buff.ptr[MAXSIZE] ) sIdx = buff.ptr;

                /* 処理済のシードなら無視 */
                if ( point( lx, ly ) != col )
                    continue;

                /* 右方向の境界を探す */
                while ( rx < MAXX ) {
                    if ( point( rx + 1, ly ) != col ) break;
                    rx++;
                }
                /* 左方向の境界を探す */
                while ( lx > MINX ) {
                    if ( point( lx - 1, ly ) != col ) break;
                    lx--;
                }
                import std.algorithm;
                dy = min(dy, ly);
                dy2 = max(dy2, ly);
                dx = min(dx, lx);
                dx2 = max(dx2, rx);
                //
                /* lx-rxの線分を描画 */
                for ( i = lx; i <= rx; i++ ) pset( i, ly, paintCol );

                /* 真上のスキャンラインを走査する */
                if ( ly - 1 >= MINY ) {
                    if ( ly - 1 == oy ) {
                        scanLine( lx, lxsav, ly - 1, ly, col );
                        scanLine( rxsav, rx, ly - 1, ly, col );
                    } else {
                        scanLine( lx, rx, ly - 1, ly, col );
                    }
                }

                /* 真下のスキャンラインを走査する */
                if ( ly + 1 <= MAXY ) {
                    if ( ly + 1 == oy ) {
                        scanLine( lx, lxsav, ly + 1, ly, col );
                        scanLine( rxsav, rx, ly + 1, ly, col );
                    } else {
                        scanLine( lx, rx, ly + 1, ly, col );
                    }
                }

            } while ( sIdx != eIdx );
        }
        void gpaintBuffer(uint* pixels, int x, int y, uint color, GLenum tf)
        {
            int dx, dy, dx2, dy2;
            paint(x, y, color, dx, dy, dx2, dy2);
            if(dx == int.max) return;
            int h = dy2 - dy;
            //glTexSubImage2D(GL_TEXTURE_2D , 0, 0, dy, width, h, tf, GL_UNSIGNED_BYTE, pixels + (dy * width));
            glDrawPixels(512, dy2, tf, GL_UNSIGNED_BYTE, buffer.ptr);
        }
    }
    Paint paint;
    abstract void initVM();
    abstract void display(int display);
    abstract void updateVM();
    abstract void gpset(int page, int x, int y, uint color);
    abstract void gline(int page, int x, int y, int x2, int y2, uint color);
    abstract void gbox(int page, int x, int y, int x2, int y2, uint color);
    abstract void gfill(int page, int x, int y, int x2, int y2, uint color);
    abstract void gcls(int page, uint color);
    abstract void gpaint(int page, int x, int y, uint color);
    abstract void gcircle(int page, int x, int y, int r, uint color);
    abstract void gcircle(int page, int x, int y, int r, int startr, int endr, int flag, uint color);
    abstract void gputchr(int page, int x, int y, int text, int scalex, int scaley, uint color);
    abstract void gputchr(int page, int x, int y, wstring text, int scalex, int scaley, uint color);
    abstract int gspoit(int page, int x, int y);
    abstract void gsave(int savepage, int x, int y, int w, int h, double[] array, int flag);
    abstract void gsave(int savepage, int x, int y, int w, int h, int[] array, int flag);
    abstract void gload(int x, int y, int w, int h, int[] array, int flag, int copymode);
    abstract void gload(int x, int y, int w, int h, double[] array, int flag, int copymode);
    void gloadPalette(T, T2)(int x, int y, int w, int h, T[] array, T2[] palette, int copymode)
    if ((is(T == int) || is(T == double)) && (is(T2 == int) || is(T2 == double)))
    {
        if (cast(GraphicFBO)this)
        {
            GraphicFBO gfbo = (cast(GraphicFBO)this);
            gfbo.gloadPalette(x, y, w, h, array, palette, copymode);
        }
        if (cast(Graphic2)this)
        {
            Graphic2 g = (cast(Graphic2)this);
            g.gloadPalette(x, y, w, h, array, palette, copymode);
        }
    }
    abstract void gcopy(int srcpage, int x, int y, int x2, int y2, int x3, int y3, int cpmode);
    abstract void gtri(int x1, int y1, int x2, int y2, int x3, int y3, int color);
    int[2] gprios = [512, 512];

    @property ref gprio()
    {
        return gprios[petitcom.displaynum];
    }
    @property void gprio(int value)
    {
        gprios[petitcom.displaynum] = value;
    }

    void render()
    {
        for (int i = 0; i < petitcom.currentDisplay.rect.length; i++)
        {
            render(i, petitcom.currentDisplay.rect[i].w, petitcom.currentDisplay.rect[i].h);
        }
    }
    void render(int display, int w, int h)
    {
        if (!visibles[display])
            return;
        petitcom.chRenderingDisplay(display);
        float z = gprios[display];
        glColor3f(1.0, 1.0, 1.0);
        glBindTexture(GL_TEXTURE_2D, GRP[showPage[display]].glTexture);
        glEnable(GL_TEXTURE_2D);
        glBegin(GL_QUADS);
        int x1 = displayArea[display].x;
        int y1 = displayArea[display].y;
        int x2 = x1 + displayArea[display].w;
        int y2 = y1 + displayArea[display].h;
        glTexCoord2f(x1 / (cast(float)width) - 1 , y2 / (cast(float)height) - 1);
        glVertex3f(x1, y2, z);
        glTexCoord2f(x1 / (cast(float)width) - 1, y1 / (cast(float)height) - 1);
        glVertex3f(x1, y1, z);
        glTexCoord2f(x2 / (cast(float)width) - 1, y1 / (cast(float)height) - 1);
        glVertex3f(x2, y1, z);
        glTexCoord2f(x2 / (cast(float)width) - 1, y2 / (cast(float)height) - 1);
        glVertex3f(x2, y2, z);
        glEnd();
        //glFlush();
    }
    SDL_Rect[2] writeArea;
    SDL_Rect[2] displayArea;
    void clip(bool clipmode)
    {
        if (clipmode)
        {
            clip(clipmode, 0, 0, width, height);
        }
        else
        {
            clip(clipmode, 0, 0, petitcom.currentScreenWidth, petitcom.currentScreenHeight);
        }
    }
    void clip(bool clipmode, int x, int y, int w, int h)
    {
        if (clipmode)
        {
            writeArea[petitcom.displaynum].x = x;
            writeArea[petitcom.displaynum].y = y;
            writeArea[petitcom.displaynum].w = w;
            writeArea[petitcom.displaynum].h = h;
            this.display(petitcom.displaynum);
        }
        else
        {
            displayArea[petitcom.displaynum] = SDL_Rect(x, y, w, h);
        }
    }
    static int ARGB32ToRGBA16Color(int argb32)
    {
        auto a = (argb32 & 0xFF000000) >> 24;
        auto r = (argb32 & 0x00FF0000) >> 16;
        auto g = (argb32 & 0x0000FF00) >> 8;
        auto b = (argb32 & 0x000000FF);
        return (a == 255) | r >> 3 << 11 | g >> 3 << 6 | b >> 3 << 1;
    }
    static int RGBA16ToARGB32Color(int rgba16)
    {
        auto a = (rgba16 & 0b0000000000000001) == 1 ? 0xFF000000 : 0;
        auto r = (rgba16 & 0b1111100000000000) >> 11;
        auto g = (rgba16 & 0b0000011111000000) >> 6;
        auto b = (rgba16 & 0b0000000000111110) >> 1;
        return a | r << 19 | g << 11 | b << 3;
    }
}

class GraphicFBO : Graphic
{
    this(PetitComputer p)
    {
        super(p);
    }
    override void initGraphicPages()
    {
        foreach(g; GRP)
        {
            if (g.glTexture)
                g.deleteGL();
        }
        super.initGraphicPages();
        foreach(g; GRP)
        {
            g.createTexture(petitcom.renderer, petitcom.textureScaleMode);
            g.createBuffer();
        }
    }
    override @property void useGRP(int page)
    {
        super.useGRP = page;
        glBindFramebufferEXT(GL_FRAMEBUFFER, this.GRP[page].buffer);
    }
    override @property int useGRP()
    {
        return super.useGRP;
    }
    
    //(x+r,y):0°
    //(x,y+r):90°
    void drawCircle(int x, int y, int r, int startr, int endr, int flag)
    {
        import std.math : sin, cos, PI;
        import std.algorithm.comparison : min;
        int count = min(r, 1000);
        if (flag)
        {
            glBegin(GL_LINE_LOOP);
            glVertex2i(x, y);
        }
        else
        {
            glBegin(GL_LINE_STRIP);
        }
        startr = startr % 360;
        endr = endr % 360;
        if (startr > endr)
        {
            endr += 360;
        }
        for (int i = 0; i <= r; i++)
        {
            //float a = i * (360f / count);
            float angle = (cast(float)i / count) * ((endr - startr) / 180f) * PI + (startr / 180f * PI);
            glVertex2f(cos(angle) * r + x, sin(angle) * r + y);
        }
        glEnd();
    }
    void drawCircle(int x, int y, int r)
    {
        import std.math : sin, cos, PI;
        import std.algorithm.comparison : min;
        int count = min(r, 1000);
        glBegin(GL_LINE_STRIP);
        for (int i = 0; i <= r; i++)
        {
            //float a = i * (360f / r);
            float angle = cast(float)i / count * 2f * PI;
            glVertex2f(cos(angle) * r + x, sin(angle) * r + y);
        }
        glEnd();
    }
    void drawText(wstring text)
    {
        int x;
        foreach (c; text)
        {
            drawCharacter(x, 0, c);
            x += 8;
        }
    }
    void drawCharacter(int x, int y, wchar character)
    {
        auto rect = petitcom.console.fontTable[character];
        float tx1 = (rect.x) / (cast(float)GRPFWidth) - 1;
        float ty1 = (rect.y + 8) / (cast(float)GRPFHeight) - 1;
        float tx2 = (rect.x + 8) / (cast(float)GRPFWidth) - 1;
        float ty2 = (rect.y) / (cast(float)GRPFHeight) - 1;
        glTexCoord2f(tx1 , ty1);
        glVertex3i(x, y + 8, 0);
        glTexCoord2f(tx1, ty2);
        glVertex3i(x, y, 0);
        glTexCoord2f(tx2, ty2);
        glVertex3i(x + 8, y, 0);
        glTexCoord2f(tx2, ty1);
        glVertex3i(x + 8, y + 8, 0);
    }
    override void initVM()
    {
        SDL_GL_MakeCurrent(petitcom.window, petitcom.contextVM);
        glAlphaFunc(GL_GEQUAL, 0.1f);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_ALPHA_TEST);
        glDisable(GL_DEPTH_TEST);
    }
    int drc = 0;
    override void display(int display)
    {
        void chScreen(int x, int y, int w, int h)
        {
            glViewport(x, y, w, h);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(x, x + w - 1, y, y + h - 1, -256, 1024);//wakaranai
        }
        chScreen(writeArea[display].x, writeArea[display].y, writeArea[display].w, writeArea[display].h);
        glBindFramebufferEXT(GL_FRAMEBUFFER, this.GRP[usePage[display]].buffer);
    }
    bool flushRequired;
    override void updateVM()
    {
        if (drc)
            glFlush();
        drc = 0;
    }
    uint convertColor(uint color)
    {
        return petitcom.toGLColor(this.GRP[0].textureFormat, color & 0xFFF8F8F8);
    }
    override void gpset(int page, int x, int y, uint color)
    {
        color = convertColor(color);
        glBegin(GL_POINTS);
        glColor4ubv(cast(ubyte*)&color);
        glVertex2f(x, y);
        glEnd();
        drc++;
    }
    override void gline(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        glBegin(GL_LINES);
        glColor4ubv(cast(ubyte*)&color);
        glVertex2f(x, y);
        glVertex2f(x2, y2);
        glEnd();
        drc++;
    }
    override void gbox(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        glBegin(GL_LINE_LOOP);
        glColor4ubv(cast(ubyte*)&color);
        glVertex2f(x, y);
        glVertex2f(x, y2);
        glVertex2f(x2, y2);
        glVertex2f(x2, y);
        glEnd();
        drc++;
    }
    override void gfill(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        glBegin(GL_QUADS);
        glColor4ubv(cast(ubyte*)&color);
        glVertex2f(x, y);
        glVertex2f(x, y2);
        glVertex2f(x2, y2);
        glVertex2f(x2, y);
        glEnd();
        drc++;
    }
    override void gcls(int page, uint color)
    {
        gfill(page, 0, 0, width - 1, height - 1, color);
    }
    override void gpaint(int page, int x, int y, uint color)
    {
        color = convertColor(color);
        updateVM();
        //glGetTexImage(GL_TEXTURE_2D,0,GRP[oldpage].textureFormat,GL_UNSIGNED_BYTE,buffer.ptr);
        glReadPixels(0, 0, width, height, GRP[useGRP].textureFormat, GL_UNSIGNED_BYTE, paint.buffer.ptr);
        paint.gpaintBuffer(paint.buffer.ptr, x, y, color, GRP[useGRP].textureFormat);
    }
    override void gcircle(int page, int x, int y, int r, uint color)
    {
        color = convertColor(color);
        glColor4ubv(cast(ubyte*)&color);
        drawCircle(x, y, r);
        drc++;
    }
    override void gcircle(int page, int x, int y, int r, int startr, int endr, int flag, uint color)
    {
        color = convertColor(color);
        glColor4ubv(cast(ubyte*)&color);
        drawCircle(x, y, r, startr, endr, flag);
        drc++;
    }
    override void gputchr(int page, int x, int y, int text, int scalex, int scaley, uint color)
    {
        import std.conv : to;
        gputchr(page, x, y, (cast(wchar)text).to!wstring, scalex, scaley, color);
    }
    override void gputchr(int page, int x, int y, wstring text, int scalex, int scaley, uint color)
    {
        color = convertColor(color);
        glColor4ubv(cast(ubyte*)&color);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_ALPHA_TEST);
        glBindTexture(GL_TEXTURE_2D, petitcom.console.GRPF.glTexture);
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glTranslatef(x, y, 0);
        glScalef(scalex, scaley, 1);
        glBegin(GL_QUADS);
        drawText(text);
        glEnd();
        glPopMatrix();
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_ALPHA_TEST);
        drc++;
    }
    override int gspoit(int page, int x, int y)
    {
        int ui;
        //flush
        //if (drc)
        //    glFinish();
        //drc = 0;
        glReadPixels(x, y, 1, 1, GRP[page].textureFormat, GL_UNSIGNED_BYTE, &ui);
        return ui;
    }
    override void gsave(int savepage, int x, int y, int w, int h, double[] array, int flag)
    {
        if (w * h > array.length)
            return;
        gsave(savepage, x, y, w, h, cast(int[])paint.buffer, flag);
        for (int i = 0; i < array.length; i++)
        {
            array[i] = cast(double)cast(int)paint.buffer[i];
        }
    }
    override void gsave(int savepage, int x, int y, int w, int h, int[] array, int flag)
    {
        if (w * h > array.length)
            return;
        auto oldPage = useGRP;
        if (savepage == -1)
        {
            glBindFramebufferEXT(GL_FRAMEBUFFER, petitcom.console.GRPF.buffer);
        }
        else
        {
            useGRP = savepage;
        }
        scope (exit)
        {
            useGRP = oldPage;
        }
        //endian?
        if (false && flag)//0=32bit
        {
            glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, array.ptr);
        }
        else
        {
            glReadPixels(x, y, w, h, GL_BGRA, GL_UNSIGNED_BYTE, array.ptr);
        }
        if (flag)
        {
            //ARRRRRGGGGGBBBBB
            for (int i = 0; i < array.length; i++)
            {
                array[i] = ARGB32ToRGBA16Color(array[i]);
            }
        }
    }
    override void gload(int x, int y, int w, int h, int[] array, int flag, int copymode)
    {
        if (w * h > array.length)
            return;
        if (!copymode)
        {
            glEnable(GL_ALPHA_TEST);
        }
        glRasterPos2i(x, y);
        //convertColor
        if (flag)
        {
            //ARRRRRGGGGGBBBBB
            for (int i = 0; i < array.length; i++)
            {
                paint.buffer[i] = RGBA16ToARGB32Color(array[i]);
            }
            glDrawPixels(w , h , GL_BGRA , GL_UNSIGNED_BYTE , paint.buffer.ptr);
        }
        else
        {
            glDrawPixels(w , h , GL_BGRA , GL_UNSIGNED_BYTE , array.ptr);
        }
        if (!copymode)
        {
            glDisable(GL_ALPHA_TEST);
        }
    }
    override void gload(int x, int y, int w, int h, double[] array, int flag, int copymode)
    {
        for (int i = 0; i < array.length; i++)
        {
            paint.buffer[i] = cast(int)array[i];
        }
        gload(x, y, w, h, cast(int[])paint.buffer, flag, copymode);
    }
    void gloadPalette(T, T2)(int x, int y, int w, int h, T[] array, T2[] palette, int copymode)
        if ((is(T == int) || is(T == double)) && (is(T2 == int) || is(T2 == double)))
        {
            for (int i = 0; i < array.length; i++)
            {
                paint.buffer[i] = cast(int)palette[cast(int)array[i]];
            }
            gload(x, y, w, h, cast(int[])paint.buffer, 0, copymode);
        }
}

class Graphic2 : Graphic
{
    this(PetitComputer p)
    {
        super(p);
    }
    override void initGraphicPages()
    {
        foreach(g; GRP)
        {
            if (g.glTexture)
                g.deleteGL();
        }
        super.initGraphicPages();
        foreach(g; GRP)
        {
            g.createTexture(petitcom.renderer, petitcom.textureScaleMode);
        }
    }
    override void initVM()
    {
        SDL_GL_MakeCurrent(petitcom.window, petitcom.contextVM);
        glAlphaFunc(GL_GEQUAL, 0.1f);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_ALPHA_TEST);
        glDisable(GL_DEPTH_TEST);
    }

    int olddisplay;
    override void display(int display)
    {
        if (df)
        {
            glBindTexture(GL_TEXTURE_2D, this.GRP[usePage[olddisplay]].glTexture);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, buffer);
            glFlush();
            df = false;
        }
        olddisplay = display;
        buffer = cast(int*)this.GRP[usePage[display]].surface.pixels;
    }

    bool df;
    void updateTexture()
    {
        glBindTexture(GL_TEXTURE_2D, this.GRP[useGRP].glTexture);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, buffer);
        glFlush();
    }
    override void updateVM()
    {
        if (df)
        {
            updateTexture;
            df = false;
        }
    }

    int convertColor(int color)
    {
        return color & 0xFFF8F8F8;
    }
    int* buffer;
    override @property void useGRP(int page)
    {
        updateVM;
        super.useGRP(page);
        buffer = cast(int*)this.GRP[useGRP].surface.pixels;
    }
    override @property int useGRP()
    {
        return super.useGRP;
    }

    override void gpset(int page, int x, int y, uint color)
    {
        color = convertColor(color);
        auto wa = writeArea[petitcom.displaynum];
        int cx2 = wa.x + wa.w;
        int cy2 = wa.y + wa.h;
        if (x >= wa.x && y >= wa.y && x < cx2 && y < cy2)
        {
            buffer[y * width + x] = color;
        }
        df = true;
    }

    import std.math;
    override void gline(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        int dx = abs(x2 - x);
        int dy = abs(y2 - y);
        int sx, sy;
        if (x < x2)
            sx = 1;
        else
            sx = -1;
        if (y < y2)
            sy = 1;
        else
            sy = -1;
        int err = dx - dy;

        auto wa = writeArea[petitcom.displaynum];
        int cx2 = wa.x + wa.w;
        int cy2 = wa.y + wa.h;
        while (true)
        {
            if (x >= wa.x && y >= wa.y && x < cx2 && y < cy2)
                buffer[y * width + x] = color;
            if (x == x2 && y == y2) break;
            int e2 = 2 * err;
            if (e2 > -dy)
            {
                err = err - dy;
                x = x + sx;
            }
            if (e2 <  dx)
            {
                err = err + dx;
                y = y + sy;
            }
        }
        df = true;
    }

    override void gbox(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        int sx, sy, ex, ey;
        if (x > x2)
        {
            sx = x2;
            ex = x;
        }
        else
        {
            sx = x;
            ex = x2;
        }
        if (y > y2)
        {
            sy = y2;
            ey = y;
        }
        else
        {
            sy = y;
            ey = y2;
        }
        auto wa = writeArea[petitcom.displaynum];
        int cx2 = wa.x + wa.w - 1;
        int cy2 = wa.y + wa.h - 1;
        void drawLine1(int y)
        {
            for (int x = sx; x <= ex; x++)
            {
                if (x >= wa.x && y >= wa.y && x <= cx2 && y <= cy2)
                    buffer[y * height + x] = color;
            }
        }
        drawLine1(sy);
        drawLine1(ey);
        void drawLine2(int x)
        {
            for (int y = sy; y <= ey; y++)
            {
                if (x >= wa.x && y >= wa.y && x <= cx2 && y <= cy2)
                    buffer[y * width + x] = color;
            }
        }
        drawLine2(ex);
        drawLine2(sx);
        df = true;
    }

    override void gfill(int page, int x, int y, int x2, int y2, uint color)
    {
        color = convertColor(color);
        int sx, sy, ex, ey;
        if (x > x2)
        {
            sx = x2;
            ex = x;
        }
        else
        {
            sx = x;
            ex = x2;
        }
        if (y > y2)
        {
            sy = y2;
            ey = y;
        }
        else
        {
            sy = y;
            ey = y2;
        }
        auto wa = writeArea[petitcom.displaynum];
        int cx2 = wa.x + wa.w - 1;
        int cy2 = wa.y + wa.h - 1;
        //clipping
        if (wa.x > sx)
        {
            if (wa.x > ex)
            {
                return;
            }
            sx = wa.x;
        }
        if (cx2 < ex)
        {
            if (cx2 < sx)
            {
                return;
            }
            ex = cx2;
        }
        if (wa.y > sy)
        {
            if (wa.y > ey)
            {
                return;
            }
            sy = wa.y;
        }
        if (cy2 < ey)
        {
            if (cy2 < sy)
            {
                return;
            }
            ey = cy2;
        }
        for (y = sy; y <= ey; y++)
        {
            (buffer + y * width + sx)[0..(ex - sx + 1)] = color;
        }
        df = true;
    }

    override void gcls(int page, uint color)
    {
        color = convertColor(color);
        gfill(page, 0, 0, width - 1, height - 1, color);
    }

    override void gpaint(int page, int x, int y, uint color)
    {
        color = convertColor(color);
    }

    override void gcircle(int page, int x0, int y0, int r, uint color)
    {
        color = convertColor(color);
        /*== http://fussy.web.fc2.com/algo/algo2-1.htm ==*/
        int x = r;
        int y = 0;
        int F = -2 * r + 3;

        void pset(int x, int y, int color)
        {
            auto wa = writeArea[petitcom.displaynum];
            int cx2 = wa.x + wa.w;
            int cy2 = wa.y + wa.h;
            if (x >= wa.x && y >= wa.y && x < cx2 && y < cy2)
            {
                buffer[y * width + x] = color;
            }
        }
        while (x >= y)
        {
            pset(x0 + x, y0 + y, color);
            pset(x0 - x, y0 + y, color);
            pset(x0 + x, y0 - y, color);
            pset(x0 - x, y0 - y, color);
            pset(x0 + y, y0 + x, color);
            pset(x0 - y, y0 + x, color);
            pset(x0 + y, y0 - x, color);
            pset(x0 - y, y0 - x, color);
            if (F >= 0)
            {
                x--;
                F -= 4 * x;
            }
            y++;
            F += 4 * y + 2;
        }
        /*== http://fussy.web.fc2.com/algo/algo2-1.htm ==*/
        df = true; 
    }

    override void gcircle(int page, int x, int y, int r, int startr, int endr, int flag, uint color)
    {
        color = convertColor(color);
    }

    override void gputchr(int page, int x, int y, int text, int scalex, int scaley, uint color)
    {
        color = convertColor(color);
        drawCharacter(x, y, scalex, scaley, color, cast(wchar)text);
        df = true;
    }

    int mulColor(int color, int color2)
    {
        ubyte r1, g1, b1, a1;
        ubyte r2, g2, b2, a2;
        PetitComputer.RGBRead(color, r1, g1, b1, a1);
        PetitComputer.RGBRead(color2, r2, g2, b2, a2);
        r1 = cast(ubyte)((r1 * r2) / 255);
        g1 = cast(ubyte)((g1 * g2) / 255);
        b1 = cast(ubyte)((b1 * b2) / 255);
        if (a2 && a1)
            a1 = 255;
        else
            a1 = 0;
        return PetitComputer.RGB(a1, r1, g1, b1);
    }
    bool clipXYWH(SDL_Rect wa, int x, int y, int w, int h, out int sx, out int sy, out int w2, out int h2)
    {
        int cx2 = wa.x + wa.w;
        int cy2 = wa.y + wa.h;
        w2 = w;
        h2 = h;
        //clipping
        if (wa.x > x)
        {
            sx = wa.x - x;
            w2 -= sx;
            if (sx >= w)
            {
                return true;
            }
        }
        if (wa.w + wa.x < x + sx + w2)
        {
            w2 = w2 - ((x + sx + w2) - (wa.w + wa.x));
            if (w < 1)
            {
                return true;
            }
        }
        if (wa.y > y)
        {
            sy = wa.y - y;
            h2 -= sy;
            if (sy >=  h)
            {
                return true;
            }
        }
        if (wa.h + wa.y < y + sy + h2)
        {
            h2 = h2 - ((y + sy + h2) - (wa.h + wa.y));
            if (h < 1)
            {
                return true;
            }
        }
        return false;
    }
    void drawCharacter(int x, int y, int scalex, int scaley, uint color, immutable wchar c)
    {
        auto rect = petitcom.console.fontTable[c];
        int[8 * 8] font;
        auto wa = writeArea[petitcom.displaynum];
        int cx2 = wa.x + wa.w;
        int cy2 = wa.y + wa.h;
        int sx, sy, w = rect.w * scalex, h = rect.h * scaley;
        //clipping
        if (wa.x > x)
        {
            sx = wa.x - x;
            w -= sx;
            if (sx >=  rect.w)
            {
                return;
            }
        }
        if (wa.w + wa.x < x + sx + w)
        {
            w = w - ((x + sx + w) - (wa.w + wa.x));
            if (w < 1)
            {
                return;
            }
        }
        if (wa.y > y)
        {
            sy = wa.y - y;
            h -= sy;
            if (sy >=  rect.h)
            {
                return;
            }
        }
        if (wa.h < h)
        {
            h = h - ((y + sy + h) - (wa.h + wa.y));
            if (h < 1)
            {
                return;
            }
        }
        int* grpfbuffer = cast(int*)petitcom.console.GRPF.surface.pixels;
        if (scalex == 1 && scaley == 1)
        {
            for (int i = sy; i < sy + h; i ++)
            {
                for (int j = sx; j < sx + w; j++)
                {
                    font[i * 8 + j] = mulColor(grpfbuffer[rect.x + j + (rect.y + i) * GRPFWidth], color);
                }
            }
            for (int iy = sy; iy < sy + h; iy++)
            {
                for (int ix = sx; ix < sx + w; ix++)
                {
                    if (font[iy * 8 + ix] >> 24/*is trans*/)
                    {
                        (buffer + (y + iy) * width + x + ix)[0] = font[iy * 8 + ix];
                    }
                }
            }
            return;
        }
        for (int i = sy / scaley; i < (sy + h) / scaley; i ++)
        {
            for (int j = sx / scalex; j < (sx + w) / scalex; j++)
            {
                font[i * 8 + j] = mulColor(grpfbuffer[rect.x + j + (rect.y + i) * GRPFWidth], color);
            }
        }
        for (int iy = sy; iy < sy + h; iy++)
        {
            for (int ix = sx; ix < sx + w; ix++)
            {
                if (font[iy / scaley * 8 + ix / scalex] >> 24/*is trans*/)
                {
                    (buffer + (y + iy) * width + x + ix)[0] = font[iy / scaley * 8 + ix / scalex];
                }
            }
        }
    }
    override void gputchr(int page, int x, int y, wstring text, int scalex, int scaley, uint color)
    {
        color = convertColor(color);
        foreach (i, c; text)
        {
            drawCharacter(x + scalex * cast(int)i * 8, y, scalex, scaley, color, c);
        }
        df = true;
    }

    override int gspoit(int page, int x, int y)
    {
        return buffer[y * height + x];
    }

    void gsaveTempl(T)(int savepage, int x, int y, int w, int h, T array, int flag)
    {
        int arrayH = h, arrayW = w;
        int sx, sy;
        int* buffer = cast(int*)GRP[savepage].surface.pixels;
        for (int iy = sy; iy < h; iy++)
        {
            for (int ix = sx; ix < w; ix++)
            {
                if (x >= 0 && y >= 0 && x < width && y < height)
                {
                    auto c = *(buffer + (iy + y) * width + (x + ix));
                    array[iy * arrayW + ix] = flag ? ARGB32ToRGBA16Color(c) : c;
                }
                else
                    array[iy * arrayW + ix] = 0;
            }
        }
    }

    override void gsave(int savepage, int x, int y, int w, int h, double[] array, int flag)
    {
        gsaveTempl(savepage, x, y, w, h, array, flag);
    }

    override void gsave(int savepage, int x, int y, int w, int h, int[] array, int flag)
    {
        gsaveTempl(savepage, x, y, w, h, array, flag);
    }

    void gloadTempl(T)(int x, int y, int w, int h, T array, int flag, int copymode)
    {
        int arrayH = h, arrayW = w;
        int sx, sy;
        if (clipXYWH(writeArea[petitcom.displaynum], x, y, w, h, sx, sy, w, h))
            return;
        for (int iy = sy; iy < sy + h; iy++)
        {
            for (int ix = sx; ix < sx + w; ix++)
            {
                auto c = cast(int)array[iy * arrayW + ix];
                if (flag)
                    c = RGBA16ToARGB32Color(c);
                c = convertColor(c);
                if (copymode || c >> 24)
                    *(buffer + (iy + y) * width + (x + ix)) = c;
            }
        }
        df = true;
    }

    override void gload(int x, int y, int w, int h, int[] array, int flag, int copymode)
    {
        gloadTempl(x, y, w, h, array, flag, copymode);
    }

    override void gload(int x, int y, int w, int h, double[] array, int flag, int copymode)
    {
        gloadTempl(x, y, w, h, array, flag, copymode);
    }

    void gloadPalette(T, T2)(int x, int y, int w, int h, T[] array, T2[] palette, int copymode)
    if ((is(T == int) || is(T == double)) && (is(T2 == int) || is(T2 == double)))
    {
        int arrayH = h, arrayW = w;
        int sx, sy;
        if (clipXYWH(writeArea[petitcom.displaynum], x, y, w, h, sx, sy, w, h))
            return;
        for (int iy = sy; iy < sy + h; iy++)
        {
            for (int ix = sx; ix < sx + w; ix++)
            {
                auto c = cast(int)palette[cast(int)array[iy * arrayW + ix]];
                c = convertColor(c);
                if (copymode || c >> 24)
                    *(buffer + (iy + y) * width + (x + ix)) = c;
            }
        }
        df = true;
    }
    //incomplete
    override void gcopy(int srcpage, int x1, int y1, int x2, int y2, int x3, int y3, int cpmode)
    {
        import std.algorithm;
        auto tempbuffer = paint.buffer;//aa
        if (x1 > x2)
            swap(x1, x2);
        if (y1 > y2)
            swap(y1, y2);
        int w = x2 - x1 + 1, h = y2 - y1 + 1;
        gsaveTempl(srcpage, x1, y1, w, h, tempbuffer, 0);
        gloadTempl(x3, y3, w, h, tempbuffer, 0, cpmode);
    }
    override void gtri(int x1, int y1, int x2, int y2, int x3, int y3, int color)
    {
        import std.algorithm, std.typecons, std.math;
        df = true;
        Tuple!(int, "x", int, "y") drawSize = tuple(511, 511);
        void pset(int x, int y)
        {
            auto wa = writeArea[petitcom.displaynum];
            int cx2 = wa.x + wa.w;
            int cy2 = wa.y + wa.h;
            if (x >= wa.x && y >= wa.y && x < cx2 && y < cy2)
            {
                buffer[y * width + x] = color;
            }
        }
        //====http://fussy.web.fc2.com/algo/polygon3_misc.htm====
        /*
        TriFill_XDraw : 三角形描画スキャンライン描画

        DrawingArea_IF& draw : 描画領域
        GPixelOp& pset : 点描画に使う関数オブジェクト
        pair<double, double> &l, &r : スキャンラインの両端座標の X 成分と増分の pair
        int& sy : 描画開始 Y 座標(描画終了時に次の Y 座標を返す)
        int ey : 描画終了 Y 座標
        */
        void TriFill_XDraw(ref Tuple!(double, double) l, ref Tuple!(double, double) r, ref int sy, int ey )
        {
            for ( ; sy < ey ; ++sy ) {
                int sx = cast(int)round( l[0]); // 描画開始 X 座標
                int ex = cast(int)round( r[0]); // 描画終了 X 座標

                // X 座標のクリッピング
                if ( sx < 0 ) sx = 0;
                if ( ex >= drawSize.x ) ex = drawSize.x - 1;

                // スキャンライン描画
                for ( ; sx <= ex ; ++sx )
                    pset(sx, sy);

                // X 座標の更新
                l[0] += l[1];
                r[0] += r[1];
            }
        }

        /*
        TriFill_Main : 三角形描画用 メイン・ルーチン

        DrawingArea_IF& draw : 描画領域
        GPixelOp& pset : 点描画に使う関数オブジェクト
        Coord<int> &top, &middle, &bottom : 三角形の頂角の座標(上側・中央・下側の順)
        */
        void TriFill_Main(const ref Tuple!(int, "x", int, "y") top, const ref Tuple!(int, "x", int, "y") middle, const ref Tuple!(int, "x", int, "y") bottom )
        {
            // 上側の頂点からの描画開始 X 座標(頂角が描画領域外の場合、異なる座標になる)
            double top_mid_x = top.x; // top - middle
            double top_btm_x = top.x; // top - bottom

            // 上側に水平な辺がある場合は中央の頂点で初期化する
            if ( top.y == middle.y )
                top_mid_x = middle.x;

            int sy = top.y;    // 描画開始 Y 座標
            int my = middle.y; // 中央の頂点の Y 座標
            int ey = bottom.y; // 描画終了 Y 座標

            // クリッピング

            // 上側の頂点が領域外の場合
            if ( top.y < 0 ) {
                sy = 0;
                // 上側から中央への辺をクリッピング
                if ( middle.y >= 0 ) {
                    if ( top.y != middle.y )
                        top_mid_x = cast(double)( middle.x - top.x ) * cast(double)middle.y / cast(double)( top.y - middle.y ) + cast(double)middle.x;
                } else {
                    if ( middle.y != bottom.y )
                        top_mid_x = cast(double)( bottom.x - middle.x ) * cast(double)bottom.y / cast(double)( middle.y - bottom.y ) + cast(double)bottom.x;
                }
                // 上側から下側への辺をクリッピング
                if ( top.y != bottom.y )
                    top_btm_x = cast(double)( bottom.x - top.x ) * cast(double)bottom.y / cast(double)( top.y - bottom.y ) + cast(double)bottom.x;
            }

            // 下側の頂点が領域外の場合は描画終了 Y 座標を描画領域内にする
            if ( bottom.y >= drawSize.y )
                ey = drawSize.y - 1;

            // X 座標に対する増分
            double top_mid_a = ( middle.y != top.y ) ?
                cast(double)( middle.x - top.x ) / cast(double)( middle.y - top.y ) : 0;       // top - middle
            double mid_btm_a = ( middle.y != bottom.y ) ?
                cast(double)( middle.x - bottom.x ) / cast(double)( middle.y - bottom.y ) : 0; // middle - bottom
            double top_btm_a = ( top.y != bottom.y ) ?
                cast(double)( top.x - bottom.x ) / cast(double)( top.y - bottom.y ) : 0;       // top - bottom

            // 描画開始 X 座標とその増分の pair
            Tuple!(double, double) top_mid = tuple( top_mid_x, top_mid_a ); // top - middle
            Tuple!(double, double) top_btm = tuple( top_btm_x, top_btm_a ); // top - bottom

            // 中央の頂点が右向きか左向きかを判定して、各辺が左側・右側ののいずれかを決定する
            // 中央の頂点を通る水平線が、上側・下側を通る直線と交わる点の X 座標
            int splitLine_x = ( top.y != bottom.y ) ?
                ( top.x - bottom.x ) * ( middle.y - top.y ) / ( top.y - bottom.y ) + top.x :
            bottom.x; // 中央・下側の Y 座標が等しい場合、下側の X 座標
            Tuple!(double, double)* l = ( middle.x < splitLine_x ) ? &top_mid : &top_btm; // 左側
            Tuple!(double, double)* r = ( middle.x < splitLine_x ) ? &top_btm : &top_mid; // 右側

            // 描画開始
            TriFill_XDraw(*l, *r, sy, my );
            top_mid[1] = mid_btm_a;
            TriFill_XDraw(*l, *r, sy, ey + 1 );
        }

        /*
        TriFill : 三角形描画用ルーチン 前処理

        DrawingArea_IF& draw : 描画領域
        GPixelOp& pset : 点描画に使う関数オブジェクト
        Coord<int> c1, c2, c3 : 三角形の頂点
        */
        void TriFill(Tuple!(int, "x", int, "y") c1, Tuple!(int, "x", int, "y") c2, Tuple!(int, "x", int, "y") c3 )
        {
            // Y 座標で昇順にソート
            if ( c1.y > c2.y ) swap( c1, c2 );
            if ( c1.y > c3.y ) swap( c1, c3 );
            if ( c2.y > c3.y ) swap( c2, c3 );

            // ポリゴンが描画領域外なら処理しない
            if ( c1.y >= drawSize.y ) return;
            if ( c3.y < 0 ) return;

            // 描画ルーチン メインへ
            TriFill_Main(c1, c2, c3 );
        }
        //====http://fussy.web.fc2.com/algo/polygon3_misc.htm====
        TriFill(Tuple!(int, "x", int, "y")(x1, y1), Tuple!(int, "x", int, "y")(x2, y2), Tuple!(int, "x", int, "y")(x3, y3));
    }
}
