// ----
// ---- file   : render_sample.cpp
// ---- author : Bastian Spiegel <bs@tkscript.de>
// ---- legal  : (c) 2009-2019 by Bastian Spiegel. 
// ----          Distributed under terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL). See 
// ----          http://www.gnu.org/licenses/licenses.html#LGPL or COPYING for further information.
// ----
// ---- info   : 
// ----
// ---- created: 25Oct2009
// ---- changed: 29Apr2010, 11May2010, 25Feb2011, 04May2013, 07Mar2014, 18Dec2018, 15Jan2019
// ----          06May2019
// ----
// ----
// ----

#include "tksampleedit.h"

#include "../tkopengl/tkopengl_shared.h"


#define RS_DOTS   0
#define RS_LINES  1
#define RS_DELTA  2

#define IS_SH         0
#define IS_LINEAR     1
#define IS_SMOOTHSTEP 2
#define IS_AVG8       3

typedef sF32 (*get_sample_fxn_t)(sF32 *s, sUI maxSamples, sUI numCh, sF64 off);



static sF32 get_sample_sh(sF32 *s, sUI maxSamples, sUI numCh, sF64 off) {
   (void)maxSamples;

   sUI i = ((sUI) off) * numCh;

   if(i < maxSamples)
   {
      return s[i];
   }
   else
   {
      return 0.0f;
   }
}

static sF32 get_sample_linear(sF32 *s, sUI maxSamples, sUI numCh, sF64 off) {
   sUI i = ((sUI) off) * numCh;
   sF64 f = off - ((sUI)off);

   if(i < (maxSamples-numCh))
   {
      return sF32(s[i]*(1.0f-f) + s[i+numCh]*f);
   }
   else
   {
      return 0.0f;
   }
}

static sF32 get_sample_smoothstep(sF32 *s, sUI maxSamples, sUI numCh, sF64 off) {
   sUI i = ((sUI) off) * numCh;
   sF64 f = off - ((sUI)off);

   if(i < (maxSamples-numCh))
   {
      f = (f*f) * (3.0f - 2.0f*f);

      return sF32(s[i]*(1.0f-f) + s[i+numCh]*f);
   }
   else
   {
      return 0.0f;
   }
}

static sF32 get_sample_avg8(sF32 *s, sUI maxSamples, sUI numCh, sF64 off) {
   sUI i = ((sUI) off);
   sF64 sum = 0.0f;
   sSI x;

   for(x=0; x<8; x++)
   {
      sUI k = ((i + x) - 3) * numCh;

      if(k < maxSamples)
      {
         sum += s[k];
      }
   }
   return sF32(sum / 8.0);
}



static void render_sample_dots(sF32 *s, sUI _startOffset, sUI _numCh, sUI _numFrames, sUI _maxSamples, 
                               sF32 _w, sF32 _h, 
                               sF32 _min, sF32 _max,
                               get_sample_fxn_t _readFxn
                               )
{
   sF32 scly = (_h / (_max - _min));

   ////yac_host->printf("xxx render_sample_skip_sh #ch=%u #samples=%u w=%f h=%f\n", numCh, numFrames, _w, _h);

   tkopengl_shared->_glBegin(GL_POINTS);

   sF64 off = (sF32)_startOffset;
   sF64 spd = _numFrames / _w;
   sF64 maxFrames = (sF32) (_maxSamples / _numCh);
   
   sF32 cx = 0.0f;
   
   while(cx < _w)
   {
      cx += 1.0f;
      sF32 cy = _h - ( ((_readFxn)(s, _maxSamples, _numCh, off) - _min) * scly );

      tkopengl_shared->_glVertex2f(cx, cy);

      off += spd;

      if(off >= maxFrames)
      {
         off -= maxFrames;
      }
   }

   tkopengl_shared->_glEnd();
}

