/*
 *     XaoS, a fast portable realtime fractal zoomer
 *                  Copyright (C) 1996,1997 by
 *
 *      Jan Hubicka          (hubicka@paru.cas.cz)
 *      Thomas Marsh         (tmarsh@austin.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 <cstdlib>
#include <cstdio>
#include <climits>
#include <cmath>
#include <cstring>
#include <cassert>

#include "config.h"
#define SLARGEITER
#include "filter.h"
#include "zoom.h"
#include "cmplx.h" /*for myabs */
#include "plane.h"
#include "btrace.h"
#include "xthread.h"
#include "xerror.h"
#include "calculate.h" /*an inlined calculate function */
#include "i18n.h"

#define ASIZE 16
#define ALIGN(x) (((x) + ASIZE - 1) & (~(ASIZE - 1)))
static int nsymmetrized;
unsigned char *tmpdata, *tmpdata1;
struct realloc_s {
    number_t position;
    number_t price;
    unsigned int plus;
    int recalculate;
    int symto;
    int symref;
    int dirty;
}
#ifdef __GNUC__
__attribute__((aligned(32)))
#endif
;
typedef struct realloc_s realloc_t;

typedef struct zoom_context {
    number_t *xpos, *ypos;
    int newcalc;
    int forversion;
    int forpversion;
    realloc_t *reallocx, *reallocy;
    int incomplete;
    int changed;
} zoom_context;

struct filltable {
    int from;
    int to;
    int length;
    int end;
};
#define getzcontext(f) ((zoom_context *)((f)->data))
#define getfcontext(f) ((f)->fractalc)

#define callwait()                                                             \
    if (cfilter.wait_function != NULL)                                         \
        cfilter.wait_function(&cfilter);
#define tcallwait()                                                            \
    if (!xth_nthread(task) && cfilter.wait_function != NULL)                   \
        cfilter.wait_function(&cfilter);
#define setincomplete(i) (getzcontext(&cfilter)->incomplete = i)
#define incincomplete() (getzcontext(&cfilter)->incomplete++)
#define setchanged(i) (getzcontext(&cfilter)->changed = i)

zoom_context czoomc;
struct filter cfilter;
#ifdef STATISTICS
static int tocalculate = 0, avoided = 0;
static int nadded = 0, nsymmetry = 0, nskipped = 0;
int nperi = 0;
#endif

static void moveoldpoints(void *data1, struct taskinfo *task, int r1, int r2);
static void fillline_8(int line);
static void fillline_16(int line);
// static void fillline_24(int line);
static void fillline_32(int line);

/*first of all inline driver section */
/*If you think this way is ugly, I must agree. Please let me know
 *about better one that allows to generate custom code for 8,16,24,32
 *bpp modes and use of static variables
 */
#include "c256.h"
#define fillline fillline_8
#define dosymmetry2 dosymmetry2_8
#define calcline calcline_8
#define calccolumn calccolumn_8
#include "zoomd.h"

#include "truecolor.h"
#define fillline fillline_32
#define dosymmetry2 dosymmetry2_32
#define calcline calcline_32
#define calccolumn calccolumn_32
#include "zoomd.h"

#include "true24.h"
#define fillline fillline_24
#define dosymmetry2 dosymmetry2_24
#define calcline calcline_24
#define calccolumn calccolumn_24
#include "zoomd.h"

#include "hicolor.h"
#define fillline fillline_16
#define dosymmetry2 dosymmetry2_16
#define calcline calcline_16
#define calccolumn calccolumn_16
#include "zoomd.h"

#define calcline(a)                                                            \
    drivercall(cimage, calcline_8(a), calcline_16(a), calcline_24(a),          \
               calcline_32(a));
#define calccolumn(a)                                                          \
    drivercall(cimage, calccolumn_8(a), calccolumn_16(a), calccolumn_24(a),    \
               calccolumn_32(a));

struct dyn_data {
    long price;
    struct dyn_data *previous;
};

#define FPMUL 64 /*Let multable fit into pentium cache */
#define RANGES 2 /*shift equal to x*RANGE */
#define RANGE 4

#define DSIZEHMASK (0x7) /*mask equal to x%(DSIZE) */
#define DSIZE (2 * RANGE)
#define DSIZES (RANGES + 1) /*shift equal to x*DSIZE */

#define adddata(n, i) (dyndata + (((n) << DSIZES) + (((i) & (DSIZEHMASK)))))
#define getbest(i) (dyndata + ((size) << DSIZES) + (i))
#define nosetadd ((size * 2) << DSIZES)
#ifndef DEBUG
#define CHECKPOS(pos)
#else
#define CHECKPOS(pos)                                                          \
    (assert((pos) >= dyndata),                                                 \
     assert((pos) < dyndata + (size) + ((size) << DSIZES)))
#endif

#ifdef __POWERPC__
#undef USE_MULTABLE
#else
#define USE_MULTABLE 1
#endif

#ifdef USE_MULTABLE
#define PRICE(i, i1) mulmid[(i) - (i1)]
#else
#define PRICE(i, i1) (((i) - (i1)) * ((i) - (i1)))
#endif
#define NEWPRICE (FPMUL * FPMUL * (RANGE) * (RANGE))

#define NOSETMASK ((unsigned int)0x80000000)
#define END NULL
#define MAXPRICE LONG_MAX
/*static int dynsize = (int)sizeof (struct dyn_data);*/
#ifndef INT_MIN
#define INT_MIN (-INT_MAX - 1)
#endif
#define IRANGE FPMUL *RANGE

#ifdef USE_MULTABLE
static int multable[RANGE * FPMUL * 2];
static int *mulmid;
#endif

/*Functions looks through rows/columns marked for calculation and tries to use
 *some symmetrical one instead
 */

/*FIXME should be threaded...but thread overhead should take more work than
 *do it in one, since it is quite simple and executes just in case fractal
 *on the screen is symmetrical and it is quite rare case...who knows
 */

