#include "SkGL.h"
#include "SkColorPriv.h"
#include "SkGeometry.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkTemplates.h"
#include "SkXfermode.h"

//#define TRACE_TEXTURE_CREATION

///////////////////////////////////////////////////////////////////////////////

#ifdef SK_GL_HAS_COLOR4UB
static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) {
    glColor4ub(r, g, b, a);
}

void SkGL::SetAlpha(U8CPU alpha) {
    glColor4ub(alpha, alpha, alpha, alpha);
}
#else
static inline SkFixed byte2fixed(U8CPU value) {
    return (value + (value >> 7)) << 8;
}

static inline void gl_pmcolor(U8CPU r, U8CPU g, U8CPU b, U8CPU a) {
    glColor4x(byte2fixed(r), byte2fixed(g), byte2fixed(b), byte2fixed(a));
}

void SkGL::SetAlpha(U8CPU alpha) {
    SkFixed fa = byte2fixed(alpha);
    glColor4x(fa, fa, fa, fa);
}
#endif

void SkGL::SetColor(SkColor c) {
    SkPMColor pm = SkPreMultiplyColor(c);
    gl_pmcolor(SkGetPackedR32(pm),
               SkGetPackedG32(pm),
               SkGetPackedB32(pm),
               SkGetPackedA32(pm));
}

static const GLenum gXfermodeCoeff2Blend[] = {
    GL_ZERO,
    GL_ONE,
    GL_SRC_COLOR,
    GL_ONE_MINUS_SRC_COLOR,
    GL_DST_COLOR,
    GL_ONE_MINUS_DST_COLOR,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
    GL_DST_ALPHA,
    GL_ONE_MINUS_DST_ALPHA,
};

void SkGL::SetPaint(const SkPaint& paint, bool isPremul, bool justAlpha) {
    if (justAlpha) {
        SkGL::SetAlpha(paint.getAlpha());
    } else {
        SkGL::SetColor(paint.getColor());
    }

    GLenum sm = GL_ONE;
    GLenum dm = GL_ONE_MINUS_SRC_ALPHA;

    SkXfermode* mode = paint.getXfermode();
    SkXfermode::Coeff sc, dc;
    if (mode && mode->asCoeff(&sc, &dc)) {
        sm = gXfermodeCoeff2Blend[sc];
        dm = gXfermodeCoeff2Blend[dc];
    }
    
    // hack for text, which is not-premul (afaik)
    if (!isPremul) {
        if (GL_ONE == sm) {
            sm = GL_SRC_ALPHA;
        }
    }
    
    glEnable(GL_BLEND);
    glBlendFunc(sm, dm);
    
    if (paint.isDither()) {
        glEnable(GL_DITHER);
    } else {
        glDisable(GL_DITHER);
    }
}

///////////////////////////////////////////////////////////////////////////////

void SkGL::DumpError(const char caller[]) {
    GLenum err = glGetError();
    if (err) {
        SkDebugf("---- glGetError(%s) %d\n", caller, err);
    }
}

void SkGL::SetRGBA(uint8_t rgba[], const SkColor src[], int count) {
    for (int i = 0; i < count; i++) {
        SkPMColor c = SkPreMultiplyColor(*src++);
        *rgba++ = SkGetPackedR32(c);
        *rgba++ = SkGetPackedG32(c);
        *rgba++ = SkGetPackedB32(c);
        *rgba++ = SkGetPackedA32(c);
    }
}

///////////////////////////////////////////////////////////////////////////////

void SkGL::Scissor(const SkIRect& r, int viewportHeight) {
    glScissor(r.fLeft, viewportHeight - r.fBottom, r.width(), r.height());
}

///////////////////////////////////////////////////////////////////////////////

void SkGL::Ortho(float left, float right, float bottom, float top,
                 float near, float far) {
    
    float mat[16];
    
    bzero(mat, sizeof(mat));
    
    mat[0] = 2 / (right - left);
    mat[5] = 2 / (top - bottom);
    mat[10] = 2 / (near - far);
    mat[15] = 1;
    
    mat[12] = (right + left) / (left - right);
    mat[13] = (top + bottom) / (bottom - top);
    mat[14] = (far + near) / (near - far);
    
    glMultMatrixf(mat);
}

