/**************************************************************************
 *{@C
 *      Copyright:      2008-2018 Paul Obermeier (obermeier@tcl3d.org)
 *
 *                      See the file "Tcl3D_License.txt" for information on
 *                      usage and redistribution of this file, and for a
 *                      DISCLAIMER OF ALL WARRANTIES.
 *
 *      Module:         Tcl3D -> tcl3dOgl
 *      Filename:       tcl3dUtilFractal.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions to create fractal images.
 *
 *      Exported functions:
 *                      tcl3dUtilFractalToPhoto
 *
 **************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <tcl.h>
#include <tk.h>

#include "tcl3dUtilRandom.h"
#include "tcl3dUtilFractal.h"

static unsigned int *rRand = NULL;
static unsigned int *gRand = NULL;
static unsigned int *bRand = NULL;
static randomGen rGen = NULL;

static void setupRandomColors (
            int numIter,
            unsigned int br, unsigned int bg, unsigned int bb)
{
    int i;

    rRand = (unsigned int *) malloc ((numIter+1) * sizeof (unsigned int));
    gRand = (unsigned int *) malloc ((numIter+1) * sizeof (unsigned int));
    bRand = (unsigned int *) malloc ((numIter+1) * sizeof (unsigned int));
    rGen  = tcl3dNewRandomGen (0);

    for (i = 0; i < numIter; i++) {
        rRand[i] = (unsigned int) (tcl3dGetRandomInt (rGen, 0, 255));
        gRand[i] = (unsigned int) (tcl3dGetRandomInt (rGen, 0, 255));
        bRand[i] = (unsigned int) (tcl3dGetRandomInt (rGen, 0, 255));
    }
    rRand[numIter] = br;
    gRand[numIter] = bg;
    bRand[numIter] = bb;
}

static void freeRandomColors ()
{
    if (rRand) {
        free (rRand);
        free (gRand);
        free (bRand);
        tcl3dDeleteRandomGen (rGen);
        rRand = NULL;
        gRand = NULL;
        bRand = NULL;
        rGen  = NULL;
    }
}

static void getRandomColor (int n,
            unsigned int *r, unsigned int *g, unsigned int *b)
{
    *r = rRand[n];
    *g = gRand[n];
    *b = bRand[n];
}

static void getRenormalizedColor (
            double re, double im, int n, 
            int numIter, double bandFrequ, 
            unsigned int br, unsigned int bg, unsigned int bb,
            unsigned int ir, unsigned int ig, unsigned int ib,
            unsigned int or, unsigned int og, unsigned int ob,
            unsigned int *r, unsigned int *g, unsigned int *b)
{
    double len, f, mix;

    if ( n >= numIter) {
        *r = br;
        *g = bg;
        *b = bb;
        return;
    }
    len = sqrt (re*re + im*im);
    f = n + 1.0 - log (log (len)) / log(2);
    mix = f * bandFrequ;
    if ( mix > 1.0 ) {
        mix = 1.0;
    }
    *r = (unsigned int) ((1.0 - mix) * ir + mix * or);
    *g = (unsigned int) ((1.0 - mix) * ig + mix * og);
    *b = (unsigned int) ((1.0 - mix) * ib + mix * ob);
    return;
}

int tcl3dUtilFractalToPhoto (
        Tcl_Interp *interp, const char *photoName, const char *coloringMethod,
        int width, int height, 
        double cx, double cy, double size,
        int numIter, double bandFrequ,
        unsigned char br, unsigned char bg, unsigned char bb,
        unsigned char ir, unsigned char ig, unsigned char ib,
        unsigned char or, unsigned char og, unsigned char ob)
{
    int x, y, n, numChans;
    Tk_PhotoHandle photo;
    Tk_PhotoImageBlock photoCont;
    unsigned char *dstPtr;
    unsigned char *dstStop;
    unsigned char *scanBuf;
    unsigned int r, g, b;
    double xScale, yScale, min_im, min_re;
    double c_re, c_im, z_re, z_im, z_re2, z_im2;
    int useRandom = (strcmp (coloringMethod, "Random") == 0);

    if (!(photo = Tk_FindPhoto (interp, (char *)photoName))) {
        Tcl_AppendResult (interp, "cannot find photo image: ",
                          photoName, (char *)NULL);
        return TCL_ERROR;
    }

    #if (TK_MAJOR_VERSION > 8) || \
       ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
        Tk_PhotoSetSize (interp, photo, width, height);
    #else
        Tk_PhotoSetSize (photo, width, height);
    #endif

    numChans = 3;

    if (!(scanBuf = (unsigned char *)
                    malloc (width * numChans * sizeof (unsigned char)))) {
        Tcl_AppendResult (interp, "cannot alloc scanline bufer for photo: ",
                          photoName, (char *)NULL);
        return TCL_ERROR;
    }

    photoCont.pixelSize = numChans;
    photoCont.pitch = width * numChans;
    photoCont.width = width;
    photoCont.height = 1;
    photoCont.offset[0] = 0;
    photoCont.offset[1] = 1;
    photoCont.offset[2] = 2;
    photoCont.offset[3] = (numChans == 3? 0: 3);

    photoCont.pixelPtr = scanBuf;

    xScale = size / width;
    yScale = size / height;
    min_re   = cx - size*0.5;
    min_im   = cy - size*0.5;

    if (useRandom) {
        setupRandomColors (numIter, br, bg, bb);
    }

    for (y = 0; y < height; y++) {
        dstPtr = scanBuf;
        c_im = min_im + y * yScale;
        for (x = 0; x < width; x++) {
            c_re = min_re + x * xScale;

            z_re = c_re;
            z_im = c_im;
            for (n = 0; n < numIter; n++) {
                z_re2 = z_re * z_re;
                z_im2 = z_im * z_im;
                if ((z_re2 + z_im2) > 4.0) {
                    break;
                }
                z_im = 2.0 * z_re * z_im + c_im;
                z_re = z_re2 - z_im2 + c_re;
            }
            if (useRandom) {
                getRandomColor (n, &r, &g, &b);
            } else {
                getRenormalizedColor (z_re, z_im, n, numIter, bandFrequ, 
                                      br, bg, bb, ir, ig, ib, or, og, ob,
                                      &r, &g, &b);
            }
            *(dstPtr++) = (unsigned char) r;
            *(dstPtr++) = (unsigned char) g;
            *(dstPtr++) = (unsigned char) b;
        }

        #if (TK_MAJOR_VERSION > 8) || \
            ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
            Tk_PhotoPutBlock (interp, photo, &photoCont, 0, height-1-y,
                              width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
        #elif ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION > 3))
            Tk_PhotoPutBlock (photo, &photoCont, 0, height-1-y,
                              width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
        #else
            Tk_PhotoPutBlock (photo, &photoCont, 0, height-1-y,
                              width, 1);
        #endif
    }
    free (scanBuf);
    if (useRandom) {
        freeRandomColors();
    }
    return TCL_OK;
}
