/*
 * filter_resize.c -- resizing filter
 * Copyright (C) 2003-2023 Meltytech, LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <framework/mlt_filter.h>
#include <framework/mlt_frame.h>
#include <framework/mlt_log.h>
#include <framework/mlt_profile.h>

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

static uint8_t *resize_alpha(
    uint8_t *input, int owidth, int oheight, int iwidth, int iheight, uint8_t alpha_value)
{
    uint8_t *output = NULL;

    if (input != NULL && (iwidth != owidth || iheight != oheight) && (owidth > 6 && oheight > 6)) {
        uint8_t *out_line;
        int offset_x = (owidth - iwidth) / 2;
        int offset_y = (oheight - iheight) / 2;
        int iused = iwidth;

        output = mlt_pool_alloc(owidth * oheight);
        memset(output, alpha_value, owidth * oheight);

        offset_x -= offset_x % 2;

        out_line = output + offset_y * owidth;
        out_line += offset_x;

        // Loop for the entirety of our output height.
        while (iheight--) {
            // We're in the input range for this row.
            memcpy(out_line, input, iused);

            // Move to next input line
            input += iwidth;

            // Move to next output line
            out_line += owidth;
        }
    }

    return output;
}

static void resize_image(uint8_t *output,
                         int owidth,
                         int oheight,
                         uint8_t *input,
                         int iwidth,
                         int iheight,
                         int bpp,
                         mlt_image_format format,
                         uint8_t alpha_value)
{
    // Calculate strides
    int istride = iwidth * bpp;
    int ostride = owidth * bpp;
    int offset_x = (owidth - iwidth) / 2 * bpp;
    int offset_y = (oheight - iheight) / 2;
    uint8_t *in_line = input;
    uint8_t *out_line;
    int size = owidth * oheight;
    uint8_t *p = output;

    // Optimisation point
    if (output == NULL || input == NULL
        || (owidth <= 6 || oheight <= 6 || iwidth <= 6 || iheight <= 6)) {
        return;
    } else if (iwidth == owidth && iheight == oheight) {
        memcpy(output, input, iheight * istride);
        return;
    }

    if (format == mlt_image_rgba) {
        memset(p, 0, size * bpp);
        if (alpha_value != 0) {
            while (size--) {
                p[3] = alpha_value;
                p += 4;
            }
        }
    } else if (bpp == 2) {
        memset(p, 16, size * bpp);
        while (size--) {
            p[1] = 128;
            p += 2;
        }
        offset_x -= offset_x % 4;
    } else {
        memset(p, 0, size * bpp);
    }

    out_line = output + offset_y * ostride;
    out_line += offset_x;

    // Loop for the entirety of our output height.
    while (iheight--) {
        // We're in the input range for this row.
        memcpy(out_line, in_line, istride);

        // Move to next input line
        in_line += istride;

        // Move to next output line
        out_line += ostride;
    }
}

/** A padding function for frames - this does not rescale, but simply
	resizes.
*/

static uint8_t *frame_resize_image(mlt_frame frame, int owidth, int oheight, mlt_image_format format)
{
    // Get properties
    mlt_properties properties = MLT_FRAME_PROPERTIES(frame);

    // Get the input image, width and height
    uint8_t *input = mlt_properties_get_data(properties, "image", NULL);
    uint8_t *alpha = mlt_frame_get_alpha(frame);
    int alpha_size = 0;
    mlt_frame_get_alpha_size(frame, &alpha_size);
    int bpp = 0;
    mlt_image_format_size(format, owidth, oheight, &bpp);

    int iwidth = mlt_properties_get_int(properties, "width");
    int iheight = mlt_properties_get_int(properties, "height");

    // If width and height are correct, don't do anything
    if (iwidth < owidth || iheight < oheight) {
        mlt_log_debug(NULL,
                      "[filter_resize] %dx%d -> %dx%d (%s)\n",
                      iwidth,
                      iheight,
                      owidth,
                      oheight,
                      mlt_image_format_name(format));

        uint8_t alpha_value = mlt_properties_get_int(properties, "resize_alpha");
        // Create the output image
        uint8_t *output = mlt_pool_alloc(owidth * (oheight + 1) * bpp);

        // Call the generic resize
        resize_image(output, owidth, oheight, input, iwidth, iheight, bpp, format, alpha_value);

        // Now update the frame
        mlt_frame_set_image(frame, output, owidth * (oheight + 1) * bpp, mlt_pool_release);

        // We should resize the alpha too
        if (format != mlt_image_rgba && alpha && alpha_size >= iwidth * iheight) {
            alpha = resize_alpha(alpha, owidth, oheight, iwidth, iheight, alpha_value);
            if (alpha)
                mlt_frame_set_alpha(frame, alpha, owidth * oheight, mlt_pool_release);
        }

        // Return the output
        return output;
    }
    // No change, return input
    return input;
}