///////////////////////////////////////////////////////////////////////////////

static bool canBeTexture(const SkBitmap& bm, GLenum* format, GLenum* type) {
    switch (bm.config()) {
        case SkBitmap::kARGB_8888_Config:
            *format = GL_RGBA;
            *type = GL_UNSIGNED_BYTE;
            break;
        case SkBitmap::kRGB_565_Config:
            *format = GL_RGB;
            *type = GL_UNSIGNED_SHORT_5_6_5;
            break;
        case SkBitmap::kARGB_4444_Config:
            *format = GL_RGBA;
            *type = GL_UNSIGNED_SHORT_4_4_4_4;
            break;
        case SkBitmap::kIndex8_Config:
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
            *format = GL_PALETTE8_RGBA8_OES;
            *type = GL_UNSIGNED_BYTE;   // unused I think
#else
            // we promote index to argb32
            *format = GL_RGBA;
            *type = GL_UNSIGNED_BYTE;
#endif
            break;
        case SkBitmap::kA8_Config:
            *format = GL_ALPHA;
            *type = GL_UNSIGNED_BYTE;
            break;
        default:
            return false;
    }
    return true;
}

#define SK_GL_SIZE_OF_PALETTE   (256 * sizeof(SkPMColor))

size_t SkGL::ComputeTextureMemorySize(const SkBitmap& bitmap) {
    int shift = 0;
    size_t adder = 0;
    switch (bitmap.config()) {
        case SkBitmap::kARGB_8888_Config:
        case SkBitmap::kRGB_565_Config:
        case SkBitmap::kARGB_4444_Config:
        case SkBitmap::kA8_Config:
            // we're good as is
            break;
        case SkBitmap::kIndex8_Config:
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
            // account for the colortable
            adder = SK_GL_SIZE_OF_PALETTE;
#else
            // we promote index to argb32
            shift = 2;
#endif
            break;
        default:
            return 0;
    }
    return (bitmap.getSize() << shift) + adder;
}

#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
/*  Fill out buffer with the compressed format GL expects from a colortable
    based bitmap. [palette (colortable) + indices].

    At the moment I always take the 8bit version, since that's what my data
    is. I could detect that the colortable.count is <= 16, and then repack the
    indices as nibbles to save RAM, but it would take more time (i.e. a lot
    slower than memcpy), so I'm skipping that for now.
 
    GL wants a full 256 palette entry, even though my ctable is only as big
    as the colortable.count says it is. I presume it is OK to leave any
    trailing entries uninitialized, since none of my indices should exceed
    ctable->count().
*/
static void build_compressed_data(void* buffer, const SkBitmap& bitmap) {
    SkASSERT(SkBitmap::kIndex8_Config == bitmap.config());

    SkColorTable* ctable = bitmap.getColorTable();
    uint8_t* dst = (uint8_t*)buffer;

    memcpy(dst, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
    ctable->unlockColors(false);

    // always skip a full 256 number of entries, even if we memcpy'd fewer
    dst += SK_GL_SIZE_OF_PALETTE;
    memcpy(dst, bitmap.getPixels(), bitmap.getSize());
}
#endif

/*  Return true if the bitmap cannot be supported in its current config as a
    texture, and it needs to be promoted to ARGB32.
 */
static bool needToPromoteTo32bit(const SkBitmap& bitmap) {
    if (bitmap.config() == SkBitmap::kIndex8_Config) {
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
        const int w = bitmap.width();
        const int h = bitmap.height();
        if (SkNextPow2(w) == w && SkNextPow2(h) == h) {
            // we can handle Indx8 if we're a POW2
            return false;
        }
#endif
        return true;    // must promote to ARGB32
    }
    return false;
}

GLuint SkGL::BindNewTexture(const SkBitmap& origBitmap, SkPoint* max) {
    SkBitmap tmpBitmap;
    const SkBitmap* bitmap = &origBitmap;

    if (needToPromoteTo32bit(origBitmap)) {
        origBitmap.copyTo(&tmpBitmap, SkBitmap::kARGB_8888_Config);
        // now bitmap points to our temp, which has been promoted to 32bits
        bitmap = &tmpBitmap;
    }
    
    GLenum format, type;
    if (!canBeTexture(*bitmap, &format, &type)) {
        return 0;
    }
    
    SkAutoLockPixels alp(*bitmap);
    if (!bitmap->readyToDraw()) {
        return 0;
    }
    
    GLuint  textureName;
    glGenTextures(1, &textureName);
    
    glBindTexture(GL_TEXTURE_2D, textureName);
    
    // express rowbytes as a number of pixels for ow
    int ow = bitmap->rowBytesAsPixels();
    int oh = bitmap->height();
    int nw = SkNextPow2(ow);
    int nh = SkNextPow2(oh);
    
    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
    
    // check if we need to scale to create power-of-2 dimensions
#ifdef SK_GL_SUPPORT_COMPRESSEDTEXIMAGE2D
    if (SkBitmap::kIndex8_Config == bitmap->config()) {
        size_t imagesize = bitmap->getSize() + SK_GL_SIZE_OF_PALETTE;
        SkAutoMalloc storage(imagesize);

        build_compressed_data(storage.get(), *bitmap);
        // we only support POW2 here (GLES 1.0 restriction)
        SkASSERT(ow == nw);
        SkASSERT(oh == nh);
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0,
                               imagesize, storage.get());
    } else  // fall through to non-compressed logic
#endif
    {
        if (ow != nw || oh != nh) {
            glTexImage2D(GL_TEXTURE_2D, 0, format, nw, nh, 0,
                         format, type, NULL);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ow, oh,
                            format, type, bitmap->getPixels());
        } else {
            // easy case, the bitmap is already pow2
            glTexImage2D(GL_TEXTURE_2D, 0, format, ow, oh, 0,
                         format, type, bitmap->getPixels());
        }
    }
    
