/*
 * producer_pgm.c -- PGM producer
 * Copyright (C) 2008-2026 Meltytech, LLC
 *
 * This program 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 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <framework/mlt_frame.h>
#include <framework/mlt_luma_map.h>
#include <framework/mlt_producer.h>
#include <stdlib.h>
#include <string.h>

static int read_pgm(char *name, uint8_t **image, int *width, int *height, int *maxval);
static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index);
static void producer_close(mlt_producer parent);

mlt_producer producer_pgm_init(mlt_profile profile,
                               mlt_service_type type,
                               const char *id,
                               char *resource)
{
    mlt_producer this = NULL;
    uint8_t *image = NULL;
    int width = 0;
    int height = 0;
    int maxval = 0;

    if (read_pgm(resource, &image, &width, &height, &maxval)) {
        if (resource && strstr(resource, "%luma")) {
            // Failed to read file; generate it.
            mlt_luma_map luma = mlt_luma_map_new(resource);
            if (profile) {
                luma->w = profile->width;
                luma->h = profile->height;
            }
            uint16_t *map = mlt_luma_map_render(luma);
            if (map) {
                int n = luma->w * luma->h;
                image = mlt_pool_alloc(n * 2);
                width = luma->w;
                height = luma->h;
                for (int i = 0; i < n; i++) {
                    image[2 * i] = 16 + map[i] * 219 / USHRT_MAX;
                    image[2 * i + 1] = 128;
                }
                mlt_pool_release(map);
            }
            free(luma);
        }
    }

    if (image) {
        this = calloc(1, sizeof(struct mlt_producer_s));
        if (this != NULL && mlt_producer_init(this, NULL) == 0) {
            mlt_properties properties = MLT_PRODUCER_PROPERTIES(this);
            this->get_frame = producer_get_frame;
            this->close = (mlt_destructor) producer_close;
            mlt_properties_set(properties, "resource", resource);
            mlt_properties_set_data(properties, "image", image, 0, mlt_pool_release, NULL);
            mlt_properties_set_int(properties, "meta.media.width", width);
            mlt_properties_set_int(properties, "meta.media.height", height);
        } else {
            mlt_pool_release(image);
            free(this);
            this = NULL;
        }
    }

    return this;
}

/** Load the PGM file.
*/

static int read_pgm(char *name, uint8_t **image, int *width, int *height, int *maxval)
{
    uint8_t *input = NULL;
    int error = 0;
    FILE *f = mlt_fopen(name, "rb");
    char data[512];

    // Initialise
    *image = NULL;
    *width = 0;
    *height = 0;
    *maxval = 0;

    // Get the magic code
    if (f != NULL && fgets(data, 511, f) != NULL && data[0] == 'P' && data[1] == '5') {
        char *p = data + 2;
        int i = 0;
        int val = 0;

        // PGM Header parser (probably needs to be strengthened)
        for (i = 0; !error && i < 3; i++) {
            if (*p != '\0' && *p != '\n')
                val = strtol(p, &p, 10);
            else
                p = NULL;

            while (error == 0 && p == NULL) {
                if (fgets(data, 511, f) == NULL)
                    error = 1;
                else if (data[0] != '#')
                    val = strtol(data, &p, 10);
            }

            switch (i) {
            case 0:
                *width = val;
                break;
            case 1:
                *height = val;
                break;
            case 2:
                *maxval = val;
                break;
            }
        }

        if (!error) {
            // Validate dimensions and maxval to prevent integer overflow
            if (*width <= 0 || *height <= 0 || *maxval <= 0 || *maxval > 65535 || *width > 16384
                || *height > 16384) {
                error = 1;
            }
        }

        if (!error) {
            // Determine if this is one or two bytes per pixel
            int bpp = *maxval > 255 ? 2 : 1;
            int size = *width * *height * bpp;
            uint8_t *p;

            // Allocate temporary storage for the data and the image
            input = mlt_pool_alloc(*width * *height * bpp);
            *image = mlt_pool_alloc(*width * *height * sizeof(uint8_t) * 2);
            p = *image;

            error = *image == NULL || input == NULL;

            if (!error) {
                // Read the raw data
                error = fread(input, *width * *height * bpp, 1, f) != 1;

                if (!error) {
                    // Convert to yuv422 (very lossy - need to extend this to allow 16 bit alpha out)
                    for (i = 0; i < size; i += bpp) {
                        *p++ = 16 + (input[i] * 219) / 255;
                        *p++ = 128;
                    }
                }
            }
        }

        if (error)
            mlt_pool_release(*image);
        mlt_pool_release(input);
    } else {
        error = 1;
    }

    if (f != NULL)
        fclose(f);

    return error;
}

static int producer_get_image(mlt_frame this,
                              uint8_t **buffer,
                              mlt_image_format *format,
                              int *width,
                              int *height,
                              int writable)
{
    mlt_producer producer = mlt_frame_pop_service(this);
    int real_width = mlt_properties_get_int(MLT_FRAME_PROPERTIES(this), "meta.media.width");
    int real_height = mlt_properties_get_int(MLT_FRAME_PROPERTIES(this), "meta.media.height");
    int size = real_width * real_height;
    uint8_t *image = mlt_pool_alloc(size * 2);
    uint8_t *source = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(producer), "image", NULL);

    mlt_frame_set_image(this, image, size * 2, mlt_pool_release);

    *width = real_width;
    *height = real_height;
    *format = mlt_image_yuv422;
    *buffer = image;

    if (image != NULL && source != NULL)
        memcpy(image, source, size * 2);

    return 0;
}

static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index)
{
    // Construct a test frame
    *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer));

    // Get the frames properties
    mlt_properties properties = MLT_FRAME_PROPERTIES(*frame);

    // Pass the data on the frame properties
    mlt_properties_set_int(properties, "has_image", 1);
    mlt_properties_set_int(properties, "progressive", 1);
    mlt_properties_set_double(properties, "aspect_ratio", 1);
    // Inform framework that this producer creates yuv frames by default
    mlt_properties_set_int(properties, "format", mlt_image_yuv422);

    // Push the image callback
    mlt_frame_push_service(*frame, producer);
    mlt_frame_push_get_image(*frame, producer_get_image);

    // Update timecode on the frame we're creating
    mlt_frame_set_position(*frame, mlt_producer_position(producer));

    // Calculate the next timecode
    mlt_producer_prepare_next(producer);

    return 0;
}

static void producer_close(mlt_producer parent)
{
    parent->close = NULL;
    mlt_producer_close(parent);
    free(parent);
}
