/*
 * filter_pillar_echo.c -- filter to interpolate pixels outside an area of interest
 * Copyright (c) 2020-2021 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 "image_proc.h"

#include <framework/mlt.h>
#include <framework/mlt_log.h>

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

/** Constrain a rect to be within the max dimensions
*/
static mlt_rect constrain_rect( mlt_rect rect, int max_x, int max_y )
{
	if ( rect.x < 0 )
	{
		rect.w = rect.w + rect.x ;
		rect.x = 0;
	}
	if ( rect.y < 0 )
	{
		rect.h = rect.h + rect.y;
		rect.y = 0;
	}
	if ( rect.x + rect.w < 0 )
	{
		rect.w = 0;
	}
	if ( rect.y + rect.h < 0 )
	{
		rect.h = 0;
	}
	if ( rect.x + rect.w > max_x )
	{
		rect.w = max_x - rect.x;
	}
	if ( rect.y + rect.h > max_y )
	{
		rect.h = max_y - rect.y;
	}
	return rect;
}

typedef struct
{
	mlt_image src;
	mlt_image dst;
	mlt_rect rect;
} scale_sliced_desc;

static int scale_sliced_proc(int id, int index, int jobs, void* data)
{
	(void) id; // unused
	scale_sliced_desc* desc = ((scale_sliced_desc*) data);
	mlt_image src = desc->src;
	mlt_image dst = desc->dst;
	mlt_rect rect = desc->rect;
	int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, src->height, &slice_line_start);
	int slice_line_end = slice_line_start + slice_height;
	double srcScale = rect.h / (double)src->height;
	int linesize = src->width * 4;
	uint8_t* d = dst->data + (slice_line_start * linesize);
	for ( int y = slice_line_start; y < slice_line_end; y++ )
	{
		double srcY = rect.y + (double)y * srcScale;
		int srcYindex = floor(srcY);
		double fbottom = srcY - srcYindex;
		double ftop = 1.0 - fbottom;

		int x = 0;
		for ( x = 0; x < src->width; x++ )
		{
			double srcX = rect.x + (double)x * srcScale;
			int srcXindex = floor(srcX);
			double fright = srcX - srcXindex;
			double fleft = 1.0 - fright;

			double valueSum[] = {0.0, 0.0, 0.0, 0.0};
			double factorSum[] = {0.0, 0.0, 0.0, 0.0};

			uint8_t* s = src->data + (srcYindex * linesize) + (srcXindex * 4);

			// Top Left
			double ftl = ftop * fleft;
			valueSum[0] += s[0] * ftl;
			factorSum[0] += ftl;
			valueSum[1] += s[1] * ftl;
			factorSum[1] += ftl;
			valueSum[2] += s[2] * ftl;
			factorSum[2] += ftl;
			valueSum[3] += s[3] * ftl;
			factorSum[3] += ftl;

			// Top Right
			if ( x < src->width - 1 )
			{
				double ftr = ftop * fright;
				valueSum[0] += s[4] * ftr;
				factorSum[0] += ftr;
				valueSum[1] += s[5] * ftr;
				factorSum[1] += ftr;
				valueSum[2] += s[6] * ftr;
				factorSum[2] += ftr;
				valueSum[3] += s[7] * ftr;
				factorSum[3] += ftr;
			}

			if( y < src->height - 1 )
			{
				uint8_t* sb = s + linesize;

				// Bottom Left
				double fbl = fbottom * fleft;
				valueSum[0] += sb[0] * fbl;
				factorSum[0] += fbl;
				valueSum[1] += sb[1] * fbl;
				factorSum[1] += fbl;
				valueSum[2] += sb[2] * fbl;
				factorSum[2] += fbl;
				valueSum[3] += sb[3] * fbl;
				factorSum[3] += fbl;

				// Bottom Right
				if ( x < src->width - 1 )
				{
					double fbr = fbottom * fright;
					valueSum[0] += sb[4] * fbr;
					factorSum[0] += fbr;
					valueSum[1] += sb[5] * fbr;
					factorSum[1] += fbr;
					valueSum[2] += sb[6] * fbr;
					factorSum[2] += fbr;
					valueSum[3] += sb[7] * fbr;
					factorSum[3] += fbr;
				}
			}

			d[0] = (uint8_t)round( valueSum[0] / factorSum[0] );
			d[1] = (uint8_t)round( valueSum[1] / factorSum[1] );
			d[2] = (uint8_t)round( valueSum[2] / factorSum[2] );
			d[3] = (uint8_t)round( valueSum[3] / factorSum[3] );
			d += 4;
		}
	}
	return 0;
}

/** Perform a bilinear scale from the rect inside the source to fill the destination
  *
  * \param src a pointer to the source image
  * \param dst a pointer to the destination image
  * \param rect the area of interest in the src to be scaled to fit the dst
  */

