#include "audio/aframe.h"
#include "video/mp_image.h"

#include "f_utils.h"
#include "filter_internal.h"

struct frame_duration_priv {
    struct mp_image *buffered;
};

static void frame_duration_process(struct mp_filter *f)
{
    struct frame_duration_priv *p = f->priv;

    if (!mp_pin_can_transfer_data(f->ppins[1], f->ppins[0]))
        return;

    struct mp_frame frame = mp_pin_out_read(f->ppins[0]);

    if (frame.type == MP_FRAME_EOF && p->buffered) {
        mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, p->buffered));
        p->buffered = NULL;
        // Pass through the actual EOF in the next iteration.
        mp_pin_out_repeat_eof(f->ppins[0]);
    } else if (frame.type == MP_FRAME_VIDEO) {
        struct mp_image *next = frame.data;
        if (p->buffered) {
            if (p->buffered->pts != MP_NOPTS_VALUE &&
                next->pts != MP_NOPTS_VALUE &&
                next->pts >= p->buffered->pts)
            {
                p->buffered->pkt_duration = next->pts - p->buffered->pts;
            }
            mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, p->buffered));
        } else {
            mp_pin_out_request_data(f->ppins[0]);
        }
        p->buffered = next;
    } else {
        mp_pin_in_write(f->ppins[1], frame);
    }
}

static void frame_duration_reset(struct mp_filter *f)
{
    struct frame_duration_priv *p = f->priv;

    mp_image_unrefp(&p->buffered);
}

static const struct mp_filter_info frame_duration_filter = {
    .name = "frame_duration",
    .priv_size = sizeof(struct frame_duration_priv),
    .process = frame_duration_process,
    .reset = frame_duration_reset,
    .destroy = frame_duration_reset,
};

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

    mp_filter_add_pin(f, MP_PIN_IN, "in");
    mp_filter_add_pin(f, MP_PIN_OUT, "out");

    return f;
}

void mp_chain_filters(struct mp_pin *in, struct mp_pin *out,
                      struct mp_filter **filters, int num_filters)
{
    for (int n = 0; n < num_filters; n++) {
        if (!filters[n])
            continue;
        assert(filters[n]->num_pins == 2);
        mp_pin_connect(filters[n]->pins[0], in);
        in = filters[n]->pins[1];
    }
    mp_pin_connect(out, in);
}

// Make it repeat process().
static void mark_progress(struct mp_subfilter *sub)
{
    // f == NULL is not really allowed, but at least don't crash.
    struct mp_filter *f = mp_pin_get_manual_connection(sub->in);
    if (f)
        mp_filter_internal_mark_progress(f);
}

bool mp_subfilter_read(struct mp_subfilter *sub)
{
    if (sub->filter) {
        if (mp_pin_can_transfer_data(sub->out, sub->filter->pins[1])) {
            struct mp_frame frame = mp_pin_out_read(sub->filter->pins[1]);
            if (sub->draining && frame.type == MP_FRAME_EOF) {
                sub->draining = false;
                TA_FREEP(&sub->filter);
                mark_progress(sub);
                return false;
            }
            mp_pin_in_write(sub->out, frame);
            return false;
        }
        if (sub->draining)
            return false;
    }

    struct mp_pin *out = sub->filter ? sub->filter->pins[0] : sub->out;

    if (sub->frame.type)
        return mp_pin_in_needs_data(out);

    if (!mp_pin_can_transfer_data(out, sub->in))
        return false;

    sub->frame = mp_pin_out_read(sub->in);
    return true;
}

void mp_subfilter_reset(struct mp_subfilter *sub)
{
    if (sub->filter && sub->draining)
        TA_FREEP(&sub->filter);
    sub->draining = false;
    mp_frame_unref(&sub->frame);
}

void mp_subfilter_continue(struct mp_subfilter *sub)
{
    struct mp_pin *out = sub->filter ? sub->filter->pins[0] : sub->out;
    // It was made sure earlier that the pin is writable, unless the filter
    // was newly created, or a previously existing filter (which was going to
    // accept input) was destroyed. In those cases, essentially restart
    // data flow.
    if (!mp_pin_in_needs_data(out)) {
        mark_progress(sub);
        return;
    }
    mp_pin_in_write(out, sub->frame);
    sub->frame = MP_NO_FRAME;
}

void mp_subfilter_destroy(struct mp_subfilter *sub)
{
    TA_FREEP(&sub->filter);
    sub->draining = false;
}

