#include <memory>
#include <vector>
#include <dirent.h>
#include <algorithm>
#include <chrono>
#include <iostream>
#include <cstring>

#include <rlottie.h>

static bool isJsonFile(const char *filename) {
  const char *dot = strrchr(filename, '.');
  if(!dot || dot == filename) return false;
  return !strcmp(dot + 1, "json");
}

static std::vector<std::string>
jsonFiles(const std::string &dirName)
{
    DIR *d;
    struct dirent *dir;
    std::vector<std::string> result;
    d = opendir(dirName.c_str());
    if (d) {
      while ((dir = readdir(d)) != NULL) {
        if (isJsonFile(dir->d_name))
          result.push_back(dirName + dir->d_name);
      }
      closedir(d);
    }

    std::sort(result.begin(), result.end(), [](auto & a, auto &b){return a < b;});

    return result;
}

class Renderer
{
public:
    explicit Renderer(const std::string& filename)
    {
        _animation = rlottie::Animation::loadFromFile(filename);
        _frames = _animation->totalFrame();
        _buffer = std::make_unique<uint32_t[]>(100 * 100);
        _surface = rlottie::Surface(_buffer.get(), 100, 100, 100 * 4);
    }
    void render()
    {
        if (_cur >= _frames) _cur = 0;
        _animation->renderSync(_cur++, _surface);
    }
    void renderAsync()
    {
        if (_cur >= _frames) _cur = 0;
        _future = _animation->render(_cur++, _surface);
    }
    void get()
    {
        if (_future.valid()) _future.get();
    }
private:
    std::unique_ptr<uint32_t[]>         _buffer;
    std::unique_ptr<rlottie::Animation> _animation;
    size_t                              _frames{0};
    size_t                              _cur{0};
    rlottie::Surface                   _surface;
    std::future<rlottie::Surface>      _future;
};

class PerfTest
{
public:
    explicit PerfTest(size_t resourceCount, size_t iterations):
        _resourceCount(resourceCount), _iterations(iterations)
    {
        _resourceList = jsonFiles(std::string(DEMO_DIR));
    }
    void test(bool async)
    {
        setup();
        std::cout<<" Test Started : .... \n";
        auto start = std::chrono::high_resolution_clock::now();
        benchmark(async);
        std::chrono::duration<double> secs = std::chrono::high_resolution_clock::now() - start;
        std::chrono::duration<double, std::milli> millisecs = secs;
        std::cout<< " Test Finished.\n";
        std::cout<< " \nPerformance Report: \n\n";
        std::cout<< " \t Resource Rendered per Frame : "<< _resourceCount <<"\n";
        std::cout<< " \t Render Buffer Size          : (100 X 100) \n";
        std::cout<< " \t Render Mode                 : "<< (async ? "Async" : "Sync")<<"\n";
        std::cout<< " \t Total Frames Rendered       : "<< _iterations<<"\n";
        std::cout<< " \t Total Render Time           : "<< secs.count()<<"sec\n";
        std::cout<< " \t Avrage Time per Resource    : "<< millisecs.count() / (_iterations * _resourceCount)<<"ms\n";
        std::cout<< " \t Avrage Time Per Frame       : "<< millisecs.count() / _iterations <<"ms\n";
        std::cout<< " \t FPS                         : "<< _iterations / secs.count() <<"fps\n\n";
    }
private:
    void setup()
    {
        for (auto i = 0u; i < _resourceCount; i++) {
            auto index = i % _resourceList.size();
            _renderers.push_back(std::make_unique<Renderer>(_resourceList[index]));
        }
    }

    void benchmark(bool async)
    {
        for (auto i = 0u; i < _iterations; i++) {
            if (async) {
                for (const auto &e : _renderers) e->renderAsync();
                for (const auto &e : _renderers) e->get();
            } else {
                for (const auto &e : _renderers) e->render();
            }
        }
    }

private:
    size_t  _resourceCount;
    size_t  _iterations;

    std::vector<std::string>                 _resourceList;
    std::vector<std::unique_ptr<Renderer>>   _renderers;
};

static int help()
{
    std::cout<<"\nUsage : ./perf [--sync] [-c] [resource count] [-i] [iteration count] \n";
    std::cout<<"\nExample : ./perf -c 50 -i 100 \n";
    std::cout<<"\n\t runs perf test for 100 iterations. renders 50 resource per iteration\n\n";
    return 0;
}
int
main(int argc, char ** argv)
{
    bool async = true;
    size_t resourceCount = 250;
    size_t iterations = 500;
    auto index = 0;

    while (index < argc) {
      const char* option = argv[index];
      index++;
      if (!strcmp(option,"--help") || !strcmp(option,"-h")) {
          return help();
      } else if (!strcmp(option,"--sync")) {
          async = false;
      } else if (!strcmp(option,"-c")) {
         resourceCount = (index < argc) ? atoi(argv[index]) : resourceCount;
         index++;
      } else if (!strcmp(option,"-i")) {
         iterations = (index < argc) ? atoi(argv[index]) : iterations;
         index++;
      }
   }

    PerfTest obj(resourceCount, iterations);
    obj.test(async);
    return 0;
}