static void preparesymmetries(realloc_t *realloc, const int size, int symi,
                              number_t sym, number_t step)
{
    int i;
    int istart = 0;
    number_t fy, ftmp;
    realloc_t *r = realloc, *reallocs;

    sym *= 2;
    i = 2 * symi - size;
    if (i < 0)
        i = 0;
    realloc += i;

    for (; i <= symi; i++, realloc++) { /*makes symmetries */
        int j, min = 0;
        number_t dist = NUMBER_BIG, tmp1;

        if (realloc->symto != -1)
            continue;

        fy = realloc->position;
        realloc->symto = 2 * symi - i;

        if (realloc->symto >= size - RANGE)
            realloc->symto = size - RANGE - 1;

        dist = RANGE * step;
        min = RANGE;
#ifdef DEBUG
        if (realloc->symto < 0 || realloc->symto >= size) {
            x_fatalerror("Internal error #22-1 %i", realloc->symto);
            assert(0);
        }
#endif
        reallocs = &r[realloc->symto];
        j = (realloc->symto - istart > RANGE) ? -RANGE
                                              : (-realloc->symto + istart);

        if (realloc->recalculate) {
            for (; j < RANGE && realloc->symto + j < size - 1; j++) {
                ftmp = sym - (reallocs + j)->position;
                if ((tmp1 = myabs(ftmp - fy)) < dist) {
                    if ((realloc == r || ftmp > (realloc - 1)->position) &&
                        (ftmp < (realloc + 1)->position)) {
                        dist = tmp1;
                        min = j;
                    }
                } else if (ftmp < fy)
                    break;
            }

        } else {
            for (; j < RANGE && realloc->symto + j < size - 1; j++) {
                if (!realloc->recalculate)
                    continue;
                ftmp = sym - (reallocs + j)->position;
                if ((tmp1 = myabs(ftmp - fy)) < dist) {
                    if ((realloc == r || ftmp > (realloc - 1)->position) &&
                        (ftmp < (realloc + 1)->position)) {
                        dist = tmp1;
                        min = j;
                    }
                } else if (ftmp < fy)
                    break;
            }
        }
        realloc->symto += min;

        if (min == RANGE || realloc->symto <= symi ||
            (reallocs = reallocs + min)->symto != -1 ||
            reallocs->symref != -1) {
            realloc->symto = -1;
            continue;
        }

        if (!realloc->recalculate) {
            realloc->symto = -1;
            if (reallocs->symto != -1 || !reallocs->recalculate)
                continue;
            reallocs->plus = realloc->plus;
            reallocs->symto = i;
            nsymmetrized++;
            istart = realloc->symto - 1;
            reallocs->dirty = 1;
            realloc->symref = (int)(reallocs - r);
            STAT(nadded -= reallocs->recalculate);
            reallocs->recalculate = 0;
            reallocs->position = sym - realloc->position;
        } else {
            if (reallocs->symto != -1) {
                realloc->symto = -1;
                continue;
            }
            istart = realloc->symto - 1;
            STAT(nadded -= realloc->recalculate);
            nsymmetrized++;
            realloc->dirty = 1;
            realloc->plus = reallocs->plus;
            realloc->recalculate = 0;
            reallocs->symref = i;
            realloc->position = sym - reallocs->position;
        }
        STAT(nsymmetry++);

#ifdef DEBUG
        if (realloc->symto < -1 || realloc->symto >= size) {
            x_fatalerror("Internal error #22 %i", realloc->symto);
            assert(0);
        }
        if (reallocs->symto < -1 || reallocs->symto >= size) {
            x_fatalerror("Internal error #22-2 %i", reallocs->symto);
            assert(0);
        }
#endif
    }
}

static void newpositions(realloc_t *realloc, unsigned int size, number_t begin1,
                         number_t end1, const number_t *fpos, int yend)
{
    realloc_t *rs, *re, *rend;
    number_t step = size / (end1 - begin1);
    number_t start;
    number_t end;
    rend = realloc + size;
    rs = realloc - 1;
    re = realloc;
    while (rs < rend - 1) {
        re = rs + 1;
        if (re->recalculate) {
            while (re < rend && re->recalculate)
                re++;

            if (re == rend)
                end = end1;
            else
                end = re->position;

            if (rs == realloc - 1) {
                start = begin1;
                if (start > end)
                    start = end;
            } else
                start = rs->position;

            if (re == rend && start > end)
                end = start;

            if (re - rs == 2)
                end = (end - start) * 0.5;
            else
                end = ((number_t)(end - start)) / (re - rs);

            switch (yend) {
                case 1:
                    for (rs++; rs < re; rs++) {
                        start += end, rs->position = start;
                        rs->price =
                            1 / (1 + myabs(fpos[rs - realloc] - start) * step);
                    }
                    break;
                case 2:
                    for (rs++; rs < re; rs++) {
                        start += end, rs->position = start;
                        rs->price = (myabs(fpos[rs - realloc] - start) * step);
                        if (rs == realloc || rs == rend - 1)
                            rs->price *= 500;
                    }
                    break;
                default:
                    for (rs++; rs < re; rs++) {
                        start += end, rs->position = start;
                        rs->price = (number_t)1;
                    }
                    break;
            }
        }
        rs = re;
    }
}

/* This is the main reallocation algorithm described in xaos.info
 * It is quite complex since many loops are unrooled and uses custom
 * fixedpoint
 *
 * Takes approx 30% of time so looking for way to do it threaded.
 * Let me know :)
 */
