/*
  image-generator.cpp  -  generate animated test video frames
   Copyright (C)
     2025,            Hermann Vosseler <Ichthyostega@web.de>
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU GPL version 2+ See the LICENSE file for details.
* ************************************************************************/
#ifndef IMAGE_GENERATOR_H
#define IMAGE_GENERATOR_H
#include "commons.hpp"
#include <cstddef>
#include <array>
using uint = unsigned int;
using std::byte;
template<uint W, uint H>
class ImageGenerator
  {
  public:
    using Row = std::array<Trip, W>;
    using Img = std::array<Row,  H>;
    static_assert (sizeof(Img) == W * H * 3);
    using PackedRGB = std::array<Trip, W*H>;
    ImageGenerator(uint fps)
      : fps_{fps}
      , frameNr_{0}
      , img_{Row{Trip{}}}
      { };
    PackedRGB const&
    current()  const
      {
        return reinterpret_cast<PackedRGB const&> (img_);
      }
    /**
     * generate the next frame of the animation.
     * @return reference to the buffer with RGB888 data.
     */
    PackedRGB const&
    buildNext()
      {
        if (frameNr_ == 0)
          initGen();
        animatePos();
        attenuate();
        drawBall();
        ++frameNr_;
        return current();
      }
    uint getFrameNr()  const { return frameNr_; }
    uint getFps()      const { return fps_;     }
  private:
    uint fps_;
    uint frameNr_;
    Img img_;
    void initGen();
    void animatePos();
    void attenuate();
    void drawBall();
    constexpr static auto BLACK = gray (0);
    constexpr static auto WHITE = gray (0xFF);
    constexpr static auto GRAY1 = 0.25 * WHITE;
    constexpr static auto GRAY2 = 0.50 * WHITE;
    constexpr static auto GRAY3 = 0.75 * WHITE;
    constexpr static auto LT_YELLOW = trip (0xFF, 0xFF, 0xE0);
    constexpr static auto DARK_BLUE = trip (0x10, 0   , 0x50);
    constexpr static auto BALL = std::array{BLACK, GRAY3, WHITE, GRAY3, BLACK
                                           ,GRAY3, WHITE, WHITE, WHITE, GRAY3
                                           ,WHITE, WHITE, WHITE, WHITE, WHITE
                                           ,GRAY3, WHITE, WHITE, WHITE, GRAY3
                                           ,BLACK, GRAY3, WHITE, GRAY3, BLACK
                                           };
    constexpr static int BALL_SIZ = std::ceil (std::sqrt (BALL.size()));
    Vec2 p1_, p2_, v1_, v2_;
    double a1_{0}, a2_{0};
    void maybeBounce (Vec2&, Vec2&);
  };
  template<uint W, uint H>
  void
  ImageGenerator<W,H>::initGen()
    {
      std::srand (std::time (nullptr));
      p1_ = {0,0};
      v1_ = { 1 + rand() % 5,  1 + rand() % 5};
      p2_ = {rand() % int(W), rand() % int(H)};
      v2_ = {-1,-1};
      // initially fill background with colour bar pattern
      byte ON {0xE0};
      byte OFF{0};
      // classic NTSC colour bars  --R---G---B--
      std::array<Trip, 7> bars = {{{ ON, ON, ON}
                                  ,{ ON, ON,OFF}
                                  ,{OFF, ON, ON}
                                  ,{OFF, ON,OFF}
                                  ,{ ON,OFF, ON}
                                  ,{ ON,OFF,OFF}
                                  ,{OFF,OFF, ON}
                                 }};
      // create a colour strip pattern in the first row...
      for (uint x = 0; x < W; ++x)
        {  //  quantise into 7 columns
          uint col = x  * 7/W;
          img_[0][x] = bars[col];
        }
       // fill remaining rows alternating
      for (uint y = 1; y < H; ++y)
        if ((y/25) % 2 == 0)
          img_[y] = img_[0];
    }
  template<uint W, uint H>
  void
  ImageGenerator<W,H>::animatePos()
    {
      p1_ += v1_;
      maybeBounce (p1_, v1_);
      p2_ += v2_;
      maybeBounce (p2_, v2_);
      auto fade = [this](double& val, double target, uint dur)
                        {
                          if (val == target)
                            return;
                          val = std::min (target, val + target/(dur*fps_));
                        };
      fade (a1_, 0.05, 9);
      fade (a2_, 0.1,  2);
    }
  template<uint W, uint H>
  void
  ImageGenerator<W,H>::attenuate()
    {
      double dim{H*W};
      for (int row=0; row < int(H); ++row)
        for (int col=0; col < int(W); ++col)
          {
            Vec2 point{col,row};
            auto vicinity = [&](Vec2& p, int s)
                              {
                                int range = (p - point).norm();
                                return 1.0 /(1 + s/dim * range);
                              };
            Trip& px{img_[row][col]};
            decay (px, a1_,      (1.0 * vicinity (p2_, 1)) * DARK_BLUE);
            decay (px, a2_, px + (0.5 * vicinity (p1_,13)) * LT_YELLOW);
          }
    }
  template<uint W, uint H>
  void
  ImageGenerator<W,H>::drawBall()
    {
      for (uint row=0; row < BALL_SIZ; ++row)
        for (uint col=0; col < BALL_SIZ; ++col)
          img_[p1_.y+row][p1_.x+col] += BALL[row*BALL_SIZ + col];
    }
  template<uint W, uint H>
  void
  ImageGenerator<W,H>::maybeBounce (Vec2& p, Vec2& v)
    {
      auto randomise = [](int& val)
                          {
                            int offset{rand() % 7 - 3};
                            val += offset;
                            if (abs(val > 3) and val*offset > 0)
                              val /= 2; // damp excess outward trend
                          };
      int limX = W-BALL_SIZ;
      int limY = H-BALL_SIZ;
      if (p.x <= 0)
        {
          p.x *= -1;
          v.x *= -1;
          randomise (v.y);
        }
      else
      if (p.x >= limX)
        {
          p.x  = limX - (p.x-limX);
          v.x *= -1;
          randomise (v.y);
        }
      if (p.y <= 0)
        {
          p.y *= -1;
          v.y *= -1;
          randomise (v.x);
        }
      else
      if (p.y >= limY)
        {
          p.y  = limY - (p.y-limY);
          v.y *= -1;
          randomise (v.x);
        }
    }
#endif /*IMAGE_GENERATOR_H*/