/**
 *  Copyright (C) 2020 Savoir-faire Linux Inc.
 *
 *  Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 3 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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#pragma once
extern "C" {
#include <libavutil/avutil.h>
#include <libavutil/frame.h>
#include <libavutil/pixfmt.h>
#include <libswscale/swscale.h>
}

//STL
#include <memory>
#include <functional>

using FrameUniquePtr = std::unique_ptr<AVFrame, void(*)(AVFrame*)>;

class FrameScaler {
public:
    FrameScaler() : ctx_(nullptr), mode_(SWS_FAST_BILINEAR) {}

    /**
     * @brief scaleConvert
     * Scales an av frame accoding to the desired width height/height
     * Converts the frame to another format if the desiredFromat is different from the input PixelFormat
     * @param input
     * @param desiredWidth
     * @param desiredHeight
     * @param desiredFormat
     * @return
     */
    FrameUniquePtr
    scaleConvert (const AVFrame* input, const size_t desiredWidth, const size_t desiredHeight,
                                const AVPixelFormat desiredFormat)
    {
        FrameUniquePtr output{av_frame_alloc(), [](AVFrame* frame){ if(frame) {av_frame_free(&frame);} }};
        if(input) {
            output->width = static_cast<int>(desiredWidth);
            output->height = static_cast<int>(desiredHeight);
            output->format = static_cast<int>(desiredFormat);

            auto output_frame = output.get();

            if (av_frame_get_buffer(output_frame, 0))
                throw std::bad_alloc();

            ctx_ = sws_getCachedContext(ctx_,
                                        input->width,
                                        input->height,
                                        static_cast<AVPixelFormat>(input->format),
                                        output_frame->width,
                                        output_frame->height,
                                        static_cast<AVPixelFormat>(output_frame->format),
                                        mode_,
                                        nullptr, nullptr, nullptr);
            if (!ctx_) {
                throw std::bad_alloc();
            }

            sws_scale(ctx_, input->data, input->linesize, 0,
                      input->height, output_frame->data,
                      output_frame->linesize);
        }

        return output;
    }

    /**
     * @brief convertFormat
     * @param input
     * @param pix
     * @return
     */
    FrameUniquePtr
    convertFormat (const AVFrame* input, AVPixelFormat pix) {
        return input?scaleConvert(input,static_cast<size_t>(input->width),static_cast<size_t>(input->height), pix):
                     std::unique_ptr<AVFrame, void(*)(AVFrame*)>{nullptr, [](AVFrame* frame){(void)frame;}};
    }

    /**
     * @brief moveFrom
     * @param dst
     * @param src
     */
    void
    moveFrom (AVFrame* dst,  AVFrame* src) {
        if(dst && src) {
            av_frame_unref(dst);
            av_frame_move_ref(dst, src);
        }
    }

protected:
    SwsContext *ctx_;
    int mode_;
};