static void mkrealloc_table(const number_t *fpos, realloc_t *realloc,
                            const unsigned int size, const number_t begin,
                            const number_t end, number_t sym,
                            unsigned char *tmpdata)
{
    unsigned int i;
    int counter;
    unsigned int ps, ps1 = 0, pe;
    unsigned int p;
    long int bestprice = MAXPRICE;
    realloc_t *r = realloc;
    struct dyn_data *dyndata;
    int yend, y;
    struct dyn_data **best;
    struct dyn_data **best1, **tmp;
    int *pos;
    number_t step, tofix;
    int symi = -1;
    unsigned int lastplus = 0;
    struct dyn_data *data;
    struct dyn_data *previous = NULL, *bestdata = NULL;
    int myprice;
#ifdef STATISTICS
    nadded = 0, nsymmetry = 0, nskipped = 0;
#endif

    pos = (int *)tmpdata;
    best = (struct dyn_data **)(tmpdata + ALIGN((size + 2) * sizeof(int)));
    best1 = (struct dyn_data **)(tmpdata + ALIGN((size + 2) * sizeof(int)) +
                                 ALIGN(size * sizeof(struct dyn_data **)));
    dyndata = (struct dyn_data *)(tmpdata + ALIGN((size + 2) * sizeof(int)) +
                                  2 * ALIGN(size * sizeof(struct dyn_data **)));

    tofix = size * FPMUL / (end - begin);
    pos[0] = INT_MIN;
    pos++;
    for (counter = (int)size - 1; counter >= 0; counter--) {
        pos[counter] =
            (int)((fpos[counter] - begin) *
                  tofix); /*first convert everything into fixedpoint */
        if (counter < (int)size - 1 && pos[counter] > pos[counter + 1])
            /*Avoid processing of missordered rows.
               They should happen because of limited
               precisity of FP numbers */
            pos[counter] = pos[counter + 1];
    }
    pos[size] = INT_MAX;
    step = (end - begin) / (number_t)size;
    if (begin > sym || sym > end) /*calculate symmetry point */
        symi = -2;
    else {
        symi = (int)((sym - begin) / step);
    }

    ps = 0;
    pe = 0;
    y = 0;

    /* This is first pass that fills table dyndata, that holds information
     * about all ways algorithm thinks about. Correct way is discovered at
     * end by looking backward and determining witch way algorithm used to
     * calculate minimal value*/

    for (i = 0; i < size; i++, y += FPMUL) {
        bestprice = MAXPRICE;
        p = ps; /*just inicialize parameters */

        tmp = best1;
        best1 = best;
        best = tmp;

        yend = y - IRANGE;
        if (yend < -FPMUL) /*do no allow lines outside screen */
            yend = -FPMUL;

        while (pos[p] <= yend) /*skip lines out of range */
            p++;
        ps1 = p;
        yend = y + IRANGE;

        /*First try case that current line will be newly calculated */

        /*Look for best way how to connect previous lines */
        if (ps != pe && p > ps) { /*previous point had lines */
            assert(p >= ps);
            if (p < pe) {
                previous = best[p - 1];
                CHECKPOS(previous);
            } else
                previous = best[pe - 1];
            CHECKPOS(previous);
            myprice = previous->price; /*find best one */
        } else {
            if (i > 0) { /*previous line had no lines */
                previous = getbest(i - 1);
                myprice = previous->price;
            } else
                previous = END, myprice = 0;
        }

        data = getbest(i); /*find store position */
        myprice += NEWPRICE;
        bestdata = data;
        data->previous = previous;
        bestprice = myprice;    /*calculate best available price */
        data->price = myprice;  /*store data */
        assert(bestprice >= 0); /*FIXME:tenhle assert muze FAILIT! */
        data = adddata(p, i);   /*calculate all lines good for this y */

        /* Now try all acceptable connection and calculate best possibility
         * with this connection
         */
        if (ps != pe) { /*in case that previous had also positions */
            int price1 = INT_MAX;
            /*At first line of previous interval we have only one possibility
             *don't connect previous line at all.
             */
            if (p == ps) { /*here we must skip previous point */
                if (pos[p] != pos[p + 1]) {
                    previous = getbest(i - 1);
                    myprice = previous->price;
                    myprice += PRICE(pos[p], y); /*store data */
                    if (myprice < bestprice) {   /*calculate best */
                        bestprice = myprice, bestdata = data;
                        data->price = myprice;
                        data->previous = previous;
                    }
                }
                assert(bestprice >= 0);
                assert(myprice >= 0);
                best1[p] = bestdata;
                data += DSIZE;
                p++;
            }

            previous = NULL;
            price1 = myprice;
            while (p < pe) { /*this is area where intervals of current point and
                                previous one are crossed */
                if (pos[p] != pos[p + 1]) {
                    if (previous != best[p - 1]) {

                        previous = best[p - 1];
                        CHECKPOS(previous);
                        price1 = myprice = previous->price;

                        /*In case we found revolutional point, we should think
                         *about changing our gusesses in last point too - don't
                         *connect it at all, but use this way instead*/
                        if (myprice + NEWPRICE <
                            bestprice) { /*true in approx 2/3 of cases */
                            bestprice = myprice + NEWPRICE,
                            bestdata = data - DSIZE;
                            (bestdata)->price = bestprice;
                            (bestdata)->previous = previous + nosetadd;
                            best1[p - 1] = bestdata;
                        }
                    } else
                        myprice = price1;

                    myprice +=
                        PRICE(pos[p], y); /*calculate price of new connection */

                    if (myprice <
                        bestprice) { /*2/3 of cases */ /*if it is better than
                                                          previous, store it */
                        bestprice = myprice, bestdata = data;
                        data->price = myprice;
                        data->previous = previous;
                    } else if (pos[p] > y) {
                        best1[p] = bestdata;
                        data += DSIZE;
                        p++;
                        break;
                    }
                }

                assert(myprice >= 0);
                assert(bestprice >= 0); /*FIXME:tenhle assert FAILI! */

                best1[p] = bestdata;
                data += DSIZE;
                p++;
            }
            while (p < pe) { /*this is area where intervals of current point and
                                previous one are crossed */
#ifdef DEBUG
                if (pos[p] != pos[p + 1]) {
                    if (previous != best[p - 1]) {
                        x_fatalerror("Missoptimization found!");
                    }
                }
#endif
                assert(myprice >= 0);
                assert(bestprice >= 0); /*FIXME:tenhle assert FAILI! */

                best1[p] = bestdata;
                data += DSIZE;
                p++;
            }

            /* OK...we passed crossed area. All next areas have same previous
             * situation so our job is easier
             * So find the best solution once for all of them
             */
            if (p > ps) {
                previous = best[p - 1]; /*find best one in previous */
                CHECKPOS(previous);
                price1 = previous->price;
            } else {
                previous = getbest(i - 1);
                price1 = previous->price;
            }

            /* Since guesses for "revolutional point" was always one
             * step back, we need to do last one*/
            if (price1 + NEWPRICE < bestprice && p > ps1) {
                myprice = price1 + NEWPRICE;
                bestprice = myprice, bestdata = data - DSIZE;
                (bestdata)->price = myprice;
                (bestdata)->previous = previous + nosetadd;
                best1[p - 1] = bestdata;
                myprice -= NEWPRICE;
            }

            while (pos[p] < yend) {
                if (pos[p] != pos[p + 1]) {
                    myprice = price1;
                    myprice += PRICE(pos[p], y); /*store data */
                    if (myprice < bestprice) {   /*calculate best */
                        bestprice = myprice, bestdata = data;
                        data->price = myprice;
                        data->previous = previous;
                    } else if (pos[p] > y)
                        break;
                }

                assert(bestprice >= 0);
                assert(myprice >= 0);

                best1[p] = bestdata;
                data += DSIZE;
                p++;
            }
            while (pos[p] < yend) {
                best1[p] = bestdata;
                p++;
            }
        } else {
            /* This is second case - previous y was not mapped at all.
             * Situation is simpler now, since we know that behind us is
             * large hole and our decisions don't affect best solution for
             * previous problem. Se we have just one answer
             * Situation is similar to latest loop in previous case
             */
            int myprice1; /*simplified loop for case that previous
                             y had no lines */
            if (pos[p] < yend) {
                if (i > 0) {
                    previous = getbest(i - 1);
                    myprice1 = previous->price;
                } else
                    previous = END, myprice1 = 0;
                while (pos[p] < yend) {
                    if (pos[p] != pos[p + 1]) {
                        myprice = myprice1 + PRICE(pos[p], y);
                        if (myprice < bestprice) {
                            data->price = myprice;
                            data->previous = previous;
                            bestprice = myprice, bestdata = data;
                        } else if (pos[p] > y)
                            break;
                    }
                    assert(bestprice >= 0);
                    assert(myprice >= 0);
                    best1[p] = bestdata;
                    p++;
                    data += DSIZE;
                }
                while (pos[p] < yend) {
                    best1[p] = bestdata;
                    p++;
                }
            }
        }
        /*previous = ps; */ /*store positions for next loop */
        ps = ps1;
        ps1 = pe;
        pe = p;
    }

    assert(bestprice >= 0);

    realloc = realloc + size;
    yend = (int)((begin > fpos[0]) && (end < fpos[size - 1]));

    if (pos[0] > 0 && pos[size - 1] < (int)size * FPMUL)
        yend = 2;

    /*This part should be made threaded quite easily...but does it worth
     *since it is quite simple loop 0...xmax
     */
    for (i = size; i > 0;) { /*and finally traces the path */
        struct dyn_data *bestdata1;
        realloc--;
        i--;
        realloc->symto = -1;
        realloc->symref = -1;
        bestdata1 = bestdata->previous;

        if (bestdata1 >= dyndata + nosetadd ||
            bestdata >= dyndata + ((size) << DSIZES)) {
            if (bestdata1 >= dyndata + nosetadd)
                bestdata1 -= nosetadd;

            realloc->recalculate = 1;
            STAT(nadded++);
            realloc->dirty = 1;
            lastplus++;

            if (lastplus >= size)
                lastplus = 0;

            realloc->plus = lastplus;

        } else {
            p = ((unsigned int)(bestdata - dyndata)) >> DSIZES;
            assert(p < size);
            realloc->position = fpos[p];
            realloc->plus = p;
            realloc->dirty = 0;
            realloc->recalculate = 0;
            lastplus = p;
        }
        bestdata = bestdata1;
    }

    newpositions(realloc, size, begin, end, fpos, yend);
    realloc = r;
    if (symi <= (int)size && symi >= 0) {
        preparesymmetries(r, (int)size, symi, sym, step);
    }

    STAT(printf("%i added %i skipped %i mirrored\n", nadded, nskipped,
                nsymmetry));
    STAT(nadded2 += nadded; nskipped2 += nskipped; nsymmetry2 += nsymmetry);
}

