/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "stoolkit/SBinHashtable.h"

#include "swindow/SRasterizer.h"
#include "math.h"

#define SS_DEBUG_RASTERIZER 0

static bool cacheOn=false;

typedef SBinHashtable<SImage*> SRasterizerImageCache;
static SRasterizerImageCache imageCache;

static void clearImageCache();
static unsigned int cacheSize = 100;
static unsigned int cacheCount = 0;

void
SRasterizer::setCacheOn (bool on) {
    cacheOn=on;
    clearImageCache();
}

void
SRasterizer::setCacheSize(unsigned int size)
{
  cacheSize = size;
}


static void clearImageCache() {
    for (unsigned int i=0; i<imageCache.size(); i++)
    {
        for (unsigned int j=0; j<imageCache.size(i); j++)
        {
            SImage* e = imageCache.get (i, j);
            if (e != 0) delete e;
        }
    }
    imageCache.clear ();
    cacheCount = 0;
}

void
SRenderPathList::clear() {
    SRenderPathElement* el = head; 
    while (el) {
        SRenderPathElement* next = el->next;
        delete el;
        el = next;
    }
    tail = 0;
    head = 0;
    begin = 0;
    current = 0;
    elementCount = 0;
}

// we have room for 24-6 bit (262144)
#define MAX_COORD 100000

void
SRenderPathList::append (double x, double y, bool isbegin) {
    if (x < (double) -MAX_COORD) x = -MAX_COORD;
    if (y < (double) -MAX_COORD) y = -MAX_COORD;
    if (x > (double) MAX_COORD) x = MAX_COORD;
    if (y > (double) MAX_COORD) y = MAX_COORD;
    SRenderPathElement* el = new SRenderPathElement (0, x, y);
    CHECK_NEW (el);
    if (current) current->next = el;
    el->prev = current;

    current = el;
    if (isbegin || head == 0) {
        begin = el;
    } 
    if (head == 0) {
        head = el;
    }
    tail = el;

    elementCount++;
    el->begin = begin;
    el->begin->end = el;
    if(elementCount == 1) {
        bbox.minX = x;
        bbox.minY = y;
        bbox.maxX = x;
        bbox.maxY = y;
    } else {
        if (x < bbox.minX) bbox.minX = x;
        if (y < bbox.minY) bbox.minY = y;
        if (x > bbox.maxX) bbox.maxX = x;
        if (y > bbox.maxY) bbox.maxY = y;
    }
}

bool
SRasterizer::beginImage (int x, int y, const SString& id) {
    renderPath.clear();
    clearImages();
    newImageID = id;
    newImageX = x;
    newImageY = y;
    /* with a bit of a luck we have it in the cache */
    if (id.size() && imageCache.get (id) != 0) {
        return true;
    }
    return false;
}

void
SRasterizer::newpath () {
    renderPath.clear();
}

SImage*
SRasterizer::endImage () {
    renderPath.clear();
    SImage* ret = (SImage*) imageCache.get (newImageID);
    if (ret) {
        clearImages();
        SImage * im = new SImage(*ret);
        im->setOrigoX (im->getOrigoX() + (int)newImageX - im->px);
        im->setOrigoY (im->getOrigoY() + (int)newImageY - im->py);
        return im;
    }
    if (images.size() == 0) return 0;
    SImage currentLayer (*images[0]);
    delete images[0];
    for (unsigned int i=1; i<images.size(); i++) {
        currentLayer = currentLayer.addLayer ((*images[i]));
        delete images[i];
    }
    images.clear();

    ret = new SImage (currentLayer);
    if (ret->offScreen) {
#if SS_DEBUG_RASTERIZER
        fprintf (stderr, "refuse to put off screen image into the cache\n");
#endif
    }
    ret->compress ();
    // im->getOrigoX() is the minimum. We want to move it to
    // px 
    if (cacheOn && !ret->offScreen && newImageID.size() > 0) {
        cacheCount++;
        if (cacheCount > cacheSize) {
#if SS_DEBUG_RASTERIZER
            fprintf (stderr, "SRasterizer clearing cache (%u elements)\n",cacheCount);
#endif
            clearImageCache();
            cacheCount = 1;
        }
        imageCache.put (newImageID, new SImage (*ret));
    }
    return ret;
}

void
SRasterizer::moveto (double x, double y) {
    SS_Matrix2D  m = getCurrentMatrix();
    renderPath.append (m.toX(x, y), m.toY(x, y), true);
}