static void render_sample_lines(sF32 *s, sUI _startOffset, sUI _numCh, sUI _numFrames, sUI _maxSamples, 
                                sF32 _w, sF32 _h, 
                                sF32 _min, sF32 _max,
                                sUI _baseColor,
                                get_sample_fxn_t _readFxn
                                )
{
   sF32 scly = (_h / (_max - _min));

   ////yac_host->printf("xxx render_sample_lin #ch=%u #samples=%u w=%f h=%f\n", numCh, numFrames, _w, _h);

   sF64 off = (sF32)_startOffset;
   sF64 spd = _numFrames / _w;
   sSI num = (sSI) spd;
   sF64 maxFrames = (sF32) (_maxSamples / _numCh);

   // first sample
   sF32 lx = 0;
   sF32 ly = _h - ( (s[0] - _min) * scly );

   sF32 cx = 0.0;

   if(spd < 1.0)
   {
      // Read windowed sample
      tkopengl_shared->_glBegin(GL_LINES);

      // yac_host->printf("xxx draw samples 1\n");

      while(cx < _w)
      {
         tkopengl_shared->_glVertex2f(lx, ly);

         sF32 cy = _h - ( ((_readFxn)(s, _maxSamples, _numCh, off) - _min) * scly );

         tkopengl_shared->_glVertex2f(cx, cy);

         lx = cx;
         ly = cy;
         off += spd;

         if(off >= maxFrames)
         {
            off -= maxFrames;
         }

         cx += 1.0f;
      }

      tkopengl_shared->_glEnd();
   }
   else
   {
      // yac_host->printf("xxx draw samples 2\n");

      num++;
      if(1)
      {
#ifndef NO_SHADE
         // Shade
         if(num > 128) // 128
            num = 128;
#else
         num = 1;
#endif // NO_SHADE

         // // float alpha = ((255-num) / 255.0f) / num; // xxx hardcoded color..
         //float alpha = ((128-3*num) / (2*255.0f)); // xxx hardcoded color..
         float alpha = 1.0f / num;
         if(alpha < 0.02f)
            alpha=0.02f;
         // printf(" num=%d alpha=%f\n", num, alpha);
         float red   = ((_baseColor >> 16)&255) / 255.0f;  // 0x00 
         // float green = 0x33 / 255.0f;
         // float blue  = 0x75 / 255.0f;
         float green = ((_baseColor >>  8)&255) / 255.0f; // 0x11
         float blue  = ((_baseColor      )&255) / 255.0f; // 0x45

         if(alpha < 0.01f)
            alpha = 0.01f;

         tkopengl_shared->_glColor4f(red, green, blue, alpha);

         sF32 zeroy = _h - ( (0.0f - _min) * scly );

         tkopengl_shared->_glBegin(GL_LINES);

         while(cx < _w)
         {
            sF64 cOff = off - (spd*0.5);
            sSI k;
            sF64 cw = spd / num;

            for(k=0; k<num; k++)
            {
               sF32 cy = _h - ( ((_readFxn)(s, _maxSamples, _numCh, cOff) - _min) * scly );

               tkopengl_shared->_glVertex2f(cx, zeroy);
               tkopengl_shared->_glVertex2f(cx, cy);

               cOff += cw;

               if(cOff >= maxFrames)
               {
                  cOff -= maxFrames;
               }
            }

            off += spd;

            if(off >= maxFrames)
            {
               off -= maxFrames;
            }

            cx += 1.0f;
         }

         tkopengl_shared->_glEnd();
      }

   }
}

static void render_sample_delta(sF32 *s, sUI _startOffset, sUI _numCh, sUI _numFrames, sUI _maxSamples, 
                                sF32 _w, sF32 _h, sF32 _min, sF32 _max,
                                get_sample_fxn_t _readFxn
                                )
{
   // (todo) REMOVE

   sF32 scly = (_h / (_max - _min));

   ////yac_host->printf("xxx render_sample_delta #ch=%u #samples=%u w=%f h=%f\n", numCh, numFrames, _w, _h);

   sF64 off = (sF32)_startOffset;
   sF64 spd = _numFrames / _w;
   sF64 maxFrames = (sF32) (_maxSamples / _numCh);
      
   sF32 cx = 0.0f;
   sF32 lsmp = s[0];

   tkopengl_shared->_glBegin(GL_LINES);
      
   while(cx < _w)
   {
      cx += 1.0f;
      sF32 csmp = (_readFxn)(s, _maxSamples, _numCh, off);
      sF32 cy = _h - ( ((csmp - lsmp) - _min) * scly );

      tkopengl_shared->_glVertex2f(cx-1, cy);
      tkopengl_shared->_glVertex2f(cx, cy);

      lsmp = csmp;
      off += spd;

      if(off >= maxFrames)
      {
         off -= maxFrames;
      }
   }

   tkopengl_shared->_glEnd();
}