struct movedata {
    unsigned int size;
    unsigned int start;
    unsigned int plus;
};
int avgsize;
/*
 * this function prepares fast moving table for moveoldpoints
 * see xaos.info for details. It is not threaded since it is quite
 * fast.
 */
static void preparemoveoldpoints(void)
{
    struct movedata *data, *sizend;
    realloc_t *rx, *rx1, *rend1;
    int sum = 0, num = 0;
    int plus1 = 0;

    data = (struct movedata *)tmpdata;
    for (rx = czoomc.reallocx, rend1 = rx + cimage.width; rx < rend1; rx++)
        if ((rx->dirty) && plus1 < cimage.width + 1)
            plus1++;
        else
            break;
    data->start = czoomc.reallocx->plus;
    data->size = 0;
    data->plus = plus1;
    rend1--;
    while (rend1->dirty) {
        if (rend1 == czoomc.reallocx)
            return;
        rend1--;
    }
    rend1++;
    for (; rx < rend1; rx++) {
        if ((rx->dirty || rx->plus == data->start + data->size))
            data->size++;
        else {
            if (data->size) {
                plus1 = 0;
                rx1 = rx - 1;
                while (rx1 > czoomc.reallocx && rx1->dirty)
                    plus1++, data->size--, rx1--;
                if (!(data->start + data->size < (unsigned int)cimage.width) &&
                    !rx->dirty) {
                    int i;
                    if (rx == rend1)
                        break;
                    for (i = 0; rx->dirty && rx < rend1; rx++)
                        i++;
                    data++;
                    data->plus = plus1;
                    data->size = (unsigned int)i;
                    data->start = rx->plus - i;
                } else {
                    sum += data->size;
                    num++;
                    data++;
                    data->plus = plus1;
                    data->start = rx->plus;
                }
            } else
                data->start = rx->plus;
            assert(rx->plus < (unsigned int)cimage.width);
            data->size = 1;
        }
    }
    if (data->size) {
        sizend = data + 1;
        sum += data->size;
        rx1 = rx - 1;
        while (rx1 > czoomc.reallocx && rx1->dirty)
            data->size--, rx1--;
        num++;
    } else
        sizend = data;
    sizend->size = 0;
    if (cimage.bytesperpixel != 1) {
        sum *= cimage.bytesperpixel;
        for (data = (struct movedata *)tmpdata; data < sizend; data++) {
            data->plus *= cimage.bytesperpixel;
            data->size *= cimage.bytesperpixel;
            data->start *= cimage.bytesperpixel;
        }
    }
    if (num)
        avgsize = sum / num;
}