static void bilinear_scale_rgba( mlt_image src, mlt_image dst, mlt_rect rect )
{
	scale_sliced_desc desc;
	desc.src = src;
	desc.dst = dst;
	desc.rect = rect;

	// Crop out a section of the rect that has the same aspect ratio as the image
	double destAr = (double)src->width / (double)src->height;
	double sourceAr = rect.w / rect.h;
	if ( sourceAr > destAr )
	{
		// Crop sides to fit height
		desc.rect.w = rect.w * destAr / sourceAr;
		desc.rect.x = rect.x + (rect.w - desc.rect.w) / 2.0;
	}
	else if ( destAr > sourceAr )
	{
		// Crop top and bottom to fit width.
		desc.rect.h = rect.h * sourceAr / destAr;
		desc.rect.y = rect.y + (rect.h - desc.rect.h) / 2.0;
	}
	mlt_slices_run_normal(0, scale_sliced_proc, &desc);
}


/** Copy pixels from source to destination
  *
  * \param src a pointer to the source image
  * \param dst a pointer to the destination image
  * \param width the number of values in each row
  * \param rect the area of interest in the src to be copied to the dst
  */

static void blit_rect( mlt_image src, mlt_image dst, mlt_rect rect )
{
	int blitHeight = rect.h;
	int blitWidth = rect.w * 4;
	int linesize = src->width * 4;
	uint8_t* s = src->data + (int)rect.y * linesize + (int)rect.x * 4;
	uint8_t* d = dst->data + (int)rect.y * linesize + (int)rect.x * 4;
	while ( blitHeight-- )
	{
		memcpy( d, s, blitWidth );
		s += linesize;
		d += linesize;
	}
}

static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable )
{
	int error = 0;
	mlt_filter filter = (mlt_filter)mlt_frame_pop_service( frame );
	mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter );
	char* rect_str = mlt_properties_get( filter_properties, "rect" );
	if ( !rect_str )
	{
		mlt_log_warning( MLT_FILTER_SERVICE(filter), "rect property not set\n" );
		return mlt_frame_get_image( frame, image, format, width, height, writable );
	}
	mlt_profile profile = mlt_service_profile( MLT_FILTER_SERVICE( filter ) );
	mlt_position position = mlt_filter_get_position( filter, frame );
	mlt_position length = mlt_filter_get_length2( filter, frame );
	mlt_rect rect = mlt_properties_anim_get_rect( filter_properties, "rect", position, length );
	if ( strchr( rect_str, '%' ) )
	{
		rect.x *= profile->width;
		rect.w *= profile->width;
		rect.y *= profile->height;
		rect.h *= profile->height;
	}
	double scale = mlt_profile_scale_width( profile, *width );
	rect.x *= scale;
	rect.w *= scale;
	scale = mlt_profile_scale_height( profile, *height );
	rect.y *= scale;
	rect.h *= scale;
	rect = constrain_rect( rect, profile->width * scale, profile->height * scale );

	if ( rect.w < 1 || rect.h < 1 )
	{
		mlt_log_info( MLT_FILTER_SERVICE(filter), "rect invalid\n" );
		return mlt_frame_get_image( frame, image, format, width, height, writable );
	}

	*format = mlt_image_rgba;
	error = mlt_frame_get_image( frame, image, format, width, height, 0 );

	if (error) return error;
	if (rect.x <= 0 && rect.y <= 0 && rect.w >= *width && rect.h >= *height)
	{
		// Rect fills the image. Nothing to blur
		return error;
	}

	double blur = mlt_properties_anim_get_double( filter_properties, "blur", position, length );
	// Convert from percent to pixels.
	blur = blur * (double)profile->width * mlt_profile_scale_width( profile, *width ) / 100.0;
	blur = MAX(round(blur), 0);

	struct mlt_image_s src;
	mlt_image_set_values( &src, *image, *format, *width, *height );

	struct mlt_image_s dst;
	mlt_image_set_values( &dst, NULL, *format, *width, *height );
	mlt_image_alloc_data( &dst );

	bilinear_scale_rgba( &src, &dst, rect );
	if (blur != 0) {
		mlt_image_box_blur( &dst, blur, blur );
	}
	blit_rect( &src, &dst, rect );

	*image = dst.data;
	mlt_frame_set_image( frame, dst.data, 0, dst.release_data );

	return error;
}

static mlt_frame filter_process( mlt_filter filter, mlt_frame frame )
{
	mlt_frame_push_service( frame, filter );
	mlt_frame_push_get_image( frame, filter_get_image );
	return frame;
}

mlt_filter filter_pillar_echo_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
{
	mlt_filter filter = mlt_filter_new();

	if ( filter )
	{
		mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
		mlt_properties_set( properties, "rect", "0% 0% 10% 10%" );
		mlt_properties_set_double( properties, "blur", 4.0 );
		filter->process = filter_process;
	}
	else
	{
		mlt_log_error( NULL, "Filter pillar_echo initialization failed\n" );
	}
	return filter;
}
