#include "pswarp.h"

static void transformTileArgsFree(pswarpTransformTileArgs *args)
{
    psFree(args->input);
    psFree(args->output);
    psFree(args->grid);
    psFree(args->interp);
    psFree(args->region);
    return;
}

pswarpTransformTileArgs *pswarpTransformTileArgsAlloc()
{
    pswarpTransformTileArgs *args = psAlloc(sizeof(pswarpTransformTileArgs));
    psMemSetDeallocator(args, (psFreeFunc)transformTileArgsFree);

    args->input = NULL;
    args->output = NULL;
    args->grid = NULL;
    args->interp = NULL;
    args->region = NULL;

    args->gridX = 0;
    args->gridY = 0;

    args->goodPixels = 0;
    args->xMin = PS_MAX_S32;
    args->xMax = PS_MIN_S32;
    args->yMin = PS_MAX_S32;
    args->yMax = PS_MIN_S32;

    return args;
}

bool pswarpTransformTile(pswarpTransformTileArgs *args)
{
    psImage *inImage = args->input->image; // Input image
    psImage *outImage = args->output->image; // Output image

    int inNumCols = inImage->numCols, inNumRows = inImage->numRows; // Size of input image
    int outNumCols = outImage->numCols, outNumRows = outImage->numRows; // Size of output image
    int outCol0 = outImage->col0, outRow0 = outImage->row0; // Offset of output image

    psPlane minPt, maxPt;               // Minimum and maximum points for this tile
    pswarpMapGridCoordRange(args->grid, args->gridX, args->gridY, &minPt, &maxPt);

    // Dereference images for convenience
    psF32 **outImageData     = args->output->image->data.F32;
    psF32 **outVarData       = (args->output->weight) ? args->output->weight->data.F32 : NULL;
    psMaskType **outMaskData = (args->output->mask)   ? args->output->mask->data.PS_TYPE_MASK_DATA : NULL;
    psMaskType **inMaskData  = (args->input->mask)    ? args->input->mask->data.PS_TYPE_MASK_DATA : NULL;

    pswarpMap *map = args->grid->maps[args->gridX][args->gridY]; // Map for this tile
    psImage *region = args->region;     // Region to transform

    // Bounds for iteration
    int xMin = PS_MAX(minPt.x, 0);
    int xMax = PS_MIN(maxPt.x, outNumCols);
    int yMin = PS_MAX(minPt.y, 0);
    int yMax = PS_MIN(maxPt.y, outNumRows);

    // Iterate over the output image pixels (parent frame)
    long goodPixels = 0;                // Number of input pixels landing on the output image
    for (int y = yMin; y < yMax; y++) {
        for (int x = xMin; x < xMax; x++) {

            // Only transform those pixels requested
            if (region && region->data.U8[y][x]) {
                continue;
            }

            // pswarpMapApply converts the output coordinate (x,y) to the input coordinate.
            // both are in the parent frames of the input and output images.
            double xInRaw, yInRaw;      // Input raw pixel coordinates
            pswarpMapApply(&xInRaw, &yInRaw, map, x + 0.5, y + 0.5);
            double xIn = xInRaw - outCol0, yIn = yInRaw - outRow0; // Position on input image
            if (xIn < 0 || xIn >= inNumCols || yIn < 0 || yIn >= inNumRows) {
                continue;
            }

            // psImagePixelInterpolate determines the value at pixel coordinate (x,y) in child coordinates
            double imageValue, varValue; // Value of image and variance map
            psMaskType maskValue = inMaskData ? inMaskData[(int)yIn][(int)xIn] : 0; // Value of mask
            if (!psImageInterpolate(&imageValue, &varValue, &maskValue, xIn, yIn, args->interp)) {
                psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
                return false;
            }

            int xOut = x - outCol0, yOut = y - outRow0; // Position on output image

            if (outImageData) {
                outImageData[yOut][xOut] = imageValue;
            }
            if (outVarData) {
                outVarData[yOut][xOut] = varValue;
            }
            if (outMaskData) {
                outMaskData[yOut][xOut] = maskValue;
            }

            goodPixels++;
        }
    }

    args->goodPixels = goodPixels;
    args->xMin = xMin;
    args->xMax = xMax;
    args->yMin = yMin;
    args->yMax = yMax;

    return true;
}
