/*
 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "rlottie.h"
#include "rlottie_capi.h"
#include "vdebug.h"

using namespace rlottie;

extern void lottie_init_impl();
extern void lottie_shutdown_impl();

extern "C" {
#include <string.h>
#include <stdarg.h>

struct Lottie_Animation_S
{
    std::unique_ptr<Animation>      mAnimation;
    std::future<Surface>            mRenderTask;
    uint32_t                       *mBufferRef;
    LOTMarkerList                  *mMarkerList;
};

static uint32_t _lottie_lib_ref_count = 0;

RLOTTIE_API void lottie_init(void)
{
    if (_lottie_lib_ref_count > 0) {
        _lottie_lib_ref_count++;
        return;
    }
    lottie_init_impl();

    _lottie_lib_ref_count = 1;
}

RLOTTIE_API void lottie_shutdown(void)
{
    if (_lottie_lib_ref_count <= 0) {
        // lottie_init() is not called before lottie_shutdown()
        // or multiple shutdown is getting called.
        return;
    }

    _lottie_lib_ref_count--;

    if (_lottie_lib_ref_count == 0) {
        lottie_shutdown_impl();
    }
}

RLOTTIE_API Lottie_Animation_S *lottie_animation_from_file(const char *path)
{
    if (auto animation = Animation::loadFromFile(path) ) {
        Lottie_Animation_S *handle = new Lottie_Animation_S();
        handle->mAnimation = std::move(animation);
        return handle;
    } else {
        return nullptr;
    }
}

RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, const char *key, const char *resourcePath)
{
    if (auto animation = Animation::loadFromData(data, key, resourcePath) ) {
        Lottie_Animation_S *handle = new Lottie_Animation_S();
        handle->mAnimation = std::move(animation);
        return handle;
    } else {
        return nullptr;
    }
}

RLOTTIE_API void lottie_animation_destroy(Lottie_Animation_S *animation)
{
    if (animation) {
        if (animation->mMarkerList) {
            for(size_t i = 0; i < animation->mMarkerList->size; i++) {
                if (animation->mMarkerList->ptr[i].name) free(animation->mMarkerList->ptr[i].name);
            }
            delete[] animation->mMarkerList->ptr;
            delete animation->mMarkerList;
        }

        if (animation->mRenderTask.valid()) {
            animation->mRenderTask.get();
        }
        animation->mAnimation = nullptr;
        delete animation;
    }
}

RLOTTIE_API void lottie_animation_get_size(const Lottie_Animation_S *animation, size_t *width, size_t *height)
{
   if (!animation) return;

   animation->mAnimation->size(*width, *height);
}

RLOTTIE_API double lottie_animation_get_duration(const Lottie_Animation_S *animation)
{
   if (!animation) return 0;

   return animation->mAnimation->duration();
}

RLOTTIE_API size_t lottie_animation_get_totalframe(const Lottie_Animation_S *animation)
{
   if (!animation) return 0;

   return animation->mAnimation->totalFrame();
}


RLOTTIE_API double lottie_animation_get_framerate(const Lottie_Animation_S *animation)
{
   if (!animation) return 0;

   return animation->mAnimation->frameRate();
}

RLOTTIE_API const LOTLayerNode * lottie_animation_render_tree(Lottie_Animation_S *animation, size_t frame_num, size_t width, size_t height)
{
    if (!animation) return nullptr;

    return animation->mAnimation->renderTree(frame_num, width, height);
}

RLOTTIE_API size_t
lottie_animation_get_frame_at_pos(const Lottie_Animation_S *animation, float pos)
{
    if (!animation) return 0;

    return animation->mAnimation->frameAtPos(pos);
}

RLOTTIE_API void
lottie_animation_render(Lottie_Animation_S *animation,
                        size_t frame_number,
                        uint32_t *buffer,
                        size_t width,
                        size_t height,
                        size_t bytes_per_line)
{
    if (!animation) return;

    rlottie::Surface surface(buffer, width, height, bytes_per_line);
    animation->mAnimation->renderSync(frame_number, surface);
}

RLOTTIE_API void
lottie_animation_render_async(Lottie_Animation_S *animation,
                              size_t frame_number,
                              uint32_t *buffer,
                              size_t width,
                              size_t height,
                              size_t bytes_per_line)
{
    if (!animation) return;

    rlottie::Surface surface(buffer, width, height, bytes_per_line);
    animation->mRenderTask = animation->mAnimation->render(frame_number, surface);
    animation->mBufferRef = buffer;
}

RLOTTIE_API uint32_t *
lottie_animation_render_flush(Lottie_Animation_S *animation)
{
    if (!animation) return nullptr;

    if (animation->mRenderTask.valid()) {
        animation->mRenderTask.get();
    }

    return animation->mBufferRef;
}

RLOTTIE_API void
lottie_animation_property_override(Lottie_Animation_S *animation,
                                   const Lottie_Animation_Property type,
                                   const char *keypath,
                                   ...)
{
    va_list prop;
    va_start(prop, keypath);
    const int arg_count = [type](){
                             switch (type) {
                              case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR:
                              case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR:
                                return 3;
                              case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY:
                              case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY:
                              case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH:
                              case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION:
                                return 1;
                              case LOTTIE_ANIMATION_PROPERTY_TR_POSITION:
                              case LOTTIE_ANIMATION_PROPERTY_TR_SCALE:
                                return 2;
                              default:
                                return 0;
                             }
                          }();
    double v[3] = {0};
    for (int i = 0; i < arg_count ; i++) {
      v[i] = va_arg(prop, double);
    }
    va_end(prop);

    switch(type) {
    case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR: {
        double r = v[0];
        double g = v[1];
        double b = v[2];
        if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break;
        animation->mAnimation->setValue<rlottie::Property::FillColor>(keypath, rlottie::Color(r, g, b));
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY: {
        double opacity = v[0];
        if (opacity > 100 || opacity < 0) break;
        animation->mAnimation->setValue<rlottie::Property::FillOpacity>(keypath, (float)opacity);
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR: {
        double r = v[0];
        double g = v[1];
        double b = v[2];
        if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break;
        animation->mAnimation->setValue<rlottie::Property::StrokeColor>(keypath, rlottie::Color(r, g, b));
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY: {
        double opacity = v[0];
        if (opacity > 100 || opacity < 0) break;
        animation->mAnimation->setValue<rlottie::Property::StrokeOpacity>(keypath, (float)opacity);
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH: {
        double width = v[0];
        if (width < 0) break;
        animation->mAnimation->setValue<rlottie::Property::StrokeWidth>(keypath, (float)width);
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_TR_POSITION: {
        double x = v[0];
        double y = v[1];
        animation->mAnimation->setValue<rlottie::Property::TrPosition>(keypath, rlottie::Point((float)x, (float)y));
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_TR_SCALE: {
        double w = v[0];
        double h = v[1];
        animation->mAnimation->setValue<rlottie::Property::TrScale>(keypath, rlottie::Size((float)w, (float)h));
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION: {
        double r = v[0];
        animation->mAnimation->setValue<rlottie::Property::TrRotation>(keypath, (float)r);
        break;
    }
    case LOTTIE_ANIMATION_PROPERTY_TR_ANCHOR:
    case LOTTIE_ANIMATION_PROPERTY_TR_OPACITY:
        //@TODO handle propery update.
        break;
    }
}

RLOTTIE_API const LOTMarkerList*
lottie_animation_get_markerlist(Lottie_Animation_S *animation)
{
   if (!animation) return nullptr;

   auto markers = animation->mAnimation->markers();
   if (markers.size() == 0) return nullptr;
   if (animation->mMarkerList) return (const LOTMarkerList*)animation->mMarkerList;

   animation->mMarkerList = new LOTMarkerList();
   animation->mMarkerList->size = markers.size();
   animation->mMarkerList->ptr = new LOTMarker[markers.size()]();

   for(size_t i = 0; i < markers.size(); i++) {
       animation->mMarkerList->ptr[i].name = strdup(std::get<0>(markers[i]).c_str());
       animation->mMarkerList->ptr[i].startframe= std::get<1>(markers[i]);
       animation->mMarkerList->ptr[i].endframe= std::get<2>(markers[i]);
   }
   return (const LOTMarkerList*)animation->mMarkerList;
}

RLOTTIE_API void
lottie_configure_model_cache_size(size_t cacheSize)
{
   rlottie::configureModelCacheSize(cacheSize);
}

}