void
SRasterizer::lineto (double x, double y) {
    if (!renderPath.hasBegan()) {
        moveto (0, 0);
    }
    SS_Matrix2D  m = getCurrentMatrix();
    renderPath.append (m.toX(x, y), m.toY(x, y));
}

void
SRasterizer::curveto (double x1, double y1, double x2, double y2, double x3, double y3) {
    if (!renderPath.hasBegan()) {
        moveto (0,0);
    }
    SS_Matrix2D  m =  getCurrentMatrix();
    const SRenderPathElement* el = getCurrent(); 
    splitBezier (
        &renderPath,
        el->x, el->y,
        m.toX(x1, y1), m.toY(x1, y1),
        m.toX(x2, y2), m.toY(x2, y2),
        m.toX(x3, y3), m.toY(x3, y3), 5); // even 4 is good
}

/*  This method expects converted coordinates */
void
SRasterizer::splitBezier (SRenderPathList* out, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, int iteration) {

    double delta2 = (x3 - x0) * (x3 - x0) + (y3 - y0) * (y3 - y0);
    
    if (delta2 < (1.0/32.0)  || iteration <= 0) {
        // lineto without matrix.
        out->append (x3, y3);
        return;
    }
    /* divide it up into two sub-sections */
    double qx0 = x0;
    double qy0 = y0;

    double qx1 = (x0 + x1)/2.0;
    double qy1 = (y0 + y1)/2.0;

    double qx2 = qx1/2.0 + (x1 + x2) / 4.0;
    double qy2 = qy1/2.0 + (y1 + y2) / 4.0;

    double rx3 = x3;
    double ry3 = y3;

    double rx2 = (x2 + x3) / 2.0;
    double ry2 = (y2 + y3) / 2.0;

    double rx1 = (x1 + x2) / 4.0 + rx2 / 2.0;
    double ry1 = (y1 + y2) / 4.0 + ry2 / 2.0;

    double qx3 = (qx2 + rx1) / 2.0;
    double qy3 = (qy2 + ry1) / 2.0;

    double rx0 = qx3;
    double ry0 = qy3;

    splitBezier (out, qx0, qy0, qx1, qy1, qx2, qy2, qx3, qy3, iteration-1);
    splitBezier (out, rx0, ry0, rx1, ry1, rx2, ry2, rx3, ry3, iteration-1);
}

void
SRasterizer::closepath() {
    renderPath.closepath ();
}

void
SRasterizer::stroke (int x, int y, unsigned int maxWidth, unsigned int maxHeight, const SPen& pen) {
    if (maxWidth == 0 || maxHeight == 0) return;
    if (pen.getLineWidth() < 0.01) return;
    if (pen.getForeground().alpha == 0) {
        return;
    }
    SImage* si = strokeInternal (pen.getLineWidth(), maxWidth, maxHeight);
    if (si) {
        SImage colsi = si->colorize(pen.getForeground());
        delete si;
        si = new SImage(colsi);
        images.append (si);
    }
}

void
SRasterizer::fill (int x, int y, unsigned int maxWidth, unsigned int maxHeight, const SPen& pen) {
    if (maxWidth == 0 || maxHeight == 0) return;
    SImage* si = fillInternal (windingRule, renderPath, maxWidth, maxHeight);
    if (si) {
        SImage colsi = si->colorize(pen.getForeground());
        delete si;
        si = new SImage(colsi);
        images.append (si);
    }
}

