#include <math.h>

#include "audio/aframe.h"
#include "audio/format.h"
#include "common/common.h"
#include "common/msg.h"
#include "options/m_config.h"
#include "options/options.h"
#include "video/filter/refqueue.h"
#include "video/mp_image.h"
#include "video/mp_image_pool.h"

#include "f_auto_filters.h"
#include "f_autoconvert.h"
#include "f_hwtransfer.h"
#include "f_swscale.h"
#include "f_utils.h"
#include "filter.h"
#include "filter_internal.h"
#include "user_filters.h"

struct deint_priv {
    struct mp_subfilter sub;
    int prev_imgfmt;
    bool deinterlace_active;
    struct m_config_cache *opts;
};

static void deint_process(struct mp_filter *f)
{
    struct deint_priv *p = f->priv;

    if (!mp_subfilter_read(&p->sub))
        return;

    struct mp_frame frame = p->sub.frame;

    if (mp_frame_is_signaling(frame)) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (frame.type != MP_FRAME_VIDEO) {
        MP_ERR(f, "video input required!\n");
        mp_filter_internal_mark_failed(f);
        return;
    }

    struct mp_image *img = frame.data;
    bool interlaced = img->fields & MP_IMGFIELD_INTERLACED;

    m_config_cache_update(p->opts);
    struct filter_opts *opts = p->opts->opts;
    bool should_deinterlace = (opts->deinterlace == -1 && interlaced) ||
                               opts->deinterlace == 1;

    if (!should_deinterlace)
        mp_subfilter_destroy(&p->sub);

    if (img->imgfmt == p->prev_imgfmt && p->deinterlace_active == should_deinterlace) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (!mp_subfilter_drain_destroy(&p->sub))
        return;

    mp_assert(!p->sub.filter);

    p->prev_imgfmt = img->imgfmt;
    p->deinterlace_active = should_deinterlace;
    if (!p->deinterlace_active) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    char *field_parity;
    switch (opts->field_parity) {
    case MP_FIELD_PARITY_TFF:
        field_parity = "tff";
        break;
    case MP_FIELD_PARITY_BFF:
        field_parity = "bff";
        break;
    default:
        field_parity = "auto";
    }

    bool has_filter = true;
    if (img->imgfmt == IMGFMT_VDPAU) {
        char *args[] = {"deint", "yes",
                        "parity", field_parity, NULL};
        p->sub.filter =
            mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args);
    } else if (img->imgfmt == IMGFMT_D3D11) {
        char *args[] = {"deint", "yes",
                        "parity", field_parity, NULL};
        p->sub.filter =
            mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", args);
    } else if (img->imgfmt == IMGFMT_CUDA) {
        char *args[] = {"mode", "send_field",
                        "parity", field_parity, NULL};
        p->sub.filter =
            mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_cuda", args);
    } else if (img->imgfmt == IMGFMT_VULKAN) {
        char *args[] = {"mode", "send_field",
                        "parity", field_parity, NULL};
        p->sub.filter =
            mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_vulkan", args);
    } else if (img->imgfmt == IMGFMT_VAAPI) {
        char *args[] = {"deint", "motion-adaptive",
                        "parity", field_parity, NULL};
        p->sub.filter =
            mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args);
    } else {
        has_filter = false;
    }

    if (!p->sub.filter) {
        if (has_filter)
            MP_ERR(f, "creating deinterlacer failed\n");

        struct mp_filter *subf = mp_bidir_dummy_filter_create(f);
        struct mp_filter *filters[2] = {0};

        struct mp_autoconvert *ac = mp_autoconvert_create(subf);
        if (ac) {
            filters[0] = ac->f;
            // We know vf_bwdif does not support hw inputs.
            mp_autoconvert_add_all_sw_imgfmts(ac);

            if (!mp_autoconvert_probe_input_video(ac, img)) {
                MP_ERR(f, "no deinterlace filter available for format %s\n",
                       mp_imgfmt_to_name(img->imgfmt));
                talloc_free(subf);
                mp_subfilter_continue(&p->sub);
                return;
            }
        }

        char *args[] = {"mode", "send_field",
                        "parity", field_parity, NULL};
        filters[1] =
            mp_create_user_filter(subf, MP_OUTPUT_CHAIN_VIDEO, "bwdif", args);

        mp_chain_filters(subf->ppins[0], subf->ppins[1], filters, 2);
        p->sub.filter = subf;
    }

    mp_subfilter_continue(&p->sub);
}

static void deint_reset(struct mp_filter *f)
{
    struct deint_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
}

static void deint_destroy(struct mp_filter *f)
{
    struct deint_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
    TA_FREEP(&p->sub.filter);
}