static void moveoldpoints(void * /*data1*/, struct taskinfo * /*task*/, int r1,
                          int r2)
{
    struct movedata *data;
    unsigned char *vline, *vbuff;
    realloc_t *ry, *rend;
    int i = r1;

    for (ry = czoomc.reallocy + r1, rend = czoomc.reallocy + r2; ry < rend;
         ry++, i++) {
        if (!ry->dirty) {
            assert(ry->plus < (unsigned int)cimage.height);
            vbuff = cimage.currlines[i];
            vline = cimage.oldlines[ry->plus];
            for (data = (struct movedata *)tmpdata; data->size; data++) {
                vbuff += data->plus;
                memcpy(vbuff, vline + data->start, (size_t)data->size),
                    vbuff += data->size;
            }
        }
    }
}

/* This function prepares fast filling tables for fillline */
static int mkfilltable(void)
{
    int vsrc;
    int pos;
    realloc_t *rx, *r1, *r2, *rend, *rend2;
    int n = 0;
    int num = 0;
    struct filltable *tbl = (struct filltable *)tmpdata;

    pos = 0;
    vsrc = 0;

    rx = czoomc.reallocx;
    while (rx > czoomc.reallocx && rx->dirty)
        rx--;
    for (rend = czoomc.reallocx + cimage.width,
        rend2 = czoomc.reallocx + cimage.width;
         rx < rend; rx++) {
        if (rx->dirty) {
            r1 = rx - 1;
            for (r2 = rx + 1; r2 < rend2 && r2->dirty; r2++)
                ;
            while (rx < rend2 && rx->dirty) {
                n = (int)(r2 - rx);
                assert(n > 0);
                if (r2 < rend2 &&
                    (r1 < czoomc.reallocx ||
                     rx->position - r1->position > r2->position - rx->position))
                    vsrc = (int)(r2 - czoomc.reallocx), r1 = r2;
                else {
                    vsrc = (int)(r1 - czoomc.reallocx);
                    if (vsrc < 0)
                        goto end;
                }
                pos = (int)(rx - czoomc.reallocx);
                assert(pos >= 0 && pos < cimage.width);
                assert(vsrc >= 0 && vsrc < cimage.width);

                tbl[num].length = n;
                tbl[num].to = pos * cimage.bytesperpixel;
                tbl[num].from = vsrc * cimage.bytesperpixel;
                tbl[num].end =
                    tbl[num].length * cimage.bytesperpixel + tbl[num].to;
                /*printf("%i %i %i %i\n",num,tbl[num].length, tbl[num].to,
                 * tbl[num].from); */
                while (n) {
                    rx->position = czoomc.reallocx[vsrc].position;
                    rx->dirty = 0;
                    rx++;
                    n--;
                }
                num++;
            } /*while rx->dirty */
        }     /*if rx->dirty */
    }         /*for czoomc */
end:
    tbl[num].length = 0;
    tbl[num].to = pos;
    tbl[num].from = vsrc;
    return num;
}

static void filly(void * /*data*/, struct taskinfo * /*task*/, int rr1, int rr2)
{
    unsigned char **vbuff = cimage.currlines;
    realloc_t *ry, *r1, *r2, *rend, *rend2, *rs = NULL;
    int linesize = cimage.width * cimage.bytesperpixel;

    ry = czoomc.reallocy + rr1;

    ry = czoomc.reallocy + rr1;
    while (ry > czoomc.reallocy && ry->dirty > 0)
        ry--;
    for (rend = czoomc.reallocy + rr2, rend2 = czoomc.reallocy + cimage.height;
         ry < rend; ry++) {
        if (ry->dirty > 0) {
            incincomplete();
            r1 = ry - 1;
            for (r2 = ry + 1; r2 < rend2 && r2->dirty > 0; r2++)
                ;
            if (r2 >= rend2 && (rr2 != cimage.height || ry == 0))
                return;
            while (ry < rend2 && ry->dirty > 0) {
                if (r1 < czoomc.reallocy) {
                    rs = r2;
                    if (r2 >= rend2)
                        return;
                } else if (r2 >= rend2)
                    rs = r1;
                else if (ry->position - r1->position <
                         r2->position - ry->position)
                    rs = r1;
                else
                    rs = r2;
                if (!rs->dirty) {
                    drivercall(cimage, fillline_8(rs - czoomc.reallocy),
                               fillline_16(rs - czoomc.reallocy),
                               fillline_24(rs - czoomc.reallocy),
                               fillline_32(rs - czoomc.reallocy));
                    ry->dirty = -1;
                }
                memcpy(vbuff[ry - czoomc.reallocy], vbuff[rs - czoomc.reallocy],
                       (size_t)linesize);
                ry->position = rs->position;
                ry->dirty = -1;
                ry++;
            }
        }
        if (ry < rend && !ry->dirty) {
            drivercall(cimage, fillline_8(ry - czoomc.reallocy),
                       fillline_16(ry - czoomc.reallocy),
                       fillline_24(ry - czoomc.reallocy),
                       fillline_32(ry - czoomc.reallocy));
            ry->dirty = -1;
        }
    }
}

static void fill(void)
{
    if (cfilter.interrupt) {
        cfilter.pass = "reducing resolution";
        mkfilltable();
        xth_function(filly, NULL, cimage.height);
    }
    xth_sync();
}