void YAC_CALL tksampleedit_render_sample(YAC_Object *_data, sUI _numCh, 
                                         sUI _frameOffset, sUI _numFrames, 
                                         sF32 _w, sF32 _h, 
                                         sF32 _min, sF32 _max,
                                         sSI _renderStyle,
                                         sSI _interpolStyle,
                                         sUI _baseColor/*lines*/
                                         )
{
   //
   // Render sample (range) at the given zoom level, using OpenGL.
   //

   ////yac_host->printf("xxx tksampleedit_render_sample\n");

   if(YAC_Is_FloatArray(_data))
   {
      YAC_CAST_ARG(YAC_FloatArray, a, _data);

      if( (_numCh > 0) && (_numCh <= 2/*max interleaved channels*/) )
      {
         sUI maxSamples = a->num_elements; // do not divide by numCh!

         sUI numFramesAvail = maxSamples / _numCh;

         if(_frameOffset >= numFramesAvail)
         {
            _frameOffset -= numFramesAvail;
         }
         
         // if(_frameOffset < numFramesAvail)
         {
            if(_numFrames <= numFramesAvail)
            {
               // (note) allow wrap-around (ringbuffer)
               // if( (_frameOffset + _numFrames) <= numFramesAvail)
               {
                  // if( (_frameOffset + _numFrames) >= _frameOffset) // 32bit overflow
                  {
                     get_sample_fxn_t readFxn;

                     tkopengl_shared_resolve();

                     switch(_interpolStyle)
                     {
                        default:
                        case IS_SH:
                           readFxn = get_sample_sh;
                           break;

                        case IS_LINEAR:
                           readFxn = get_sample_linear;
                           break;

                        case IS_SMOOTHSTEP:
                           readFxn = get_sample_smoothstep;
                           break;

                        case IS_AVG8:
                           readFxn = get_sample_avg8;
                           break;
                     }

                     // Render samples
                     {
                        sF32 h = _h / _numCh;

                        tkopengl_shared->_glPushMatrix();

                        for(sUI ch=0; ch<_numCh; ch++)
                        {
                           switch(_renderStyle)
                           {
                              default:
                              case RS_DOTS:
                                 render_sample_dots(a->elements + ch, _frameOffset,
                                                    _numCh, _numFrames, maxSamples,
                                                    _w, h, 
                                                    _min, _max,
                                                    readFxn
                                                    );
                                 break;

                              case RS_LINES:
                                 render_sample_lines(a->elements + ch, _frameOffset,
                                                     _numCh, _numFrames, maxSamples,
                                                     _w, h, 
                                                     _min, _max,
                                                     _baseColor,
                                                     readFxn
                                                     );
                                 break;

                              case RS_DELTA:
                                 render_sample_delta(a->elements + ch, _frameOffset,
                                                     _numCh, _numFrames, maxSamples,
                                                     _w, h, 
                                                     _min, _max,
                                                     readFxn
                                                     );
                                 break;
                           }

                           tkopengl_shared->_glTranslatef(0, h, 0);
                        }

                        tkopengl_shared->_glPopMatrix();
                     }

                  }
                  // else
                  // {
                  //    yac_host->printf("[---] tksampleedit_render_sample: (frameOffset + numFrames) < frameOffset ((%u+%u)=%u < %u)\n", 
                  //                     _frameOffset, _numFrames, (_frameOffset+_numFrames), _frameOffset);
                  // }
               }
               // else
               // {
               //    yac_host->printf("[---] tksampleedit_render_sample: (frameOffset + numFrames) > numFramesAvail ((%u+%u)=%u >= %u)\n", 
               //                     _frameOffset, _numFrames, (_frameOffset+_numFrames), numFramesAvail);
               // }
            }
            else
            {
               yac_host->printf("[---] tksampleedit_render_sample: numFrames > numFramesAvail (%u >= %u)\n", _numFrames, numFramesAvail);
            }
         }
         // else
         // {
         //    yac_host->printf("[---] tksampleedit_render_sample: frameOffset >= numFramesAvail (%u >= %u)\n", _frameOffset, numFramesAvail);
         // }
      }

   }
}

void YAC_CALL tksampleedit_render_mono_sample_1d_8bit(YAC_Object *_byteArray, sUI _width, YAC_Object *_fa, sUI _frameOffset, sUI _numFrames, sF32 _lumScl) {
   YAC_CAST_ARG(YAC_IntArray, ba, _byteArray);
   YAC_CAST_ARG(YAC_FloatArray, fa, _fa);

   if(_numFrames > 0)
   {
      if((_frameOffset + _numFrames) <= fa->max_elements)
      {
         const sF32 *s = fa->elements + _frameOffset;
         sU8 *d = (sU8*)ba->yacArrayGetPointer();

         sF64 xStep = sF64(_width) / _numFrames;
         sF64 x = 0.0;
         // // memset((void*)ba, 0, _width); // done by caller

         // sF32 smpPerPixScl = sF32(255.0f * _width * 8.0f) / _numFrames;
         sF32 smpPerPixScl = _lumScl;//255.0f;

         for(sUI i = 0u; i < _numFrames; i++)
         {
            sUI di = sUI(x);
            sF32 f = s[i];
            f = sABS(f) * smpPerPixScl;
            sSI pixOld = sSI(d[di]);
            sSI pix = sSI(f);
            // yac_host->printf("xxx s[%u]=%f pix=%u\n", i, s[i], pix);
            if(pix > 255)
               pix = 255;
            d[di] = sU8(sMAX(pix,pixOld));

            // Next frame
            x += xStep;
         }
      }
   }
}