#ifdef TRACE_TEXTURE_CREATION
    SkDebugf("--- new texture [%d] size=(%d %d) bpp=%d\n", textureName, ow, oh,
             bitmap->bytesPerPixel());
#endif

    if (max) {
        max->fX = SkFixedToScalar(bitmap->width() << (16 - SkNextLog2(nw)));
        max->fY = SkFixedToScalar(oh << (16 - SkNextLog2(nh)));
    }
    return textureName;
}

static const GLenum gTileMode2GLWrap[] = {
    GL_CLAMP_TO_EDGE,
    GL_REPEAT,
#if GL_VERSION_ES_CM_1_0
    GL_REPEAT       // GLES doesn't support MIRROR
#else
    GL_MIRRORED_REPEAT
#endif
};

void SkGL::SetTexParams(bool doFilter,
                        SkShader::TileMode tx, SkShader::TileMode ty) {
    SkASSERT((unsigned)tx < SK_ARRAY_COUNT(gTileMode2GLWrap));
    SkASSERT((unsigned)ty < SK_ARRAY_COUNT(gTileMode2GLWrap));
    
    GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST;

    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gTileMode2GLWrap[tx]);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gTileMode2GLWrap[ty]);
}

void SkGL::SetTexParamsClamp(bool doFilter) {
    GLenum filter = doFilter ? GL_LINEAR : GL_NEAREST;

    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

///////////////////////////////////////////////////////////////////////////////

void SkGL::DrawVertices(int count, GLenum mode,
                        const SkGLVertex* SK_RESTRICT vertex,
                        const SkGLVertex* SK_RESTRICT texCoords,
                        const uint8_t* SK_RESTRICT colorArray,
                        const uint16_t* SK_RESTRICT indexArray,
                        SkGLClipIter* iter) {
    SkASSERT(NULL != vertex);
    
    if (NULL != texCoords) {
        glEnable(GL_TEXTURE_2D);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, SK_GLType, 0, texCoords);
    } else {
        glDisable(GL_TEXTURE_2D);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    
    if (NULL != colorArray) {
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray);
        glShadeModel(GL_SMOOTH); 
    } else {
        glDisableClientState(GL_COLOR_ARRAY);
        glShadeModel(GL_FLAT); 
    }
    
    glVertexPointer(2, SK_GLType, 0, vertex);

    if (NULL != indexArray) {
        if (iter) {
            while (!iter->done()) {
                iter->scissor();
                glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray);
                iter->next();
            }
        } else {
            glDrawElements(mode, count, GL_UNSIGNED_SHORT, indexArray);
        }
    } else {
        if (iter) {
            while (!iter->done()) {
                iter->scissor();
                glDrawArrays(mode, 0, count);
                iter->next();
            }
        } else {
            glDrawArrays(mode, 0, count);
        }
    }
}

