/*
 * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "GlyphImageRef.h"

#ifdef HEADLESS
#include "SurfaceData.h"
#else
#include "X11SurfaceData.h"
#include "GraphicsPrimitiveMgr.h"
#endif /* !HEADLESS */
#include <jlong.h>

#define TEXT_BM_WIDTH   1024
#define TEXT_BM_HEIGHT  32

#ifndef HEADLESS

static jboolean checkPixmap(JNIEnv *env, AwtGraphicsConfigDataPtr cData)
{
    XImage *img;
    int image_size;
    Window root;

    if (cData->monoImage == NULL) {
        img = XCreateImage(awt_display, NULL, 1, XYBitmap, 0, 0,
                                   TEXT_BM_WIDTH, TEXT_BM_HEIGHT, 32, 0);
        if (img != NULL) {
            image_size = img->bytes_per_line * TEXT_BM_HEIGHT;
            // assert(BM_W and BM_H are not large enough to overflow);
            img->data = (char *) malloc(image_size);
            if (img->data == NULL) {
                XFree(img);
            } else {
                // Force same bit/byte ordering
                img->bitmap_bit_order = img->byte_order;
                cData->monoImage = img;
            }
        }
        if (cData->monoImage == NULL) {
            JNU_ThrowOutOfMemoryError(env, "Cannot allocate bitmap for text");
            return JNI_FALSE;
        }
    }
    if (cData->monoPixmap == 0 ||
        cData->monoPixmapGC == NULL ||
        cData->monoPixmapWidth != TEXT_BM_WIDTH ||
        cData->monoPixmapHeight != TEXT_BM_HEIGHT)
    {
        if (cData->monoPixmap != 0) {
            XFreePixmap(awt_display, cData->monoPixmap);
            cData->monoPixmap = 0;
        }
        if (cData->monoPixmapGC != NULL) {
            XFreeGC(awt_display, cData->monoPixmapGC);
            cData->monoPixmapGC = 0;
        }
        root = RootWindow(awt_display, cData->awt_visInfo.screen);
        cData->monoPixmap = XCreatePixmap(awt_display, root,
                                          TEXT_BM_WIDTH, TEXT_BM_HEIGHT, 1);
        if (cData->monoPixmap == 0) {
            JNU_ThrowOutOfMemoryError(env, "Cannot allocate pixmap for text");
            return JNI_FALSE;
        }
        cData->monoPixmapGC = XCreateGC(awt_display, cData->monoPixmap,
                                        0, NULL);
        if (cData->monoPixmapGC == NULL) {
            XFreePixmap(awt_display, cData->monoPixmap);
            cData->monoPixmap = 0;
            JNU_ThrowOutOfMemoryError(env, "Cannot allocate pixmap for text");
            return JNI_FALSE;
        }
        XSetForeground(awt_display, cData->monoPixmapGC, 1);
        XSetBackground(awt_display, cData->monoPixmapGC, 0);
        cData->monoPixmapWidth = TEXT_BM_WIDTH;
        cData->monoPixmapHeight = TEXT_BM_HEIGHT;
    }
    return JNI_TRUE;
}

static void FillBitmap(XImage *theImage,
                       ImageRef *glyphs, jint totalGlyphs,
                       jint clipLeft, jint clipTop,
                       jint clipRight, jint clipBottom)
{
    int glyphCounter;
    int scan = theImage->bytes_per_line;
    int y, left, top, right, bottom, width, height;
    jubyte *pPix;
    const jubyte *pixels;
    unsigned int rowBytes;

    pPix = (jubyte *) theImage->data;
    glyphCounter = ((clipRight - clipLeft) + 7) >> 3;
    for (y = clipTop; y < clipBottom; y++) {
        memset(pPix, 0, glyphCounter);
        pPix += scan;
    }

    for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
        pixels = (const jubyte *)glyphs[glyphCounter].pixels;
        if (!pixels) {
            continue;
        }
        rowBytes = glyphs[glyphCounter].width;
        left     = glyphs[glyphCounter].x;
        top      = glyphs[glyphCounter].y;
        width    = glyphs[glyphCounter].width;
        height   = glyphs[glyphCounter].height;

        /* if any clipping required, modify parameters now */
        right  = left + width;
        bottom = top + height;
        if (left < clipLeft) {
            pixels += clipLeft - left;
            left = clipLeft;
        }
        if (top < clipTop) {
            pixels += (clipTop - top) * rowBytes;
            top = clipTop;
        }
        if (right > clipRight) {
            right = clipRight;
        }
        if (bottom > clipBottom) {
            bottom = clipBottom;
        }
        if (right <= left || bottom <= top) {
            continue;
        }
        width = right - left;
        height = bottom - top;
        top -= clipTop;
        left -= clipLeft;
        pPix = ((jubyte *) theImage->data) + (left >> 3) + top * scan;
        left &= 0x07;
        if (theImage->bitmap_bit_order == MSBFirst) {
            left = 0x80 >> left;
            do {
                int x = 0, bx = 0;
                int pix = pPix[0];
                int bit = left;
                do {
                    if (bit == 0) {
                        pPix[bx] = (jubyte) pix;
                        pix = pPix[++bx];
                        bit = 0x80;
                    }
                    if (pixels[x]) {
                        pix |= bit;
                    }
                    bit >>= 1;
                } while (++x < width);
                pPix[bx] = (jubyte) pix;
                pPix += scan;
                pixels += rowBytes;
            } while (--height > 0);
        } else {
            left = 1 << left;
            do {
                int x = 0, bx = 0;
                int pix = pPix[0];
                int bit = left;
                do {
                    if ((bit >> 8) != 0) {
                        pPix[bx] = (jubyte) pix;
                        pix = pPix[++bx];
                        bit = 1;
                    }
                    if (pixels[x]) {
                        pix |= bit;
                    }
                    bit <<= 1;
                } while (++x < width);
                pPix[bx] = (jubyte) pix;
                pPix += scan;
                pixels += rowBytes;
            } while (--height > 0);
        }
    }
}
#endif /* !HEADLESS */

