/******************************************************************************
    Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>

    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, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include "../util/bmem.h"
#include "video-scaler.h"

#include <libswscale/swscale.h>

struct video_scaler {
	struct SwsContext *swscale;
	int src_height;
};

static inline enum AVPixelFormat get_ffmpeg_video_format(
		enum video_format format)
{
	switch (format) {
	case VIDEO_FORMAT_NONE: return AV_PIX_FMT_NONE;
	case VIDEO_FORMAT_I420: return AV_PIX_FMT_YUV420P;
	case VIDEO_FORMAT_NV12: return AV_PIX_FMT_NV12;
	case VIDEO_FORMAT_YVYU: return AV_PIX_FMT_NONE;
	case VIDEO_FORMAT_YUY2: return AV_PIX_FMT_YUYV422;
	case VIDEO_FORMAT_UYVY: return AV_PIX_FMT_UYVY422;
	case VIDEO_FORMAT_RGBA: return AV_PIX_FMT_RGBA;
	case VIDEO_FORMAT_BGRA: return AV_PIX_FMT_BGRA;
	case VIDEO_FORMAT_BGRX: return AV_PIX_FMT_BGRA;
	case VIDEO_FORMAT_Y800: return AV_PIX_FMT_GRAY8;
	case VIDEO_FORMAT_I444: return AV_PIX_FMT_YUV444P;
	}

	return AV_PIX_FMT_NONE;
}

static inline int get_ffmpeg_scale_type(enum video_scale_type type)
{
	switch (type) {
	case VIDEO_SCALE_DEFAULT:       return SWS_FAST_BILINEAR;
	case VIDEO_SCALE_POINT:         return SWS_POINT;
	case VIDEO_SCALE_FAST_BILINEAR: return SWS_FAST_BILINEAR;
	case VIDEO_SCALE_BILINEAR:      return SWS_BILINEAR | SWS_AREA;
	case VIDEO_SCALE_BICUBIC:       return SWS_BICUBIC;
	}

	return SWS_POINT;
}

static inline const int *get_ffmpeg_coeffs(enum video_colorspace cs)
{
	switch (cs) {
	case VIDEO_CS_DEFAULT: return sws_getCoefficients(SWS_CS_ITU601);
	case VIDEO_CS_601:     return sws_getCoefficients(SWS_CS_ITU601);
	case VIDEO_CS_709:     return sws_getCoefficients(SWS_CS_ITU709);
	}

	return sws_getCoefficients(SWS_CS_ITU601);
}

static inline int get_ffmpeg_range_type(enum video_range_type type)
{
	switch (type) {
	case VIDEO_RANGE_DEFAULT: return 0;
	case VIDEO_RANGE_PARTIAL: return 0;
	case VIDEO_RANGE_FULL:    return 1;
	}

	return 0;
}

#define FIXED_1_0 (1<<16)

int video_scaler_create(video_scaler_t **scaler_out,
		const struct video_scale_info *dst,
		const struct video_scale_info *src,
		enum video_scale_type type)
{
	enum AVPixelFormat format_src = get_ffmpeg_video_format(src->format);
	enum AVPixelFormat format_dst = get_ffmpeg_video_format(dst->format);
	int                scale_type = get_ffmpeg_scale_type(type);
	const int          *coeff_src = get_ffmpeg_coeffs(src->colorspace);
	const int          *coeff_dst = get_ffmpeg_coeffs(dst->colorspace);
	int                range_src  = get_ffmpeg_range_type(src->range);
	int                range_dst  = get_ffmpeg_range_type(dst->range);
	struct video_scaler *scaler;
	int ret;

	if (!scaler_out)
		return VIDEO_SCALER_FAILED;

	if (format_src == AV_PIX_FMT_NONE ||
	    format_dst == AV_PIX_FMT_NONE)
		return VIDEO_SCALER_BAD_CONVERSION;

	scaler = bzalloc(sizeof(struct video_scaler));
	scaler->src_height = src->height;

	scaler->swscale = sws_getCachedContext(NULL,
			src->width, src->height, format_src,
			dst->width, dst->height, format_dst,
			scale_type, NULL, NULL, NULL);
	if (!scaler->swscale) {
		blog(LOG_ERROR, "video_scaler_create: Could not create "
		                "swscale");
		goto fail;
	}

	ret = sws_setColorspaceDetails(scaler->swscale,
			coeff_src, range_src,
			coeff_dst, range_dst,
			0, FIXED_1_0, FIXED_1_0);
	if (ret < 0) {
		blog(LOG_DEBUG, "video_scaler_create: "
		                "sws_setColorspaceDetails failed, ignoring");
	}

	*scaler_out = scaler;
	return VIDEO_SCALER_SUCCESS;

fail:
	video_scaler_destroy(scaler);
	return VIDEO_SCALER_FAILED;
}

void video_scaler_destroy(video_scaler_t *scaler)
{
	if (scaler) {
		sws_freeContext(scaler->swscale);
		bfree(scaler);
	}
}

bool video_scaler_scale(video_scaler_t *scaler,
		uint8_t *output[], const uint32_t out_linesize[],
		const uint8_t *const input[], const uint32_t in_linesize[])
{
	if (!scaler)
		return false;

	int ret = sws_scale(scaler->swscale,
			input, (const int *)in_linesize,
			0, scaler->src_height,
			output, (const int *)out_linesize);
	if (ret <= 0) {
		blog(LOG_ERROR, "video_scaler_scale: sws_scale failed: %d",
				ret);
		return false;
	}

	return true;
}