/*
 * filter_avdeinterlace.c -- deinterlace filter
 * Copyright (C) 2003-2017 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 <stdlib.h>
#include <string.h>

// ffmpeg Header files
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>

#ifdef USE_MMX
#include "mmx.h"
#else
#define MAX_NEG_CROP 1024
static uint8_t ff_cropTbl[256 + 2 * MAX_NEG_CROP] = {
    0,
};
#endif

#ifdef USE_MMX
#define DEINT_INPLACE_LINE_LUM \
    movd_m2r(lum_m4[0], mm0); \
    movd_m2r(lum_m3[0], mm1); \
    movd_m2r(lum_m2[0], mm2); \
    movd_m2r(lum_m1[0], mm3); \
    movd_m2r(lum[0], mm4); \
    punpcklbw_r2r(mm7, mm0); \
    movd_r2m(mm2, lum_m4[0]); \
    punpcklbw_r2r(mm7, mm1); \
    punpcklbw_r2r(mm7, mm2); \
    punpcklbw_r2r(mm7, mm3); \
    punpcklbw_r2r(mm7, mm4); \
    paddw_r2r(mm3, mm1); \
    psllw_i2r(1, mm2); \
    paddw_r2r(mm4, mm0); \
    psllw_i2r(2, mm1); \
    paddw_r2r(mm6, mm2); \
    paddw_r2r(mm2, mm1); \
    psubusw_r2r(mm0, mm1); \
    psrlw_i2r(3, mm1); \
    packuswb_r2r(mm7, mm1); \
    movd_r2m(mm1, lum_m2[0]);

#define DEINT_LINE_LUM \
    movd_m2r(lum_m4[0], mm0); \
    movd_m2r(lum_m3[0], mm1); \
    movd_m2r(lum_m2[0], mm2); \
    movd_m2r(lum_m1[0], mm3); \
    movd_m2r(lum[0], mm4); \
    punpcklbw_r2r(mm7, mm0); \
    punpcklbw_r2r(mm7, mm1); \
    punpcklbw_r2r(mm7, mm2); \
    punpcklbw_r2r(mm7, mm3); \
    punpcklbw_r2r(mm7, mm4); \
    paddw_r2r(mm3, mm1); \
    psllw_i2r(1, mm2); \
    paddw_r2r(mm4, mm0); \
    psllw_i2r(2, mm1); \
    paddw_r2r(mm6, mm2); \
    paddw_r2r(mm2, mm1); \
    psubusw_r2r(mm0, mm1); \
    psrlw_i2r(3, mm1); \
    packuswb_r2r(mm7, mm1); \
    movd_r2m(mm1, dst[0]);
#endif

/* filter parameters: [-1 4 2 4 -1] // 8 */
static inline void deinterlace_line(uint8_t *dst,
                                    const uint8_t *lum_m4,
                                    const uint8_t *lum_m3,
                                    const uint8_t *lum_m2,
                                    const uint8_t *lum_m1,
                                    const uint8_t *lum,
                                    int size)
{
#ifndef USE_MMX
    uint8_t *cm = ff_cropTbl + MAX_NEG_CROP;
    int sum;

    for (; size > 0; size--) {
        sum = -lum_m4[0];
        sum += lum_m3[0] << 2;
        sum += lum_m2[0] << 1;
        sum += lum_m1[0] << 2;
        sum += -lum[0];
        dst[0] = cm[(sum + 4) >> 3];
        lum_m4++;
        lum_m3++;
        lum_m2++;
        lum_m1++;
        lum++;
        dst++;
    }
#else

    {
        mmx_t rounder;
        rounder.uw[0] = 4;
        rounder.uw[1] = 4;
        rounder.uw[2] = 4;
        rounder.uw[3] = 4;
        pxor_r2r(mm7, mm7);
        movq_m2r(rounder, mm6);
    }
    for (; size > 3; size -= 4) {
        DEINT_LINE_LUM
        lum_m4 += 4;
        lum_m3 += 4;
        lum_m2 += 4;
        lum_m1 += 4;
        lum += 4;
        dst += 4;
    }
#endif
}
static inline void deinterlace_line_inplace(
    uint8_t *lum_m4, uint8_t *lum_m3, uint8_t *lum_m2, uint8_t *lum_m1, uint8_t *lum, int size)
{
#ifndef USE_MMX
    uint8_t *cm = ff_cropTbl + MAX_NEG_CROP;
    int sum;

    for (; size > 0; size--) {
        sum = -lum_m4[0];
        sum += lum_m3[0] << 2;
        sum += lum_m2[0] << 1;
        lum_m4[0] = lum_m2[0];
        sum += lum_m1[0] << 2;
        sum += -lum[0];
        lum_m2[0] = cm[(sum + 4) >> 3];
        lum_m4++;
        lum_m3++;
        lum_m2++;
        lum_m1++;
        lum++;
    }
#else

    {
        mmx_t rounder;
        rounder.uw[0] = 4;
        rounder.uw[1] = 4;
        rounder.uw[2] = 4;
        rounder.uw[3] = 4;
        pxor_r2r(mm7, mm7);
        movq_m2r(rounder, mm6);
    }
    for (; size > 3; size -= 4) {
        DEINT_INPLACE_LINE_LUM
        lum_m4 += 4;
        lum_m3 += 4;
        lum_m2 += 4;
        lum_m1 += 4;
        lum += 4;
    }
#endif
}

/* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The
   top field is copied as is, but the bottom field is deinterlaced
   against the top field. */
