//
//  PRDFTFilter.m
//  PRICE
//
//  Created by Riccardo Mottola on Tue Nov 18 2003.
//  Copyright (c) 2003-2005 Carduus. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under the terms of the version 2 of the GNU General Public License as published by the Free Software Foundation.
// 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.

#import "PRDFTFilter.h"
#import "PRGrayscaleFilter.h"
#import "FFT.h"
#import <math.h>
#import "PRProgressAction.h"

extern double *cosinus;
extern double *sinus;
extern unsigned int *bitRevIndex;

@implementation PRDFTFilter

- (PRImage *)filterImage :(PRImage *)srcImage :(double **)filterMatRe :(double **)filterMatIm :(unsigned int) num :(BOOL)autoRange :(id)controller
/* perform a convolution between the source image and the filter given my the two matrices */
{
    NSBitmapImageRep *srcImageRep;
    PRImage *destImage;
    NSBitmapImageRep *destImageRep;
    
    unsigned int w, h;
    unsigned int x, y; /* image scanning variables */
    unsigned int i;    /* convolve matrix scanning */
    unsigned char *srcData;
    unsigned char *destData;
    int bytesPerPixel;
    
    unsigned int leftBorder, topBorder;  /* borders to center the image */
    unsigned int rightBorder, bottomBorder;  
    
    unsigned int bitNum;
    double *Aa;
    double *Ab;
    double *ya;
    double *yb;
    double **Ma;   /* non quantized, float image */
    double **Mb;
    
    double min, max, scale;

    if (controller)
    {
        [controller setActivity:@"initialize FFT"];
        [controller advanceProgress];
    }
        
     /* get source image representation and associated information */
    srcImageRep = [srcImage tiffRep];
    
    w = [srcImage width];
    h = [srcImage height];
    bytesPerPixel = [srcImage bitsPP] / 8;

    /* calculate back the bit number */
    bitNum = binaryLog(num);
    
    sinus = (double*)calloc(num, sizeof(double));
    cosinus = (double*)calloc(num, sizeof(double));
    bitRevIndex = (unsigned int*)calloc(num, sizeof(unsigned int));
    initTrigonometrics(num, bitNum);

    
    if ([srcImageRep hasAlpha])
    {
        if ([srcImageRep samplesPerPixel] == 2)
        {
            printf("Grayscale image\n");
        } else
        {
            printf("Color image\n");
            return srcImage;
        }
    } else
    {
        if ([srcImageRep samplesPerPixel] == 1)
        {
            printf("Grayscale image\n");
        } else
        {
            PRGrayscaleFilter *grayFilter;
            printf("Color image\n");
            grayFilter = [[PRGrayscaleFilter alloc] init];
            srcImage = [grayFilter filterImage:srcImage];
            [grayFilter release];
            NSLog (@"done greyscale converting");
            /* we reget previous information that is no longer valid */
            /* get source image representation and associated information */
            srcImageRep = [NSBitmapImageRep imageRepWithData:[srcImage TIFFRepresentation]];
            bytesPerPixel = [srcImageRep bitsPerPixel] /8;
        }
    }
    
    /* allocate 1-D FFT arrays */
    Aa = (double*)calloc(num, sizeof(double));
    Ab = (double*)calloc(num, sizeof(double));
    ya = (double*)calloc(num, sizeof(double));
    yb = (double*)calloc(num, sizeof(double));
    
    /* allocate float destination matrix */

    Ma = (double**)calloc(num, sizeof(double*));
    for (i = 0; i < num; i++)
    {
        Ma[i] = (double*)calloc(num, sizeof(double));
        if (!Ma[i])
        {
            printf("out of memory allocating float matrix?\n");
            return srcImage;
        }
        memset(Ma[i], '\000', num);
    }
    Mb = (double**)calloc(num, sizeof(double*));
    for (i = 0; i < num; i++)
    {
        Mb[i] = (double*)calloc(num, sizeof(double));
        if (!Mb[i])
        {
            printf("out of memory allocating float matrix?\n");
            return srcImage;
        }
        memset(Mb[i], '\000', num);
    }
    
    /* allocate destination image and its representation */
    destImage = [[PRImage alloc] initWithSize:NSMakeSize(w, h)];
    destImageRep = [[NSBitmapImageRep alloc] \
                    initWithBitmapDataPlanes:NULL \
                    pixelsWide:w \
                    pixelsHigh:h \
                    bitsPerSample:8 \
                    samplesPerPixel:1 \
                    hasAlpha:NO \
                    isPlanar:NO \
                    colorSpaceName:NSCalibratedWhiteColorSpace \
                    bytesPerRow:0 \
                    bitsPerPixel:0];
    
    srcData = [srcImageRep bitmapData];
    destData = [destImageRep bitmapData];
    
    /* calculate borders */
    leftBorder = (num - w) / 2;
    topBorder = (num - h) / 2;
    rightBorder =  (num - w) - leftBorder;
    bottomBorder = (num - h) - topBorder;
    printf("top %d left %d\n", topBorder, leftBorder);
    
    /* copy the image to the float matrix */
    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
            Ma[y + topBorder][x + leftBorder] = (double)srcData[y * w + x] / UCHAR_MAX; /* the image is greyscale, no bit-depth displace */


    /* replicate borders */
    
    /* top */
    for (y = 0; y < topBorder; y++)
        for (x = 0; x < w; x++)
            Ma[y][x + leftBorder] = Ma[2*topBorder-y-1][x + leftBorder];

    /* bottom */
    for (y = 0; y < bottomBorder; y++)
        for (x = 0; x < w; x++)
            Ma[y+h+topBorder][x + leftBorder] = Ma[(h+topBorder)-y-1][x + leftBorder];

    if (leftBorder > 0)
    {
        /* top left */
        for (y = 0; y < topBorder; y++)
            for (x = 0; x < leftBorder; x++)
                Ma[y][x] = Ma[2*topBorder-y-1][2*leftBorder-x-1];
        /* left */
        for (y = 0; y < h; y++)
            for (x = 0; x < leftBorder; x++)
                Ma[y + topBorder][x] = Ma[y + topBorder][2*leftBorder-x-1];
        /* bottom left */
        for (y = 0; y < topBorder; y++)
            for (x = 0; x < leftBorder; x++)
                Ma[y+h+topBorder][x] = Ma[(h+topBorder)-y-1][2*leftBorder-x-1];
    }
    
    if (rightBorder > 0)
    {
        /* top right */
        for (y = 0; y < topBorder; y++)
            for (x = 0; x < rightBorder; x++)
                Ma[y][x+w+leftBorder] = Ma[2*topBorder-y-1][(w+leftBorder)-x-1];
        /* right */
        for (y = 0; y < h; y++)
            for (x = 0; x < rightBorder; x++)
                Ma[y + topBorder][x+w+leftBorder] = Ma[y + topBorder][(w+rightBorder)-x-1];
        /* bottom right */
        for (y = 0; y < bottomBorder; y++)
            for (x = 0; x < rightBorder; x++)
                Ma[y+h+topBorder][x+w+leftBorder] = Ma[(h+topBorder)-y-1][(w+rightBorder)-x-1];
    }

   
    
    /* execute the actual filtering */
    
    if (controller)
    {
        [controller setActivity:@"FFT forward pass"];
        [controller advanceProgress];
    }
    /* forward pass */
    /* horizontal pass */
    for (y = 0; y < num; y++)
    {
        for (i = 0; i < num; i++)
        {
            Aa[i] = Ma[y][i];
            Ab[i] = Mb[y][i];
        }
        fft(num, bitNum, Aa, Ab, ya, yb);
        for (i = 0; i < num; i++)
        {
            Ma[y][i] = ya[i];
            Mb[y][i] = yb[i];
        }
    }
    

    /* vertical pass */
    for (x = 0; x < num; x++)
    {
        for (i = 0; i < num; i++)
        {
            Aa[i] = Ma[i][x];
            Ab[i] = Mb[i][x];
        }
        fft(num, bitNum, Aa, Ab, ya, yb);
        for (i = 0; i < num; i++)
        {
            Ma[i][x] = ya[i];
            Mb[i][x] = yb[i];
        }
    } 

//    printf("Fa %f %f\n", filterMatRe[0][0], filterMatRe[30][30]);

    /* applying the transformation */
    if (filterMatIm) /* we have a filter with I part */
        for (x = 0; x < num; x++)
            for (y = 0; y < num; y++)
            {
                Ma[x][y] = Ma[x][y]*filterMatRe[x][y] - Mb[x][y]*filterMatIm[x][y];
                Mb[x][y] = Ma[x][y]*filterMatIm[x][y] + Mb[x][y]*filterMatRe[x][y];
            }
    else
        for (x = 0; x < num; x++)
            for (y = 0; y < num; y++)
            {
                Ma[x][y] = Ma[x][y]*filterMatRe[x][y];
                Mb[x][y] = Mb[x][y]*filterMatRe[x][y];
            }

    if (controller)
    {
        [controller setActivity:@"FFT reverse pass"];
        [controller advanceProgress];
    }
    /* reverse pass */
    /* horizontal pass */
    for (y = 0; y < num; y++)
    {
        for (i = 0; i < num; i++)
        {
            Aa[i] = Ma[y][i];
            Ab[i] = Mb[y][i];
        }
        ifft(num, bitNum, Aa, Ab, ya, yb);
        for (i = 0; i < num; i++)
        {
            Ma[y][i] = ya[i];
            Mb[y][i] = yb[i];
        }
    }


    /* vertical pass */
    for (x = 0; x < num; x++)
    {
        for (i = 0; i < num; i++)
        {
            Aa[i] = Ma[i][x];
            Ab[i] = Mb[i][x];
        }
        ifft(num, bitNum, Aa, Ab, ya, yb);
        for (i = 0; i < num; i++)
        {
            Ma[i][x] = ya[i];
            Mb[i][x] = yb[i];
        }
    }
    
    if (autoRange)
    {   /* AutoRange result */
        /* now we find the range */
        min = DBL_MAX;
        max = DBL_MIN;
        for (y = 0; y < h; y++)
        {
            for (x = 0; x < w; x++)
            {
                if (Ma[y+topBorder][x+leftBorder] > max)
                    max = Ma[y+topBorder][x+leftBorder];
                if (Ma[y+topBorder][x+leftBorder] < min)
                    min = Ma[y+topBorder][x+leftBorder];
            }
        }
        scale = fabs(max - min)/(double)UCHAR_MAX;
        printf ("min = %f, max = %f, scale = %f\n", min, max, scale);
        /* now we copy the result back, after scaling  */
        for (y = 0; y < h; y++)
            for (x = 0; x < w; x++)
                destData[y*w + x] = (unsigned char) rint((Ma[y+topBorder][x+leftBorder]-min)/scale);
    } else
    { /* just scale back the result */
        double sample;
        /* now we copy the result back, after clipping  */
        for (y = 0; y < h; y++)
            for (x = 0; x < w; x++)
            {
                sample = rint(Ma[y+topBorder][x+leftBorder]*UCHAR_MAX);
                if (sample > UCHAR_MAX)
                    sample = UCHAR_MAX;
                else if (sample < 0)
                    sample = 0;
                destData[y*w + x] = (unsigned char) sample;
            }
    }


    [destImage addRepresentation:destImageRep];
    [destImageRep release];
    

    /* free up the 1-D arrays */
    free(Aa);
    free(Ab);
    free(ya);
    free(yb);

    /* now we free our float matrix */
    for (i = 0; i < num; i++)
        free(Ma[i]);
    free(Ma);
    for (i = 0; i < num; i++)
        free(Mb[i]);
    free(Mb);
    
    /* free other arrays */
    free(sinus);
    free(cosinus);
    free(bitRevIndex);
    
    if (controller)
    {
        [controller setActivity:@"FFT done"];
        [controller showProgress];
    }
    
    [destImage autorelease];
    return destImage;
}

@end