static bool deint_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
    struct deint_priv *p = f->priv;

    if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
        cmd->is_active = !!p->sub.filter;
        return true;
    }
    return false;
}

static const struct mp_filter_info deint_filter = {
    .name = "deint",
    .priv_size = sizeof(struct deint_priv),
    .command = deint_command,
    .process = deint_process,
    .reset = deint_reset,
    .destroy = deint_destroy,
};

bool mp_deint_active(struct mp_filter *f)
{
    struct deint_priv *p = f->priv;
    return p->deinterlace_active;
}

struct mp_filter *mp_deint_create(struct mp_filter *parent)
{
    struct mp_filter *f = mp_filter_create(parent, &deint_filter);
    if (!f)
        return NULL;

    struct deint_priv *p = f->priv;

    p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
    p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");

    p->opts = m_config_cache_alloc(f, f->global, &filter_conf);

    return f;
}

struct rotate_priv {
    struct mp_subfilter sub;
    int prev_rotate;
    int prev_imgfmt;
    int target_rotate;
};

static void rotate_process(struct mp_filter *f)
{
    struct rotate_priv *p = f->priv;

    if (!mp_subfilter_read(&p->sub))
        return;

    struct mp_frame frame = p->sub.frame;

    if (mp_frame_is_signaling(frame)) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (frame.type != MP_FRAME_VIDEO) {
        MP_ERR(f, "video input required!\n");
        return;
    }

    struct mp_image *img = frame.data;

    if (img->params.rotate == p->prev_rotate &&
        img->imgfmt == p->prev_imgfmt)
    {
        img->params.rotate = p->target_rotate;
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (!mp_subfilter_drain_destroy(&p->sub))
        return;

    mp_assert(!p->sub.filter);

    int rotate = p->prev_rotate = img->params.rotate;
    p->target_rotate = rotate;
    p->prev_imgfmt = img->imgfmt;

    struct mp_stream_info *info = mp_filter_find_stream_info(f);
    if (rotate == 0 || (info && info->rotate90 && !(rotate % 90))) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (!mp_sws_supports_input(img->imgfmt)) {
        MP_ERR(f, "Video rotation with this format not supported\n");
        mp_subfilter_continue(&p->sub);
        return;
    }

    double angle = rotate / 360.0 * M_PI * 2;
    char *args[] = {"angle", mp_tprintf(30, "%f", angle),
                    "ow", mp_tprintf(30, "rotw(%f)", angle),
                    "oh", mp_tprintf(30, "roth(%f)", angle),
                    NULL};
    p->sub.filter =
        mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "rotate", args);

    if (p->sub.filter) {
        MP_INFO(f, "Inserting rotation filter.\n");
        p->target_rotate = 0;
    } else {
        MP_ERR(f, "could not create rotation filter\n");
    }

    mp_subfilter_continue(&p->sub);
}

static void rotate_reset(struct mp_filter *f)
{
    struct rotate_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
}

static void rotate_destroy(struct mp_filter *f)
{
    struct rotate_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
    TA_FREEP(&p->sub.filter);
}

static bool rotate_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
    struct rotate_priv *p = f->priv;

    if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
        cmd->is_active = !!p->sub.filter;
        return true;
    }
    return false;
}

static const struct mp_filter_info rotate_filter = {
    .name = "autorotate",
    .priv_size = sizeof(struct rotate_priv),
    .command = rotate_command,
    .process = rotate_process,
    .reset = rotate_reset,
    .destroy = rotate_destroy,
};

struct mp_filter *mp_autorotate_create(struct mp_filter *parent)
{
    struct mp_filter *f = mp_filter_create(parent, &rotate_filter);
    if (!f)
        return NULL;

    struct rotate_priv *p = f->priv;
    p->prev_rotate = -1;

    p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
    p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");

    return f;
}

struct vflip_priv {
    struct mp_subfilter sub;
    int prev_vflip;
    int prev_imgfmt;
    int target_vflip;
};