static inline void deinterlace_bottom_field(
    uint8_t *dst, int dst_wrap, const uint8_t *src1, int src_wrap, int width, int height)
{
    const uint8_t *src_m2, *src_m1, *src_0, *src_p1, *src_p2;
    int y;

    src_m2 = src1;
    src_m1 = src1;
    src_0 = &src_m1[src_wrap];
    src_p1 = &src_0[src_wrap];
    src_p2 = &src_p1[src_wrap];
    for (y = 0; y < (height - 2); y += 2) {
        memcpy(dst, src_m1, width);
        dst += dst_wrap;
        deinterlace_line(dst, src_m2, src_m1, src_0, src_p1, src_p2, width);
        src_m2 = src_0;
        src_m1 = src_p1;
        src_0 = src_p2;
        src_p1 += 2 * src_wrap;
        src_p2 += 2 * src_wrap;
        dst += dst_wrap;
    }
    memcpy(dst, src_m1, width);
    dst += dst_wrap;
    /* do last line */
    deinterlace_line(dst, src_m2, src_m1, src_0, src_0, src_0, width);
}

static inline void deinterlace_bottom_field_inplace(uint8_t *src1,
                                                    int src_wrap,
                                                    int width,
                                                    int height)
{
    uint8_t *src_m1, *src_0, *src_p1, *src_p2;
    int y;
    uint8_t *buf;
    buf = (uint8_t *) av_malloc(width);

    src_m1 = src1;
    memcpy(buf, src_m1, width);
    src_0 = &src_m1[src_wrap];
    src_p1 = &src_0[src_wrap];
    src_p2 = &src_p1[src_wrap];
    for (y = 0; y < (height - 2); y += 2) {
        deinterlace_line_inplace(buf, src_m1, src_0, src_p1, src_p2, width);
        src_m1 = src_p1;
        src_0 = src_p2;
        src_p1 += 2 * src_wrap;
        src_p2 += 2 * src_wrap;
    }
    /* do last line */
    deinterlace_line_inplace(buf, src_m1, src_0, src_0, src_0, width);
    av_free(buf);
}

/* deinterlace - if not supported return -1 */
static int mlt_avpicture_deinterlace(uint8_t *dst_data[4],
                                     int dst_stride[4],
                                     uint8_t *src_data[4],
                                     int src_stride[4],
                                     int pix_fmt,
                                     int width,
                                     int height)
{
    int i;

    if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUV422P
        && pix_fmt != AV_PIX_FMT_YUYV422 && pix_fmt != AV_PIX_FMT_YUV444P
        && pix_fmt != AV_PIX_FMT_YUV411P)
        return -1;
    if ((width & 3) != 0 || (height & 3) != 0)
        return -1;

    if (pix_fmt != AV_PIX_FMT_YUYV422) {
        for (i = 0; i < 3; i++) {
            if (i == 1) {
                switch (pix_fmt) {
                case AV_PIX_FMT_YUV420P:
                    width >>= 1;
                    height >>= 1;
                    break;
                case AV_PIX_FMT_YUV422P:
                    width >>= 1;
                    break;
                case AV_PIX_FMT_YUV411P:
                    width >>= 2;
                    break;
                default:
                    break;
                }
            }
            if (src_data[0] == dst_data[0]) {
                deinterlace_bottom_field_inplace(dst_data[i], dst_stride[i], width, height);
            } else {
                deinterlace_bottom_field(dst_data[i],
                                         dst_stride[i],
                                         src_data[i],
                                         src_stride[i],
                                         width,
                                         height);
            }
        }
    } else {
        if (src_data[0] == dst_data[0]) {
            deinterlace_bottom_field_inplace(dst_data[0], dst_stride[0], width << 1, height);
        } else {
            deinterlace_bottom_field(dst_data[0],
                                     dst_stride[0],
                                     src_data[0],
                                     src_stride[0],
                                     width << 1,
                                     height);
        }
    }

#ifdef USE_MMX
    emms();
#endif
    return 0;
}

/** 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;
    int deinterlace = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive");

    // Determine if we need a writable version or not
    if (deinterlace && !writable)
        writable = !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "progressive");

    // Get the input image
    *format = mlt_image_yuv422;
    error = mlt_frame_get_image(frame, image, format, width, height, 1);

    // Check that we want progressive and we aren't already progressive
    if (deinterlace && *format == mlt_image_yuv422 && *image != NULL
        && !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "progressive")) {
        // Create a picture
        uint8_t *image_data[4];
        int strides[4];

        // Fill the picture
        av_image_fill_arrays(image_data, strides, *image, AV_PIX_FMT_YUYV422, *width, *height, 1);
        mlt_log_timings_begin();
        mlt_avpicture_deinterlace(image_data,
                                  strides,
                                  image_data,
                                  strides,
                                  AV_PIX_FMT_YUYV422,
                                  *width,
                                  *height);
        mlt_log_timings_end(NULL, "mlt_avpicture_deinterlace");

        // Make sure that others know the frame is deinterlaced
        mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "progressive", 1);
    }

    return error;
}

/** Deinterlace filter processing - this should be lazy evaluation here...
*/

static mlt_frame deinterlace_process(mlt_filter filter, mlt_frame frame)
{
    // 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_avdeinterlace_init(void *arg)
{
#ifndef USE_MMX
    if (ff_cropTbl[MAX_NEG_CROP + 1] == 0) {
        int i;
        for (i = 0; i < 256; i++)
            ff_cropTbl[i + MAX_NEG_CROP] = i;
        for (i = 0; i < MAX_NEG_CROP; i++) {
            ff_cropTbl[i] = 0;
            ff_cropTbl[i + MAX_NEG_CROP + 256] = 255;
        }
    }
#endif
    mlt_filter filter = mlt_filter_new();
    if (filter != NULL)
        filter->process = deinterlace_process;
    return filter;
}