static void calculatenew(void * /*data*/, struct taskinfo *task, int /*r1*/,
                         int /*r2*/)
{
    int s;
    int i, y;
    realloc_t *rx, *ry, *rend;
    int range = cfractalc.range * 2;
    int positions[16];
    int calcpositions[16];
    /*int s3; */
    if (range < 1)
        range = 1;
    if (range > 16)
        range = 16;
    memset(positions, 0, sizeof(positions));
    calcpositions[0] = 0;
    positions[0] = 1;
    for (s = 1; s < range;) {
        for (i = 0; i < range; i++) {
            if (!positions[i]) {
                for (y = i; y < range && !positions[y]; y++)
                    ;
                positions[(y + i) / 2] = 1;
                calcpositions[s++] = (y + i) / 2;
            }
        }
    }

    if (!xth_nthread(task)) {
        STAT(tocalculate = 0);
        STAT(avoided = 0);
        cfilter.pass = TR("Message", "Solid guessing 1");
        cfilter.max = 0;
        cfilter.pos = 0;
    }

    /* We don't need to wory about race conditions here, since only
     * problem that should happen is incorrectly counted number
     * of lines to do...
     *
     * I will fix that problem later, but I think that this information
     * should be quite useless at multithreaded systems so it should
     * be a bit inaccurate. Just need to take care in percentage
     * displayers that thinks like -100% or 150% should happen
     */
    if (!xth_nthread(task)) {
        for (ry = czoomc.reallocy, rend = ry + cimage.height; ry < rend; ry++) {
            if (ry->recalculate)
                cfilter.max++;
        }
        for (rx = czoomc.reallocx, rend = rx + cimage.width; rx < rend; rx++) {
            if (rx->recalculate) {
                cfilter.max++;
            }
        }
    }
    tcallwait();
    for (s = 0; s < range; s++) {
        for (ry = czoomc.reallocy + calcpositions[s],
            rend = czoomc.reallocy + cimage.height;
             ry < rend; ry += range) {
            xth_lock(0);
            if (ry->recalculate == 1) {
                ry->recalculate = 2;
                xth_unlock(0);
                setchanged(1);
                ry->dirty = 0;
                calcline(ry);
                cfilter.pos++;
#ifndef DRAW
                tcallwait();
#endif
                if (cfilter.interrupt) {
                    break;
                }
            } else {
                xth_unlock(0);
            }
        } /*for ry */
        for (rx = czoomc.reallocx + calcpositions[s],
            rend = czoomc.reallocx + cimage.width;
             rx < rend; rx += range) {
            xth_lock(1);
            if (rx->recalculate == 1) {
                rx->recalculate = 2;
                xth_unlock(1);
                setchanged(1);
                rx->dirty = 0;
                calccolumn(rx);
                cfilter.pos++;
#ifndef DRAW
                tcallwait();
#endif
                if (cfilter.interrupt) {
                    return;
                }
            } else {
                xth_unlock(1);
            }
        }
    }
    STAT(
        printf("Avoided caluclating of %i points from %i and %2.2f%% %2.2f%%\n",
               avoided, tocalculate, 100.0 * (avoided) / tocalculate,
               100.0 * (tocalculate - avoided) / cimage.width / cimage.height));
    STAT(avoided2 += avoided; tocalculate2 += tocalculate; frames2 += 1);
}

static void addprices(realloc_t *r, realloc_t *r2);

static void addprices(realloc_t *r, realloc_t *r2)
{
    realloc_t *r3;
    while (r < r2) {
        r3 = r + (((unsigned int)(r2 - r)) >> 1);
        r3->price = (r2->position - r3->position) * (r3->price);
        if (r3->symref != -1)
            r3->price = r3->price / 2;
        addprices(r, r3);
        r = r3 + 1;
    }
}

/* We can't do both symmetryies (x and y) in one loop at multithreaded
 * systems,since we need to take care to points at the cross of symmetrized
 * point/column
 */
static void dosymmetry(void * /*data*/, struct taskinfo * /*task*/, int r1,
                       int r2)
{
    unsigned char **vbuff = cimage.currlines + r1;
    realloc_t *ry, *rend;
    int linesize = cimage.width * cimage.bytesperpixel;

    for (ry = czoomc.reallocy + r1, rend = czoomc.reallocy + r2; ry < rend;
         ry++) {
        assert(ry->symto >= 0 || ry->symto == -1);
        if (ry->symto >= 0) {
            assert(ry->symto < cimage.height);
            if (!czoomc.reallocy[ry->symto].dirty) {
                memcpy(*vbuff, cimage.currlines[ry->symto], (size_t)linesize);
                ry->dirty = 0;
            }
        }
        vbuff++;
    }
}

/*Well, classical simple quicksort. Should be faster than library one
 *because of reduced number of function calls :)
 */
static inline void myqsort(realloc_t **start, realloc_t **end)
{
    number_t med;
    realloc_t **left = start, **right = end - 1;
    while (1) {

        /*Quite strange caluclation of median, but should be
         *as good as Sedgewick middle of three method and is faster*/
        med = ((*start)->price + (*(end - 1))->price) * 0.5;

        /*Avoid one comparison */
        if (med > (*start)->price) {
            realloc_t *tmp;
            tmp = *left;
            *left = *right;
            *right = tmp;
        }
        right--;
        left++;

        while (1) {
            realloc_t *tmp;

            while (left < right && (*left)->price > med)
                left++;
            while (left < right && med > (*right)->price)
                right--;

            if (left < right) {
                tmp = *left;
                *left = *right;
                *right = tmp;
                left++;
                right--;
            } else
                break;
        }
        if (left - start > 1)
            myqsort(start, left);
        if (end - right <= 2)
            return;
        left = start = right;
        right = end - 1;
    }
}

static int tocalcx, tocalcy;
static void processqueue(void *data, struct taskinfo *task, int /*r1*/,
                         int /*r2*/)
{
    realloc_t **tptr = (realloc_t **)data, **tptr1 = (realloc_t **)tmpdata;
    realloc_t *r, *end;
    end = czoomc.reallocx + cimage.width;

    while (tptr1 < tptr && (!cfilter.interrupt || tocalcx == cimage.width ||
                            tocalcy == cimage.height)) {
        xth_lock(0);
        r = *tptr1;
        if (r != NULL) {
            *tptr1 = NULL;
            xth_unlock(0);
            cfilter.pos++;
            if (tocalcx < cimage.width - 2 && tocalcy < cimage.height - 2)
                cfilter.readyforinterrupt = 1;
            tcallwait();
            if (r >= czoomc.reallocx && r < end) {
                r->dirty = 0;
                tocalcx--;
                calccolumn(r);
            } else {
                r->dirty = 0;
                tocalcy--;
                calcline(r);
            }
        } else {
            xth_unlock(0);
        }
        tptr1++;
    }
}

/*
 * Another long unthreaded code. It seems to be really long and
 * ugly, but believe or not it takes just about 4% of calculation time,
 * so why to worry about? :)
 *
 * This code looks for columns/lines to calculate, adds them into queue,
 * sorts it in order of significancy and then calls parrel processqueue,
 * that does the job.
 */