void SkGL::PrepareForFillPath(SkPaint* paint) {
    if (paint->getStrokeWidth() <= 0) {
        paint->setStrokeWidth(SK_Scalar1);
    }
}

void SkGL::FillPath(const SkPath& path, const SkPaint& paint, bool useTex,
                    SkGLClipIter* iter) {
    SkPaint p(paint);
    SkPath  fillPath;
    
    SkGL::PrepareForFillPath(&p);
    p.getFillPath(path, &fillPath);
    SkGL::DrawPath(fillPath, useTex, iter);
}

// should return max of all contours, rather than the sum (to save temp RAM)
static int worst_case_edge_count(const SkPath& path) {
    int edgeCount = 0;
    
    SkPath::Iter    iter(path, true);
    SkPath::Verb    verb;
    
    while ((verb = iter.next(NULL)) != SkPath::kDone_Verb) {
        switch (verb) {
            case SkPath::kLine_Verb:
                edgeCount += 1;
                break;
            case SkPath::kQuad_Verb:
                edgeCount += 8;
                break;
            case SkPath::kCubic_Verb:
                edgeCount += 16;
                break;
            default:
                break;
        }
    }
    return edgeCount;
}

void SkGL::DrawPath(const SkPath& path, bool useTex, SkGLClipIter* clipIter) {
    SkRect  bounds;
    
    path.computeBounds(&bounds, SkPath::kFast_BoundsType);
    if (bounds.isEmpty()) {
        return;
    }
    
    int maxPts = worst_case_edge_count(path);
    // add 1 for center of fan, and 1 for closing edge
    SkAutoSTMalloc<32, SkGLVertex>  storage(maxPts + 2);
    SkGLVertex* base = storage.get();
    SkGLVertex* vert = base;
    SkGLVertex* texs = useTex ? base : NULL;

    SkPath::Iter    pathIter(path, true);
    SkPoint         pts[4];
    
    bool needEnd = false;
    
    for (;;) {
        switch (pathIter.next(pts)) {
            case SkPath::kMove_Verb:
                if (needEnd) {
                    SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN,
                                       base, texs, NULL, NULL, clipIter);
                    clipIter->safeRewind();
                    vert = base;
                }
                needEnd = true;
                // center of the FAN
                vert->setScalars(bounds.centerX(), bounds.centerY());
                vert++;
                // add first edge point
                vert->setPoint(pts[0]);
                vert++;
                break;
                case SkPath::kLine_Verb:
                vert->setPoint(pts[1]);
                vert++;
                break;
                case SkPath::kQuad_Verb: {
                    const int n = 8;
                    const SkScalar dt = SK_Scalar1 / n;
                    SkScalar t = dt;
                    for (int i = 1; i < n; i++) {
                        SkPoint loc;
                        SkEvalQuadAt(pts, t, &loc, NULL);
                        t += dt;
                        vert->setPoint(loc);
                        vert++;
                    }
                    vert->setPoint(pts[2]);
                    vert++;
                    break;
                }
                case SkPath::kCubic_Verb: {
                    const int n = 16;
                    const SkScalar dt = SK_Scalar1 / n;
                    SkScalar t = dt;
                    for (int i = 1; i < n; i++) {
                        SkPoint loc;
                        SkEvalCubicAt(pts, t, &loc, NULL, NULL);
                        t += dt;
                        vert->setPoint(loc);
                        vert++;
                    }
                    vert->setPoint(pts[3]);
                    vert++;
                    break;
                }
                case SkPath::kClose_Verb:
                break;
                case SkPath::kDone_Verb:
                goto FINISHED;
        }
    }
FINISHED:
    if (needEnd) {
        SkGL::DrawVertices(vert - base, GL_TRIANGLE_FAN, base, texs,
                           NULL, NULL, clipIter);
    }
}