JNIEXPORT void JNICALL
AWTDrawGlyphList(JNIEnv *env, jobject xtr,
                 jlong dstData, jlong gc,
                 SurfaceDataBounds *bounds, ImageRef *glyphs, jint totalGlyphs)
{
#ifndef HEADLESS
    GC xgc, theGC;
    XImage *theImage;
    Pixmap thePixmap;
    XGCValues xgcv;
    int scan, screen;
    AwtGraphicsConfigDataPtr cData;
    X11SDOps *xsdo = (X11SDOps *)jlong_to_ptr(dstData);
    jint cx1, cy1, cx2, cy2;

    if (xsdo == NULL) {
        return;
    }

    xgc = (GC)gc;
    if (xgc == NULL) {
        return;
    }

    screen = xsdo->configData->awt_visInfo.screen;
    cData = getDefaultConfig(screen);
    if (!checkPixmap(env, cData)) {
        return;
    }
    theImage = cData->monoImage;
    thePixmap = cData->monoPixmap;
    theGC = cData->monoPixmapGC;

    scan = theImage->bytes_per_line;

    xgcv.fill_style = FillStippled;
    xgcv.stipple = thePixmap;
    xgcv.ts_x_origin = bounds->x1;
    xgcv.ts_y_origin = bounds->y1;
    XChangeGC(awt_display, xgc,
              GCFillStyle | GCStipple | GCTileStipXOrigin | GCTileStipYOrigin,
              &xgcv);

    cy1 = bounds->y1;
    while (cy1 < bounds->y2) {
        cy2 = cy1 + TEXT_BM_HEIGHT;
        if (cy2 > bounds->y2) cy2 = bounds->y2;

        cx1 = bounds->x1;
        while (cx1 < bounds->x2) {
            cx2 = cx1 + TEXT_BM_WIDTH;
            if (cx2 > bounds->x2) cx2 = bounds->x2;

            FillBitmap(theImage,
                       glyphs,
                       totalGlyphs,
                       cx1, cy1, cx2, cy2);

            // NOTE: Since we are tiling around by BM_W, BM_H offsets
            // and thePixmap is BM_W x BM_H, we do not have to move
            // the TSOrigin at each step since the stipple repeats
            // every BM_W, BM_H units
            XPutImage(awt_display, thePixmap, theGC, theImage,
                      0, 0, 0, 0, cx2 - cx1, cy2 - cy1);
            /* MGA on Linux doesn't pick up the new stipple image data,
             * probably because it caches the image as a hardware pixmap
             * and doesn't update it when the pixmap image data is changed.
             * So if the loop is executed more than once, update the GC
             * which triggers the required behaviour. This extra XChangeGC
             * call only happens on large or rotated text so isn't a
             * significant new overhead..
             * This code needs to execute on a Solaris client too, in case
             * we are remote displaying to a MGA.
             */
            if (cy1 != bounds->y1 || cx1 != bounds->x1) {
                XChangeGC(awt_display, xgc, GCStipple, &xgcv);
            }

            XFillRectangle(awt_display, xsdo->drawable, xgc,
                           cx1, cy1, cx2 - cx1, cy2 - cy1);

            cx1 = cx2;
        }

        cy1 = cy2;
    }
    XSetFillStyle(awt_display, xgc, FillSolid);

    X11SD_DirectRenderNotify(env, xsdo);
#endif /* !HEADLESS */
}