/** Do it :-).
*/

static int filter_get_image(mlt_frame frame,
                            uint8_t **image,
                            mlt_image_format *format,
                            int *width,
                            int *height,
                            int writable)
{
    int error = 0;

    // Get the properties from the frame
    mlt_properties properties = MLT_FRAME_PROPERTIES(frame);

    // Pop the top of stack now
    mlt_filter filter = mlt_frame_pop_service(frame);
    mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter));

    // Retrieve the aspect ratio
    double aspect_ratio = mlt_deque_pop_back_double(MLT_FRAME_IMAGE_STACK(frame));
    double consumer_aspect = mlt_profile_sar(mlt_service_profile(MLT_FILTER_SERVICE(filter)));

    // Correct Width/height if necessary
    if (*width == 0 || *height == 0) {
        *width = profile->width;
        *height = profile->height;
    }

    // Assign requested width/height from our subordinate
    int owidth = *width;
    int oheight = *height;

    // Check for the special case - no aspect ratio means no problem :-)
    if (aspect_ratio == 0.0)
        aspect_ratio = consumer_aspect;

    // Reset the aspect ratio
    mlt_properties_set_double(properties, "aspect_ratio", aspect_ratio);

    // Skip scaling if requested
    char *rescale = mlt_properties_get(properties, "consumer.rescale");
    if (rescale != NULL && !strcmp(rescale, "none"))
        return mlt_frame_get_image(frame, image, format, width, height, writable);

    if (mlt_properties_get_int(properties, "distort") == 0 && profile) {
        // Normalize the input and out display aspect
        int normalized_width = profile->width;
        int normalized_height = profile->height;
        int real_width = mlt_properties_get_int(properties, "meta.media.width");
        int real_height = mlt_properties_get_int(properties, "meta.media.height");
        if (real_width == 0)
            real_width = mlt_properties_get_int(properties, "width");
        if (real_height == 0)
            real_height = mlt_properties_get_int(properties, "height");
        double input_ar = aspect_ratio * real_width / real_height;
        double output_ar = consumer_aspect * owidth / oheight;

        // 		fprintf( stderr, "real %dx%d normalized %dx%d output %dx%d sar %f in-dar %f out-dar %f\n",
        // 		real_width, real_height, normalized_width, normalized_height, owidth, oheight, aspect_ratio, input_ar, output_ar);

        // Optimised for the input_ar > output_ar case (e.g. widescreen on standard)
        int scaled_width = rint((input_ar * normalized_width) / output_ar);
        int scaled_height = normalized_height;

        // Now ensure that our images fit in the output frame
        if (scaled_width > normalized_width) {
            scaled_width = normalized_width;
            scaled_height = rint((output_ar * normalized_height) / input_ar);
        }

        // Now calculate the actual image size that we want
        owidth = rint(scaled_width * owidth / normalized_width);
        oheight = rint(scaled_height * oheight / normalized_height);

        // Tell frame we have conformed the aspect to the consumer
        mlt_frame_set_aspect_ratio(frame, consumer_aspect);
    }

    mlt_properties_set_int(properties, "distort", 0);

    // Now pass on the calculations down the line
    mlt_properties_set_int(properties, "resize_width", *width);
    mlt_properties_set_int(properties, "resize_height", *height);

    // If there will be padding, then we need packed image format.
    if (*format == mlt_image_yuv420p && (owidth < *width || oheight < *height)) {
        *format = mlt_image_yuv422;
    }

    // Now get the image
    if (*format == mlt_image_yuv422) {
        owidth -= owidth % 2;
        *width -= *width % 2;
    }
    error = mlt_frame_get_image(frame, image, format, &owidth, &oheight, writable);

    if (error == 0 && *image && *format != mlt_image_yuv420p) {
        *image = frame_resize_image(frame, *width, *height, *format);
    } else {
        *width = owidth;
        *height = oheight;
    }

    return error;
}

/** Filter processing.
*/

static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
{
    // Store the aspect ratio reported by the source
    mlt_deque_push_back_double(MLT_FRAME_IMAGE_STACK(frame), mlt_frame_get_aspect_ratio(frame));

    // Push this on to the service stack
    mlt_frame_push_service(frame, filter);

    // Push the get_image method on to the stack
    mlt_frame_push_get_image(frame, filter_get_image);

    return frame;
}

/** Constructor for the filter.
*/

mlt_filter filter_resize_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg)
{
    mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s));
    if (mlt_filter_init(filter, filter) == 0) {
        filter->process = filter_process;
    }
    return filter;
}