SImage*
SRasterizer::fillInternal (const SWindingRule rule, const SRenderPathList& path, unsigned int maxWidth,unsigned int maxHeight) {
    if (path.size() == 0) return 0; 
    int width = maxWidth;
    int height = maxHeight;

    int padding =  1;
    bool offScreen = false;
    int newMinX = (int) path.bbox.minX - padding;
    int newMinY = (int) path.bbox.minY - padding;
    int newMaxX = (int) path.bbox.maxX + padding;
    int newMaxY = (int) path.bbox.maxY + padding;

    if (newMinX + (int) width < newMaxX) {
       offScreen = true;
#if SS_DEBUG_RASTERIZER
 fprintf (stderr, "Offscreen newMinX=%d newImageX=%d width=%d newMaxX=%d\n", newMinX, (int)newImageX, width, newMaxX);
#endif
    } else {
        width = newMaxX - newMinX;
    }
    if (newMinY + (int) height < newMaxY) {
        offScreen = true;
#if SS_DEBUG_RASTERIZER
fprintf (stderr, "Offscreen newMinY=%d newImageY=%d height=%d newMaxY=%d\n", newMinY, (int)newImageY, height, newMaxY);
#endif
    } else {
        height = newMaxY - newMinY;
    }
    int x = newMinX;
    int y = newMinY;
    unsigned int imageSize = width*height;
    if (imageSize == 0) return 0;

    SS_WORD32 *image = new SS_WORD32[imageSize];
    CHECK_NEW (image);
    memset (image, 0, imageSize * sizeof (SS_WORD32));

    /* scan the path horizontally */
    unsigned int overSampledHeight = height * overSample;
    unsigned int overSampledWidth = width * overSample;
    unsigned int i;
    // Sorted integer x coordinates of intersecions.
    // integer conntains 2 * x +  1 or 0 depending on winding
    SBinVector<int>** crossesX = new SBinVector<int>* [overSampledHeight];
    CHECK_NEW (crossesX);
    for (i=0; i<overSampledHeight; i++) {
        crossesX[i] = new  SBinVector<int>();
        CHECK_NEW (crossesX[i]);
    }

    const SRenderPathElement* el = path.getHead(); 
    // horizontal scanning
    while (el) {
        if (!el->isClosedPath()) {
            el = el->next;
            continue;
        }
        const SRenderPathElement* next = el->next;
        if (next == 0 || el->begin != next->begin) {  // closepath
            next = el->begin;
        }
        double x0 = (el->x - (double) x) * overSample;
        double y0 = (el->y - (double) y) * overSample;
        double x1 = (next->x - (double) x) * overSample;
        double y1 = (next->y - (double) y) * overSample;
        el = el->next;

        int run = (int) y0;
        int finish = (int) y1;
        // Move y off the grid 1/64 - 6 bit, allowing 64 oversample.
        if (y0 == (double) run) y0 = y0 + (1.0/64.0);
        if (y1 == (double) finish) y1 = y1 + (1.0/64.0);
        int increment = (y0 < y1) ? 1 : -1;
        finish = finish + increment;

        while (run != finish) {
            int crossDir = 0;
            // Equal is not crossed y0 and y1 is never on the grid.
            if (y0 < y1) {
                if (y0 < (double) run && y1 > (double) run) {
                    crossDir = 1;
                }
            } else if (y0 > y1) {
                if (y1 < (double) run && y0 > (double) run) {
                    crossDir = -1;
                }
            }
            if (crossDir != 0) {
                double xPointF = (fabs(y1 - y0) < 0.0001) 
                    ? ((x1 + x0) / 2)
                    : x0 + (x1 - x0) * (((double) run) - y0) / (y1 - y0);
                int xPoint = (int) xPointF;
                if (crossDir < 0) crossDir = 0;
                if (crossDir > 0) crossDir = 1;
                if (xPoint < 0) xPoint = 0;
                if (xPoint >= (int) overSampledWidth) {
                    xPoint = (int) overSampledWidth-1;
                }
                xPoint = 2 * xPoint + crossDir;
                unsigned int index = (run < 0) ? 0 : (unsigned int) run;
                if (index >= overSampledHeight) index = overSampledHeight-1;
                crossesX[index]->appendSorted (xPoint);
            }
            run = run + increment;
        }
    }
    // horizontal pixel value
    for (i=0; i<overSampledHeight; i++) {
        SBinVector<int>* cross = crossesX[i];
        int lastX = 0;
        int count = 0;
        int crossDir = 0;
       
        for (unsigned int j=0; j < cross->size(); j++) {
            int xPoint = cross->peek (j);
            int xCrossDir = xPoint  & 1;
            xPoint = xPoint / 2;
            // Detect change in cell.
            if (lastX < xPoint) {
                if (rule == NONZERO && crossDir != 0) {
                    for (int k=lastX; k<xPoint; k++) {
                        unsigned int imageIndex = (unsigned int)k/overSample 
                            + width * (i/overSample);
                        image[imageIndex] = image[imageIndex]+1;
                    }
                }
                if (rule == EVENODD && (count & 1)) {
                    for (int k=lastX; k<xPoint; k++) {
                        unsigned int imageIndex = (unsigned int)k/overSample 
                            + width * (i/overSample);
                        image[imageIndex] = image[imageIndex]+1;
                    }
                }
            }
            lastX = xPoint; 
            count++;
            if (xCrossDir > 0) {
                crossDir++;
            } else {
                crossDir--; 
            }
        }
#if SS_DEBUG_RASTERIZER
        if ((count & 1) != 0) {
            fprintf (stderr, "SRasterizer Internal error, count.\n");
        }
        if (crossDir != 0) {
            fprintf (stderr, "SRasterizer Internal error, cross.\n");
        }
#endif
    }
    for (i=0; i<overSampledHeight; i++) {
        delete crossesX[i];
        crossesX[i] = 0;
    }
    delete [] crossesX;
    crossesX = 0;

    // Sorted integer x coordinates of intersecions.
    // integer conntains 2 * x +  1 or 0 depending on winding
    SBinVector<int>** crossesY = new SBinVector<int>* [overSampledWidth];
    CHECK_NEW (crossesY);
    for (i=0; i<overSampledWidth; i++) {
        crossesY[i] = new  SBinVector<int>();
        CHECK_NEW (crossesY[i]);
    }
    // Vertical scanning 
    el = path.getHead(); 
    while (el) {
        if (!el->isClosedPath()) {
            el = el->next;
            continue;
        }
        const SRenderPathElement* next = el->next;
        if (next == 0 || el->begin != next->begin) {  // closepath
            next = el->begin;
        }
        double x0 = (el->x - (double) x) * overSample;
        double y0 = (el->y - (double) y) * overSample;
        double x1 = (next->x - (double) x) * overSample;
        double y1 = (next->y - (double) y) * overSample;
        el = el->next;

        int run = (int) x0;
        int finish = (int) x1;
        // Move y off the grid 1/64 - 6 bit, allowing 64 oversample.
        if (x0 == (double) run) x0 = x0 + (1.0/64.0);
        if (x1 == (double) finish) x1 = x1 + (1.0/64.0);
        int increment = (x0 < x1) ? 1 : -1;
        finish = finish + increment;

        while (run != finish) {
            int crossDir = 0;
            // Equal is not crossed y0 and y1 is never on the grid.
            if (x0 < x1) {
                if (x0 < (double) run && x1 > (double) run) {
                    crossDir = 1;
                }
            } else if (x0 > x1) {
                if (x1 < (double) run && x0 > (double) run) {
                    crossDir = -1;
                }
            }
            if (crossDir != 0) {
                double yPointF = (fabs(x1 - x0) < 0.0001) 
                    ? ((y1 +y0) / 2)
                    : y0 + (y1 - y0) * (((double) run) - x0) / (x1 - x0);
                int yPoint = (int) yPointF;
                if (crossDir < 0) crossDir = 0;
                if (crossDir > 0) crossDir = 1;
                if (yPoint < 0) yPoint = 0;
                if (yPoint >= (int) overSampledHeight) {
                    yPoint = (int) overSampledHeight-1;
                }
                yPoint = 2 * yPoint + crossDir;
                unsigned int index = (run < 0) ? 0 : (unsigned int) run;
                if (index >= overSampledWidth) index = overSampledWidth-1;
                crossesY[index]->appendSorted (yPoint);
            }
            run = run + increment;
        }
    }
    // vertical pixel value counting
    for (i=0; i<overSampledWidth; i++) {
        SBinVector<int>* cross = crossesY[i];
        int lastY = 0;
        int count = 0;
        int crossDir = 0;
       
        for (unsigned int j=0; j < cross->size(); j++) {
            int yPoint = cross->peek (j);
            int yCrossDir = yPoint  & 1;
            yPoint = yPoint / 2;
            // Detect change in cell.
            if (lastY < yPoint) {
                if (rule == NONZERO && crossDir != 0) {
                    for (int k=lastY; k<yPoint; k++) {
                        unsigned int imageIndex = (unsigned int)i/overSample 
                            + width * (k/overSample);
                        image[imageIndex] = image[imageIndex]+1;
                    }
                }
                if (rule == EVENODD && (count & 1)) {
                    for (int k=lastY; k<yPoint; k++) {
                        unsigned int imageIndex = (unsigned int)i/overSample 
                            + width * (k/overSample);
                        image[imageIndex] = image[imageIndex]+1;
                    }
                }
            }
            lastY = yPoint; 
            count++;
            if (yCrossDir > 0) {
                crossDir++;
            } else {
                crossDir--; 
            }
        }
#if SS_DEBUG_RASTERIZER
        if ((count & 1) != 0) {
            fprintf (stderr, "SRasterizer Internal error, count.\n");
        }
        if (crossDir != 0) {
            fprintf (stderr, "SRasterizer Internal error, cross.\n");
        }
#endif
    }
    for (i=0; i<overSampledWidth; i++) {
        delete crossesY[i];
        crossesY[i] = 0;
    }
    delete [] crossesY;
    crossesY = 0;

    unsigned int colors = 2 * overSample * overSample + 1;
    SImage* im = new SImage (image, colors, x, y, width, height);
    CHECK_NEW (im);
    im->offScreen = offScreen;
    im->px = (int)newImageX;
    im->py = (int)newImageY;
    return im;
}