static void vflip_process(struct mp_filter *f)
{
    struct vflip_priv *p = f->priv;

    if (!mp_subfilter_read(&p->sub))
        return;

    struct mp_frame frame = p->sub.frame;

    if (mp_frame_is_signaling(frame)) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (frame.type != MP_FRAME_VIDEO) {
        MP_ERR(f, "video input required!\n");
        return;
    }

    struct mp_image *img = frame.data;

    if (img->params.vflip == p->prev_vflip &&
        img->imgfmt == p->prev_imgfmt)
    {
        img->params.vflip = p->target_vflip;
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (!mp_subfilter_drain_destroy(&p->sub))
        return;

    mp_assert(!p->sub.filter);

    int vflip = p->prev_vflip = img->params.vflip;
    p->target_vflip = vflip;
    p->prev_imgfmt = img->imgfmt;

    struct mp_stream_info *info = mp_filter_find_stream_info(f);
    if (!vflip || (info && info->vflip)) {
        mp_subfilter_continue(&p->sub);
        return;
    }

    if (!mp_sws_supports_input(img->imgfmt)) {
        MP_ERR(f, "Video vflip with this format not supported\n");
        mp_subfilter_continue(&p->sub);
        return;
    }

    char *args[] = {NULL};

    p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vflip", args);

    if (p->sub.filter) {
        MP_INFO(f, "Inserting vflip filter.\n");
        p->target_vflip = 0;
    } else {
        MP_ERR(f, "could not create vflip filter\n");
    }

    mp_subfilter_continue(&p->sub);
}

static void vflip_reset(struct mp_filter *f)
{
    struct vflip_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
}

static void vflip_destroy(struct mp_filter *f)
{
    struct vflip_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
    TA_FREEP(&p->sub.filter);
}

static bool vflip_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
    struct vflip_priv *p = f->priv;

    if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
        cmd->is_active = !!p->sub.filter;
        return true;
    }
    return false;
}

static const struct mp_filter_info vflip_filter = {
    .name = "autovflip",
    .priv_size = sizeof(struct vflip_priv),
    .command = vflip_command,
    .process = vflip_process,
    .reset = vflip_reset,
    .destroy = vflip_destroy,
};

struct mp_filter *mp_autovflip_create(struct mp_filter *parent)
{
    struct mp_filter *f = mp_filter_create(parent, &vflip_filter);
    if (!f)
        return NULL;

    struct vflip_priv *p = f->priv;
    p->prev_vflip = -1;

    p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
    p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");

    return f;
}

struct aspeed_priv {
    struct mp_subfilter sub;
    double cur_speed, cur_speed_drop;
    int current_filter;
};

static void aspeed_process(struct mp_filter *f)
{
    struct aspeed_priv *p = f->priv;

    if (!mp_subfilter_read(&p->sub))
        return;

    if (!p->sub.filter)
        p->current_filter = 0;

    double speed = p->cur_speed * p->cur_speed_drop;

    int req_filter = 0;
    if (fabs(speed - 1.0) >= 1e-8) {
        req_filter = p->cur_speed_drop == 1.0 ? 1 : 2;
        if (p->sub.frame.type == MP_FRAME_AUDIO &&
            !af_fmt_is_pcm(mp_aframe_get_format(p->sub.frame.data)))
            req_filter = 2;
    }

    if (req_filter != p->current_filter) {
        if (p->sub.filter)
            MP_VERBOSE(f, "removing audio speed filter\n");
        if (!mp_subfilter_drain_destroy(&p->sub))
            return;

        if (req_filter) {
            if (req_filter == 1) {
                MP_VERBOSE(f, "adding scaletempo2\n");
                p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO,
                                                      "scaletempo2", NULL);
            } else if (req_filter == 2) {
                MP_VERBOSE(f, "adding drop\n");
                p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO,
                                                      "drop", NULL);
            }
            if (!p->sub.filter) {
                MP_ERR(f, "could not create filter\n");
                mp_subfilter_continue(&p->sub);
                return;
            }
            p->current_filter = req_filter;
        }
    }

    if (p->sub.filter) {
        struct mp_filter_command cmd = {
            .type = MP_FILTER_COMMAND_SET_SPEED,
            .speed = speed,
        };
        mp_filter_command(p->sub.filter, &cmd);
    }

    mp_subfilter_continue(&p->sub);
}

static bool aspeed_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
    struct aspeed_priv *p = f->priv;

    if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
        p->cur_speed = cmd->speed;
        return true;
    }

    if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_DROP) {
        p->cur_speed_drop = cmd->speed;
        return true;
    }

    if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
        cmd->is_active = !!p->sub.filter;
        return true;
    }

    return false;
}

static void aspeed_reset(struct mp_filter *f)
{
    struct aspeed_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
}

static void aspeed_destroy(struct mp_filter *f)
{
    struct aspeed_priv *p = f->priv;

    mp_subfilter_reset(&p->sub);
    TA_FREEP(&p->sub.filter);
}

static const struct mp_filter_info aspeed_filter = {
    .name = "autoaspeed",
    .priv_size = sizeof(struct aspeed_priv),
    .command = aspeed_command,
    .process = aspeed_process,
    .reset = aspeed_reset,
    .destroy = aspeed_destroy,
};

struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent)
{
    struct mp_filter *f = mp_filter_create(parent, &aspeed_filter);
    if (!f)
        return NULL;

    struct aspeed_priv *p = f->priv;
    p->cur_speed = 1.0;
    p->cur_speed_drop = 1.0;

    p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
    p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");

    return f;
}