bool mp_subfilter_drain_destroy(struct mp_subfilter *sub)
{
    if (!sub->draining && sub->filter) {
        // We know the filter is writable (unless the user created a new filter
        // and immediately called this function, which is invalid).
        mp_pin_in_write(sub->filter->pins[0], MP_EOF_FRAME);
        sub->draining = true;
    }
    return !sub->filter;
}

static const struct mp_filter_info bidir_nop_filter = {
    .name = "nop",
};

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

    mp_filter_add_pin(f, MP_PIN_IN, "in");
    mp_filter_add_pin(f, MP_PIN_OUT, "out");

    mp_pin_connect(f->ppins[1], f->ppins[0]);

    return f;
}

static const struct mp_filter_info bidir_dummy_filter = {
    .name = "dummy",
};

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

    mp_filter_add_pin(f, MP_PIN_IN, "in");
    mp_filter_add_pin(f, MP_PIN_OUT, "out");

    return f;
}

struct fixed_aframe_size_priv {
    int samples;
    bool pad_silence;
    struct mp_aframe *in;
    struct mp_aframe *out;
    int out_written; // valid samples in out
    struct mp_aframe_pool *pool;
};

static void fixed_aframe_size_process(struct mp_filter *f)
{
    struct fixed_aframe_size_priv *p = f->priv;

    if (!mp_pin_in_needs_data(f->ppins[1]))
        return;

    if (p->in && !mp_aframe_get_size(p->in))
        TA_FREEP(&p->in);

    if (!p->in) {
        struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
        if (frame.type == MP_FRAME_EOF) {
            if (!p->out) {
                mp_pin_in_write(f->ppins[1], frame);
                return;
            }
            mp_pin_out_repeat_eof(f->ppins[0]);
        } else if (frame.type == MP_FRAME_AUDIO) {
            p->in = frame.data;
            if (p->out && !mp_aframe_config_equals(p->out, p->in)) {
                mp_pin_out_unread(f->ppins[0], frame);
                p->in = NULL;
            }
        } else if (frame.type) {
            MP_ERR(f, "unsupported frame type\n");
            mp_filter_internal_mark_failed(f);
            return;
        } else {
            return; // no new data yet
        }
    }

    if (p->in) {
        if (!p->out) {
            p->out = mp_aframe_create();
            mp_aframe_config_copy(p->out, p->in);
            mp_aframe_copy_attributes(p->out, p->in);
            if (mp_aframe_pool_allocate(p->pool, p->out, p->samples) < 0) {
                mp_filter_internal_mark_failed(f);
                return;
            }
            p->out_written = 0;
        }
        int in_samples = mp_aframe_get_size(p->in);
        int copy = MPMIN(in_samples, p->samples - p->out_written);
        if (!mp_aframe_copy_samples(p->out, p->out_written, p->in, 0, copy))
            assert(0);
        mp_aframe_skip_samples(p->in, copy);
        p->out_written += copy;
    }

    // p->in not set means draining for EOF or format change
    if ((!p->in && p->out_written) || p->out_written == p->samples) {
        int missing = p->samples - p->out_written;
        assert(missing >= 0);
        if (missing) {
            mp_aframe_set_silence(p->out, p->out_written, missing);
            if (!p->pad_silence)
                mp_aframe_set_size(p->out, p->out_written);
        }
        mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, p->out));
        p->out = NULL;
        p->out_written = 0;
    } else {
        mp_pin_out_request_data_next(f->ppins[0]);
    }
}

static void fixed_aframe_size_reset(struct mp_filter *f)
{
    struct fixed_aframe_size_priv *p = f->priv;

    TA_FREEP(&p->in);
    TA_FREEP(&p->out);
    p->out_written = 0;
}

static const struct mp_filter_info fixed_aframe_size_filter = {
    .name = "fixed_aframe_size",
    .priv_size = sizeof(struct fixed_aframe_size_priv),
    .process = fixed_aframe_size_process,
    .reset = fixed_aframe_size_reset,
    .destroy = fixed_aframe_size_reset,
};

struct mp_filter *mp_fixed_aframe_size_create(struct mp_filter *parent,
                                              int samples, bool pad_silence)
{
    if (samples < 1)
        return NULL;

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

    mp_filter_add_pin(f, MP_PIN_IN, "in");
    mp_filter_add_pin(f, MP_PIN_OUT, "out");

    struct fixed_aframe_size_priv *p = f->priv;
    p->samples = samples;
    p->pad_silence = pad_silence;
    p->pool = mp_aframe_pool_create(p);

    return f;
}