SImage*
SRasterizer::strokeInternal (double lineWidth, unsigned int maxWidth,unsigned int maxHeight) {

    if (renderPath.size() == 0) return 0; 

    SRenderPathList strokeRenderPath;
    SRenderPathList innerpath;

    LineJoiner strokeJoiner (this, &strokeRenderPath, lineWidth, LineJoiner::BEVEL);

    const SRenderPathElement* el = renderPath.getHead(); 
    // Walk through the outside of the polygons.
    while (el) {
        if (!el->isClosedPath() || el->begin != el) {
            el = el->next;
            continue;
        }
        const SRenderPathElement* next = el->nextForward();
        // one element only
        if (next == el) {
            el = el->next;
            continue;
        }
        const SRenderPathElement* start = el;
        do {
            const SRenderPathElement* next2 = next->nextForward();
            double x0 = el->x;
            double y0 = el->y;
            double x1 = next->x;
            double y1 = next->y;
            double x2 = next2->x;
            double y2 = next2->y;
            strokeJoiner.joinLoop (x0, y0, x1, y1, x2, y2, (el == start));
            el = next;
            next = next2;
        } while (el != start);
        strokeRenderPath.closepath();
        el = el->next;
    }
    // walk through the inside of the polygons.
    el = renderPath.getTail(); 
    while (el) {
        if (!el->isClosedPath() || el->begin->end != el) {
            el = el->prev;
            continue;
        }
        const SRenderPathElement* next = el->nextBackward();
        // one element only
        if (next == el) {
            el = el->prev;
            continue;
        }
        const SRenderPathElement* start = el;
        do {
            const SRenderPathElement* next2 = next->nextBackward();
            double x0 = el->x;
            double y0 = el->y;
            double x1 = next->x;
            double y1 = next->y;
            double x2 = next2->x;
            double y2 = next2->y;
            strokeJoiner.joinLoop (x0, y0, x1, y1, x2, y2, (el == start));
            el = next;
            next = next2;
        } while (el != start);
        strokeRenderPath.closepath();
        el = el->prev;
    }
    el = renderPath.getHead(); 
    // Draw lines that are not closed paths.
    while (el) {
        if (el->isClosedPath() || el->begin != el) {
            el = el->next;
            continue;
        }
        bool forward = true;
        const SRenderPathElement* next = el->nextShuttle(&forward);
        // 1 element only.
        if (next == el) {
            el = el->next;
            continue;
        }
        const SRenderPathElement* start = el;
        do {
            const SRenderPathElement* next2 = next->nextShuttle(&forward);
            double x0 = el->x;
            double y0 = el->y;
            double x1 = next->x;
            double y1 = next->y;
            double x2 = next2->x;
            double y2 = next2->y;
            strokeJoiner.joinLoop (x0, y0, x1, y1, x2, y2, (el == start));
            el = next;
            next = next2;
        } while (el != start);
        strokeRenderPath.closepath();
        el = start->begin->end;
        el = el->next;
    }
    return fillInternal (NONZERO, strokeRenderPath, maxWidth, maxHeight);
    
}

void
LineJoiner::joinLoop (double x0, double y0, double x1, double y1, double x2, double y2, bool isBegin) {
    double minDelta = 1e-10;
    // perpendicular vectors of length linewidth
    double v0x = y0-y1;
    double v0y = x1-x0;
    double v0l = sqrt (v0x*v0x + v0y* v0y);
    if (v0l < minDelta) v0l = minDelta;
    v0x = v0x * lineWidth / v0l / 2.0;
    v0y = v0y * lineWidth / v0l / 2.0;
    
    double v1x = y1-y2;
    double v1y = x2-x1;
    double v1l = sqrt (v1x*v1x + v1y* v1y);

    if (v1l < minDelta) v1l = minDelta;
    v1x = v1x * lineWidth / v1l / 2.0;
    v1y = v1y * lineWidth / v1l / 2.0;

    out->append (x1+v0x, y1+v0y, isBegin);
    // Do a nice joinStyle action here. Something that I will do someday :)
#if 0
    // use BezierSplitter*  splitter to draw circle.
    if (joinStyle == ROUND) {
        // 
    }
#endif
    out->append (x1+v1x, y1+v1y);
}