static void calculatenewinterruptible(void)
{
    realloc_t *r, *r2, *end, *end1;
    realloc_t **table, **tptr;

    /*tptr = table = (realloc_t **) malloc (sizeof (*table) * (cimage.width +
     * cimage.height)); */
    tptr = table = (realloc_t **)tmpdata;
    end = czoomc.reallocx + cimage.width;
    tocalcx = 0, tocalcy = 0;

    STAT(tocalculate = 0);
    STAT(avoided = 0);

    cfilter.pass = TR("Message", "Solid guessing");

    for (r = czoomc.reallocx; r < end; r++)
        if (r->dirty)
            tocalcx++, setchanged(1);

    for (r = czoomc.reallocx; r < end; r++) {
        if (r->recalculate) {
            for (r2 = r; r2 < end && r2->recalculate; r2++)
                *(tptr++) = r2;
            if (r2 == end)
                /*(r2 - 1)->price = 0, */
                r2--;
            addprices(r, r2);
            r = r2;
        }
    }

    end1 = czoomc.reallocy + cimage.height;

    for (r = czoomc.reallocy; r < end1; r++)
        if (r->dirty)
            tocalcy++, setchanged(1);

    for (r = czoomc.reallocy; r < end1; r++) {
        if (r->recalculate) {
            for (r2 = r; r2 < end1 && r2->recalculate; r2++)
                *(tptr++) = r2;
            if (r2 == end1)
                /*(r2 - 1)->price = 0, */
                r2--;
            addprices(r, r2);
            r = r2;
        }
    }
    if (table != tptr) {

        if (tptr - table > 1)
            myqsort(table, tptr);

        cfilter.pos = 0;
        cfilter.max = (int)(tptr - table);
        cfilter.incalculation = 1;
        callwait();

        xth_function(processqueue, tptr, 1);

        callwait();
    }

    cfilter.pos = 0;
    cfilter.max = 0;
    cfilter.pass = "Processing symmetries";
    cfilter.incalculation = 0;
    callwait();

    xth_sync();
    if (nsymmetrized) {
        xth_function(dosymmetry, NULL, cimage.height);
        xth_sync();
        drivercall(cimage, xth_function(dosymmetry2_8, NULL, cimage.width),
                   xth_function(dosymmetry2_16, NULL, cimage.width),
                   xth_function(dosymmetry2_24, NULL, cimage.width),
                   xth_function(dosymmetry2_32, NULL, cimage.width));
        xth_sync();
    }
    if (cfilter.interrupt) {
        cfilter.pass = "reducing resolution";
        mkfilltable();
        xth_function(filly, NULL, cimage.height);
    }
    xth_sync();

    STAT(
        printf("Avoided caluclating of %i points from %i and %2.2f%% %2.2f%%\n",
               avoided, tocalculate, 100.0 * (avoided) / tocalculate,
               100.0 * (tocalculate - avoided) / cimage.width / cimage.height));
    STAT(avoided2 += avoided; tocalculate2 += tocalculate; frames2 += 1);
}

static void init_tables(struct filter *f)
{
    int i;
    zoom_context *c = getzcontext(f);

    /*c->dirty = 2; */
    for (i = 0; i < f->image->width + 1; i++)
        c->xpos[i] =
            (-f->fractalc->rs.nc + f->fractalc->rs.mc) + f->fractalc->rs.mc;
    for (i = 0; i < f->image->height + 1; i++)
        c->ypos[i] =
            (-f->fractalc->rs.ni + f->fractalc->rs.mi) + f->fractalc->rs.mi;
}

static int alloc_tables(struct filter *f)
{
    zoom_context *c = getzcontext(f);
    c->xpos = (number_t *)malloc((f->image->width + 8) * sizeof(*c->xpos));
    if (c->xpos == NULL)
        return 0;
    c->ypos = (number_t *)malloc((f->image->height + 8) * sizeof(*c->ypos));
    if (c->ypos == NULL) {
        free((void *)c->xpos);
        return 0;
    }
    c->reallocx =
        (realloc_t *)malloc(sizeof(realloc_t) * (f->image->width + 8));
    if (c->reallocx == NULL) {
        free((void *)c->xpos);
        free((void *)c->ypos);
        return 0;
    }
    c->reallocy =
        (realloc_t *)malloc(sizeof(realloc_t) * (f->image->height + 8));
    if (c->reallocy == NULL) {
        free((void *)c->xpos);
        free((void *)c->ypos);
        free((void *)c->reallocx);
        return 0;
    }
    return 1;
}

static void free_tables(struct filter *f)
{
    zoom_context *c = getzcontext(f);
    if (c->xpos != NULL)
        free((void *)c->xpos), c->xpos = NULL;
    if (c->ypos != NULL)
        free((void *)c->ypos), c->ypos = NULL;
    if (c->reallocx != NULL)
        free((void *)c->reallocx), c->reallocx = NULL;
    if (c->reallocy != NULL)
        free((void *)c->reallocy), c->reallocy = NULL;
}

static void free_context(struct filter *f)
{
    zoom_context *c;
    c = getzcontext(f);
    free_tables(f);
    free((void *)c);
    f->data = NULL;
}

static zoom_context *make_context(void)
{
    zoom_context *new_ctxt;

    new_ctxt = (zoom_context *)calloc(1, sizeof(zoom_context));
    if (new_ctxt == NULL)
        return NULL;
    new_ctxt->forversion = -1;
    new_ctxt->newcalc = 1;
    new_ctxt->reallocx = NULL;
    new_ctxt->reallocy = NULL;
    new_ctxt->xpos = NULL;
    new_ctxt->ypos = NULL;
    new_ctxt->incomplete = 0;
    return (new_ctxt);
}

static void startbgmkrealloc(void * /*data*/, struct taskinfo * /*task*/,
                             int /*r1*/, int /*r2*/)
{
    mkrealloc_table(czoomc.ypos, czoomc.reallocy, (unsigned int)cimage.height,
                    cfractalc.rs.ni, cfractalc.rs.mi, cursymmetry.ysym,
                    tmpdata1);
}

