//
//  Lynkeos
//  $Id$
//
//  Created by Jean-Etienne LAMIAUD on Wed Feb 22 2023.
//  Copyright (c) 2023. Jean-Etienne LAMIAUD
//
// 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//

#import <XCTest/XCTest.h>

#include <math.h>

#include "ProcessTestUtilities.h"
#include "BayerImageBuffer.h"

static u_short bayerMatrix[2][2] = {{0,1},{1,2}};

static int colorSelector(void *data, u_short x, u_short y)
{
   return bayerMatrix[x%2][y%2];
}

static REAL gradient(u_short x, u_short y, u_short xorigin, u_short yorigin, REAL originValue, double dirx, double diry, double rate)
{
   // Find the distance to the line, orthogonal to the vector, passing by the origin
   double d = (dirx*((double)x - (double)xorigin) + diry*((double)y - (double)yorigin))
              / sqrt(dirx*dirx + diry*diry);

    double v = originValue + d*rate;
   if (v < 0.0)
      v = 0.0;
   if (v > 255.0)
       v = 255.0;

   return v;
}

static void fillBayerGradient(REAL *buffer, u_short width, u_short height, u_short lineWidth,
                              REAL redOrigin, double redRate, double redDirx, double redDiry,
                              REAL greenOrigin, double greenRate, double greenDirx, double greenDiry,
                              REAL blueOrigin, double blueRate, double blueDirx, double blueDiry)
{
   for(u_short y = 0; y < width; y++)
   {
      for(u_short x = 0; x < width; x++)
      {
         REAL o;
         REAL r;
         double dirx, diry;
         switch (colorSelector(NULL, x, y))
         {
            case 0:
               o = redOrigin;
               r = redRate;
               dirx = redDirx;
               diry = redDiry;
               break;
            case 2:
               o = blueOrigin;
               r = blueRate;
               dirx = blueDirx;
               diry = blueDiry;
               break;
            default:
               o = greenOrigin;
               r = greenRate;
               dirx = greenDirx;
               diry = greenDiry;
               break;
         }
         buffer[x+y*lineWidth] = gradient(x, y, 0, 0, o, dirx, diry, r);
      }
   }
}

@interface BayerImageBufferTest : XCTestCase
{
}
@end

@implementation BayerImageBufferTest

+ (void) initialize
{
   initializeProcessTests();
}

- (void) setUp
{
   self.executionTimeAllowance = 10.0;
}

// Left, top, right gradient with three different rates, full image debayerization
- (void) testFullDebayer
{
   const u_short side = 256;
   const NSPoint ZeroOffsets[] = {NSZeroPoint, NSZeroPoint, NSZeroPoint};
   const REAL redInit = 0.0, greenInit = 64.0, blueInit = 192.0;
   const double redRate = 1.0, greenRate = 0.5, blueRate = -0.75;
   const double blackLevel[] = {0.0, 0.0, 0.0, 0.0},
                whiteLevel[] = {254.0, 254.0, 254.0, 255.0}, // Tailored to get a 1 to 1 color transformation
                gamma[] = {1.0, 1.0, 1.0, 1.0};
   REAL *buffer = malloc(side*side*sizeof(REAL));
   u_short x, y;

   // Prepare the test image
   fillBayerGradient(buffer, side, side, side,
                     redInit, redRate, 1.0, 0.0,
                     greenInit, greenRate, 0.0, 1.0,
                     blueInit, blueRate, 1.0, 0.0);

   BayerImageBuffer *bay = [[[BayerImageBuffer alloc] initWithData:buffer opaque:NULL getPlane:colorSelector
                                                             width:side lineW:side height:side
                                                           xoffset:0 yoffset:0
                                                               atX:0 Y:0 W:side H:side
                                                     withTransform:[[NSAffineTransform transform] transformStruct]
                                                    withColorAlign:ZeroOffsets
                                                          withDark:nil withFlat:nil] autorelease];

   CGImageRef cg = [bay getImageInRect:LynkeosMakeIntegerRect(0, 0, side, side)
                             withBlack:blackLevel white:whiteLevel gamma:gamma];

   u_char *plane[5];
   
   NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithCGImage:cg] autorelease];
   [rep getBitmapDataPlanes:plane];
   XCTAssertEqual([rep pixelsHigh], side);
   XCTAssertEqual([rep pixelsWide], side);
   XCTAssertEqual([rep bitmapFormat], NSBitmapFormatAlphaNonpremultiplied);
   NSInteger rowSize = [rep bytesPerRow];
   XCTAssertEqual([rep bitsPerSample], 8);
   XCTAssertEqual([rep bitsPerPixel], 32);
   XCTAssertFalse([rep isPlanar]);

   const double gain = 256.0 / 255.0; // Introduced by the gamma converter...
   for( y = 0; y < side; y += 85 )
   {
      for( x = 0; x < side; x+= 85 )
      {
         double r = plane[0][y*rowSize+x*4];
         double v = plane[0][y*rowSize+x*4+1];
         double b = plane[0][y*rowSize+x*4+2];

         if (x == (side - 1))
            // There is a right margin effect for red as the last bayer pixel is in the last but one column
            XCTAssertEqual( r, floor(gain*(redInit + redRate*(double)(x-1))), @"at %d,%d", x, y );
         else
            XCTAssertEqual( r, floor(gain*(redInit + redRate*(double)x)), @"at %d,%d", x, y );
         XCTAssertEqual( v, floor(gain*(greenInit + greenRate*(double)y)), @"at %d,%d", x, y );
         if (x == 0)
            // There is a left margin effect for blue as the first bayer pixel is in column 1
            XCTAssertEqual( b, floor(gain*(blueInit + blueRate*(double)(x+1))), @"at %d,%d", x, y );
         else
            XCTAssertEqual( b, floor(gain*(blueInit + blueRate*(double)x)), @"at %d,%d", x, y );
      }
   }
   CGImageRelease(cg);
}

// Same gradient, partial image debayerization, aligned square

// Same gradient, partial image debayerization,  fully misaligned square

// Same gradient, partial image debayerization, square misaligned on start

// Same gradient, partial image debayerization, square misaligned on end

@end