static int do_fractal(struct filter *f, int flags, int /*time*/)
{
    number_t *posptr;
    int maxres;
    int size;
    int rflags = 0;
    realloc_t *r, *rend;

    f->image->flip(f->image);
    cfilter = *f;
    set_fractalc(f->fractalc, f->image);

    if (getzcontext(f)->forversion != f->fractalc->version ||
        getzcontext(f)->newcalc ||
        getzcontext(f)->forpversion != f->image->palette->version) {
        clear_image(f->image);
        free_tables(f);
        if (!alloc_tables(f))
            return 0;
        init_tables(f);
        getzcontext(f)->newcalc = 0;
        getzcontext(f)->forversion = getfcontext(f)->version;
        getzcontext(f)->forpversion = f->image->palette->version;
        czoomc = *getzcontext(f);
        if (BTRACEOK && !(flags & INTERRUPTIBLE)) {
            boundarytraceall(czoomc.xpos, czoomc.ypos);
            f->flags &= ~ZOOMMASK;
            return CHANGED | (cfilter.interrupt ? INCOMPLETE : 0);
        }
    } else
        rflags |= INEXACT;

    czoomc = *getzcontext(f);

    setincomplete(0);
    setchanged(0);

    maxres = cimage.width;
    if (maxres < cimage.height)
        maxres = cimage.height;
    size = ALIGN((maxres) * (DSIZE + 1) * (int)sizeof(struct dyn_data)) +
           2 * ALIGN(maxres * (int)sizeof(struct dyn_data **)) +
           ALIGN((maxres + 2) * (int)sizeof(int));
    tmpdata = (unsigned char *)malloc(size);
    if (tmpdata == NULL) {
        x_error(
            "XaoS fatal error:Could not allocate memory for temporary data of size %i. "
            "I am unable to handle this problem so please resize to smaller window.",
            size);
        return 0;
    }
    if (nthreads != 1) {
        tmpdata1 = (unsigned char *)malloc(size);
        if (tmpdata1 == NULL) {
            x_error(
                "XaoS fatal error:Could not allocate memory for temporary data of size %i. "
                "I am unable to handle this problem so please resize to smaller window",
                size);
            return 0;
        }
    } else
        tmpdata1 = tmpdata;

    cfilter.incalculation = 0;
    cfilter.readyforinterrupt = 0;
    cfilter.interrupt = 0;

    nsymmetrized = 0;
    cfilter.max = 0;
    cfilter.pos = 0;
    cfilter.pass = "Making y realloc table";
    xth_bgjob(startbgmkrealloc, NULL);

    cfilter.pass = "Making x realloc table";
    mkrealloc_table(czoomc.xpos, czoomc.reallocx, (unsigned int)cimage.width,
                    cfractalc.rs.nc, cfractalc.rs.mc, cursymmetry.xsym,
                    tmpdata);

    callwait();

    cfilter.pass = "Moving old points";
    callwait();
    preparemoveoldpoints();
    xth_sync();
    xth_function(moveoldpoints, NULL, cimage.height);

    cfilter.pass = "Starting calculation";
    callwait();
    xth_sync();
    if (flags & INTERRUPTIBLE)
        calculatenewinterruptible();
    else {
        xth_function(calculatenew, NULL, 1);
        if (cfilter.interrupt) {
            getzcontext(f)->incomplete = 1;
        }
        cfilter.pos = 0;
        cfilter.max = 0;
        cfilter.pass = "Processing symmetries";
        callwait();
        xth_sync();
        if (nsymmetrized) {
            xth_function(dosymmetry, NULL, cimage.height);
            xth_sync();
            drivercall(cimage, xth_function(dosymmetry2_8, NULL, cimage.width),
                       xth_function(dosymmetry2_16, NULL, cimage.width),
                       xth_function(dosymmetry2_24, NULL, cimage.width),
                       xth_function(dosymmetry2_32, NULL, cimage.width));
            xth_sync();
        }
        if (getzcontext(f)->incomplete) {
            fill();
        }
    }
    for (r = czoomc.reallocx, posptr = czoomc.xpos,
        rend = czoomc.reallocx + cimage.width;
         r < rend; r++, posptr++) {
        *posptr = r->position;
    }
    for (r = czoomc.reallocy, posptr = czoomc.ypos,
        rend = czoomc.reallocy + cimage.height;
         r < rend; r++, posptr++) {
        *posptr = r->position;
    }
#ifdef STATISTICS
    STAT(printf("Statistics: frames %i\n"
                "mkrealloctable: added %i, symmetry %i\n"
                "calculate loop: tocalculate %i avoided %i\n"
                "calculate:calculated %i inside %i\n"
                "iters inside:%i iters outside:%i periodicty:%i\n",
                frames2, nadded2, nsymmetry2, tocalculate2, avoided2,
                ncalculated2, ninside2, niter2, niter1, nperi));
#endif
    f->flags &= ~ZOOMMASK;
    if (getzcontext(f)->incomplete)
        rflags |= INCOMPLETE, f->flags |= INCOMPLETE;
    if (getzcontext(f)->incomplete > (cimage.width + cimage.height) / 2)
        f->flags |= LOWQUALITY;
    if (getzcontext(f)->changed)
        rflags |= CHANGED;
    free(tmpdata);
    if (nthreads != 1)
        free(tmpdata1);
    return rflags;
}

static struct filter *getinstance(const struct filteraction *a)
{
    struct filter *f = createfilter(a);
    f->data = make_context();
    f->name = "Zooming engine";
    return (f);
}

static void destroyinstance(struct filter *f)
{
    free_context(f);
    free(f);
}

static int requirement(struct filter *f, struct requirements *r)
{
    r->nimages = 2;
    r->supportedmask = C256 | TRUECOLOR | TRUECOLOR24 | TRUECOLOR16 |
                       LARGEITER | SMALLITER | GRAYSCALE;
    r->flags = IMAGEDATA | TOUCHIMAGE;
    return (f->next->action->requirement(f->next, r));
}

static int initialize(struct filter *f, struct initdata *i)
{
#ifdef USE_MULTABLE
    if (!multable[0]) {
        int i;
        mulmid = multable + RANGE * FPMUL;
        for (i = -RANGE * FPMUL; i < RANGE * FPMUL; i++)
            mulmid[i] = i * i;
    }
#endif
    inhermisc(f, i);
    if (i->image != f->image || datalost(f, i))
        getzcontext(f)->forversion = -1, f->image = i->image;
    f->imageversion = i->image->version;
    return (1);
}

const struct filteraction zoom_filter = {
    "XaoS's zooming engine",
    "zoom",
    0,
    getinstance,
    destroyinstance,
    do_fractal,
    requirement,
    initialize,
    convertupgeneric,
    convertdowngeneric,
    NULL,
};
