// ----
// ---- file   : MIDIPipeRoot.cpp
// ---- author : Bastian Spiegel <bs@tkscript.de>
// ---- legal  : (c) 2014-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   : This is part of the "syntracker" sequencer.
// ----
// ---- changed: 01Sep2014, 03Sep2014, 04Sep2014, 05Sep2014, 08Sep2014, 09Sep2014, 11Sep2014
// ----          26Sep2014, 27Sep2014, 08Feb2015, 23Feb2015, 08Mar2015, 28Apr2015, 14Sep2015
// ----          09Sep2016, 17Feb2017, 03Mar2017, 29Mar2019
// ----
// ----
// ----

#include "tkmidipipe.h"


MIDIPipeRoot::MIDIPipeRoot(void) {
   devices     = NULL;
   max_devices = 0;
   pipes       = NULL;
   max_pipes   = 0;
   solo_pipe   = NULL;
   // // script_context = yac_host->yacContextCreate();
}

MIDIPipeRoot::~MIDIPipeRoot() {
   // printf("xxx ~MIDIPipeRoot this=%p\n", this);

   freePipes();
   freeDevices();
   // // yac_host->yacContextDestroy(script_context);
   // // script_context = NULL;
}

void MIDIPipeRoot::allocDeviceSlots(sUI _maxDevices) {
   devices = new MIDIPipeDevice*[_maxDevices];
   max_devices = _maxDevices;
   memset(devices, 0, sizeof(MIDIPipeDevice*) * max_devices);
}

sUI MIDIPipeRoot::getMaxDevices(void) const {
   return max_devices;
}

MIDIPipeDevice *MIDIPipeRoot::allocDevice(sUI _devIdx) {
   MIDIPipeDevice *r = NULL;

   if(_devIdx < max_devices)
   {
      if(NULL == devices[_devIdx])
      {
         r = YAC_NEW(MIDIPipeDevice);

         if(NULL != r)
         {
            r->setDevIdx(_devIdx);

            devices[_devIdx] = r;
         }
      }
   }

   return r;
}

void MIDIPipeRoot::freeDevices(void) {
   if(NULL != devices)
   {
      sUI i;

      for(i=0; i<max_devices; i++)
      {
         MIDIPipeDevice *dev = devices[i];

         if(NULL != dev)
         {
            YAC_DELETE(dev);
         }
      }

      memset(devices, 0, sizeof(MIDIPipeDevice*) * max_devices);

      delete [] devices;
      devices = NULL;
      max_devices = 0;
   }
}

MIDIPipeDevice *MIDIPipeRoot::getDeviceByIdx(sUI _devIdx) const {
   if(_devIdx < max_devices)
   {
      return devices[_devIdx];
   }
   return NULL;
}

void MIDIPipeRoot::allocPipeSlots(sUI _maxPipes) {
   pipes = new MIDIPipe*[_maxPipes];
   max_pipes = _maxPipes;
   memset(pipes, 0, sizeof(MIDIPipe*) * max_pipes);
}

sUI MIDIPipeRoot::getMaxPipes(void) const {
   return max_pipes;
}

sUI MIDIPipeRoot::getNumPipes(void) const {
   sUI num = 0;
   for(sUI i=0; i<max_pipes; i++)
   {
      if(NULL != pipes[i])
      {
         num++;
      }
   }
   return num;
}

MIDIPipe *MIDIPipeRoot::allocPipe(sUI _pipeIdx) {
   MIDIPipe *r = NULL;

   if(_pipeIdx < max_pipes)
   {
      if(NULL == pipes[_pipeIdx])
      {
         r = YAC_NEW(MIDIPipe);

         if(NULL != r)
         {
            r->pipe_idx = _pipeIdx;

            pipes[_pipeIdx] = r;

            r->init(this);
         }
      }
   }

   return r;
}

void MIDIPipeRoot::freePipe(MIDIPipe *_pipe) {
   sUI i;

   for(i=0; i<max_pipes; i++)
   {
      if(pipes[i] == _pipe)
      {
         YAC_DELETE_SAFE(pipes[i]);
         break;
      }
   }
}


void MIDIPipeRoot::freePipes(void) {
   if(NULL != pipes)
   {
      sUI i;

      for(i=0; i<max_pipes; i++)
      {
         MIDIPipe *pipe = pipes[i];

         if(NULL != pipe)
         {
            YAC_DELETE(pipe);
         }
      }

      memset(pipes, 0, sizeof(MIDIPipe*) * max_pipes);

      delete [] pipes;
      pipes = NULL;
      max_pipes = 0;
   }
}

sSI MIDIPipeRoot::findFirstUsedIdx(void) const {
   sUI i;
   for(i=0; i<max_pipes; i++)
   {
      if(NULL != pipes[i])
      {
         return (sSI)i;
      }
   }
   return -1;
}

sSI MIDIPipeRoot::findFirstUnusedIdx(void) const {
   sUI i;
   for(i=0; i<max_pipes; i++)
   {
      if(NULL == pipes[i])
      {
         return (sSI)i;
      }
   }
   return -1;
}

sSI MIDIPipeRoot::findLastUsedIdx(void) const {
   sUI i;
   sSI r = -1;
   for(i=0; i<max_pipes; i++)
   {
      if(NULL != pipes[i])
      {
         r = (sSI) i;
      }
   }
   return r;
}

MIDIPipe *MIDIPipeRoot::getPipeByIdx(sUI _pipeIdx) const {
   if(_pipeIdx < max_pipes)
   {
      return pipes[_pipeIdx];
   }
   return NULL;
}

MIDIPipe *MIDIPipeRoot::getPipeByUsedIdx(sUI _pipeUsedIdx) const {
   if(_pipeUsedIdx < max_pipes)
   {
      sUI i;
      sUI ui = 0;
      for(i=0; i<max_pipes; i++)
      {
         if(NULL != pipes[i])
         {
            if(ui == _pipeUsedIdx)
            {
               return pipes[i];
            }
            ui++;
         }
      }
   }
   return NULL;
}

void MIDIPipeRoot::swapPipeSlots(sUI _a, sUI _b) {
   MIDIPipe *t = pipes[_a];
   pipes[_a] = pipes[_b];
   pipes[_b] = t;

   if(NULL != pipes[_a])
   {
      pipes[_a]->pipe_idx = _a;
   }

   if(NULL != pipes[_b])
   {
      pipes[_b]->pipe_idx = _b;
   }
}

void MIDIPipeRoot::resetAllAndEmit(void) {
}

sBool MIDIPipeRoot::getDeviceBuffersByEvent(const midipipe_frame_base_t *_ev,
                                            YAC_Buffer**_retBufCtl,
                                            YAC_Buffer**_retBufNote,
                                            midipipe_device_ch_t**_retDevCh,
                                            midipipe_device_ch_t**_retDevChShadow,
                                            MIDIPipeDevice**_retPipeDev
                                            ) const {
   if((sUI)_ev->dev_idx < max_devices)
   {
      MIDIPipeDevice *dev = devices[_ev->dev_idx];

      if(NULL != dev)
      {
         *_retBufCtl       = dev->cmd_buffer;
         *_retBufNote      = dev->note_buffer;
         *_retDevCh        = &dev->channels[_ev->midi_ch & 15u];
         *_retDevChShadow  = &dev->shadow_channels[_ev->midi_ch & 15u];
         *_retPipeDev      = dev;

         return (NULL != dev->cmd_buffer) && (NULL != dev->note_buffer);
      }
   }
   return YAC_FALSE;
}

void MIDIPipeRoot::emitAutoNoteOffs(void) {
   sUI devIdx;

   for(devIdx=0; devIdx < max_devices; devIdx++)
   {
      MIDIPipeDevice *dev = devices[devIdx];

      if(NULL != dev)
      {
         sUI midiCh;
         midipipe_device_ch_t *devCh = dev->channels;

         for(midiCh=0; midiCh<16; midiCh++, devCh++)
         {
            if(devCh->num_notes > 0)
            {
               sUI nnLo = (sUI)devCh->note_low;
               if(nnLo > 127u)
               {
                  nnLo = 0u;
               }

               sUI nnHi = (sUI)devCh->note_hi;
               if(nnHi > 127u)
               {
                  nnHi = 127u;
               }

               // printf("xxx midiCh=%u nnLo=%u nnHi=%u\n", midiCh, nnLo, nnHi);

               for(sUI nn=nnLo; nn<=nnHi; nn++)
               // for(sUI nn=0; nn<128; nn++)
               {
                  if(-1 != devCh->note_states[nn].ticks_left)
                  {
                     // Active note
                     sBool bContNote = YAC_TRUE;

                     if(devCh->note_states[nn].ticks_left > 0)
                     {
                        if(--devCh->note_states[nn].ticks_left <= 0)
                        {
                           // Auto Note-Off
                           devCh->note_states[nn].ticks_left = -1;
                           
                           if(NULL != dev->note_buffer)
                           {
                              dev->note_buffer->yacStreamWriteI8(0x80 | midiCh);
                              dev->note_buffer->yacStreamWriteI8(nn);
                              dev->note_buffer->yacStreamWriteI8(0/*vel*/);
                              // yac_host->printf("xxx send auto note off devIdx=%u ch=%u nn=%u\n", devIdx, midiCh, nn);
                           }
                           
                           if(nn == (sUI)devCh->note_low)
                           {
                              devCh->note_low = -1;
                           }
                           
                           if(nn == (sUI)devCh->note_hi)
                           {
                              devCh->note_hi = 0xFFFFu;
                           }
                           
                           if(devCh->num_notes > 0)
                           {
                              devCh->num_notes--;
                           }

                           bContNote = YAC_FALSE;
                        }
                     }

                     if(bContNote)
                     {
                        // Continue playing note
                        if(-1 == devCh->note_low)
                        {
                           devCh->note_low = (sSI)nn;
                        }

                        if((0xFFFFu == devCh->note_hi) || (nn > devCh->note_hi))
                        {
                           devCh->note_hi = (sSI)nn;
                        }
                     }
                  }
               }
            } // if num_notes > 0
         } // for midiCh
      } // if dev
   }

}

void MIDIPipeRoot::setSoloPipe(YAC_Object *_pipe) {
   solo_pipe = (MIDIPipe*)_pipe;
}

MIDIPipe *MIDIPipeRoot::getSoloPipe(void) const {
   return solo_pipe;
}

void MIDIPipeRoot::processAllPipesAndEmit(MIDIPipeFrame *_framePlayMaster,
                                          MIDIPipeFrame *_framePlay,
                                          MIDIPipeFrame *_frameRec,
                                          sBool _bPlaySeq
                                          ) {

   sUI pipeIdx;

   // // Combined output of all pipes (to avoid duplicate notes, e.g. when using "global" devs)
   // //  (note) caveat: limited control over order of MIDI bytes
   // MIDIPipeFrame frameOut;
   // frameOut.root = this;

   MIDIPipeFrame frameGlobal;
   frameGlobal.root = this;

   _framePlay->empty();

   for(pipeIdx=0; pipeIdx<max_pipes; pipeIdx++)
   {
      if(NULL != pipes[pipeIdx])
      {
         MIDIPipe *pipe = pipes[pipeIdx];

         // printf("xxx process pipe=%p idx=%u\n", pipe, pipeIdx);

         if(0 == pipeIdx)
         {
            // printf("xxx 2 process pipe=%p idx=%u\n", pipe, pipeIdx);
            pipe->processFrame(_framePlayMaster, _frameRec, _bPlaySeq);
         }
         else
         {
            _framePlay->empty();

            _framePlay->mergeFrame(&frameGlobal);
            
            _framePlay->mergeFrame(_framePlayMaster);

            pipe->processFrame(_framePlay, _frameRec, _bPlaySeq);
            
            // Move all "global" device events to frameGlobal (for next pipe)
            frameGlobal.empty();

            for(sUI devIdx = 0u; devIdx < max_devices; devIdx++)
            {
               MIDIPipeDevice *pipeDev = devices[devIdx];

               if(NULL != pipeDev)
               {
                  if(pipeDev->b_global)
                  {
                     frameGlobal.mergeFrameFlt(_framePlay,
                                               pipeDev->dev_idx/*fltDev*/,
                                               -1/*fltCh*/
                                               );
                     _framePlay->deleteEventsByFlt(pipeDev->dev_idx, -1/*fltCh*/);

                     // if(frameGlobal.getNumEvents() > 0)
                     //    yac_host->printf("xxx MIDIPipeRoot: frameGlobal has %u events\n", frameGlobal.getNumEvents());
                  }
               }
            }

            // // frameOut.mergeFrame(_framePlay);
            _framePlay->emit();
         }
      } // if pipe
   } // loop pipes

   frameGlobal.emit();

   // // frameOut.emit();
}

void MIDIPipeRoot::seek(sUI _ticks, sBool _bSeekAll) {
   sUI pipeIdx;
   for(pipeIdx=0; pipeIdx<max_pipes; pipeIdx++)
   {
      if(NULL != pipes[pipeIdx])
      {
         MIDIPipe *pipe = pipes[pipeIdx];

         if((0 == pipeIdx) || _bSeekAll)
         {
            pipe->seek(_ticks);
         }
      }
   }
}

void MIDIPipeRoot::reset(sBool _bSoft) {
   sUI pipeIdx;
   for(pipeIdx=0; pipeIdx<max_pipes; pipeIdx++)
   {
      if(NULL != pipes[pipeIdx])
      {
         MIDIPipe *pipe = pipes[pipeIdx];

         pipe->reset(_bSoft);
      }
   }
}

void MIDIPipeRoot::clearMIDICache(void) {
   sUI i;

   for(i=0; i<max_devices; i++)
   {
      MIDIPipeDevice *dev = devices[i];

      if(NULL != dev)
      {
         dev->clearMIDICache();
      }
   }
}

void MIDIPipeRoot::clearMIDICacheForDev(sSI _devIdx) {

   if(_devIdx >= 0)
   {
      if(sUI(_devIdx) < max_devices)
      {
         MIDIPipeDevice *dev = devices[_devIdx];

         if(NULL != dev)
         {
            dev->clearMIDICache();
         }
      }
   }
}

MIDIPipe *MIDIPipeRoot::uiGetPipeBeforeIdx(sUI _pipeIdx) {

   if(_pipeIdx < max_pipes)
   {
      if(_pipeIdx > 0)
      {
         sSI i = (sSI) (_pipeIdx - 1);

         while(i >= 0)
         {
            if(NULL != pipes[i])
            {
               return pipes[i];
            }

            i--;
         }
      }
   }

   return NULL;
}

MIDIPipe *MIDIPipeRoot::uiGetPipeAfterIdx(sUI _pipeIdx) {

   if(_pipeIdx < max_pipes)
   {
      sUI i = (_pipeIdx + 1);

      while(i < max_pipes)
      {
         if(NULL != pipes[i])
         {
            return pipes[i];
         }
         
         i++;
      }
   }

   return NULL;
}

void MIDIPipeRoot::handleSongSpeedChanged(void) {

   sUI pipeIdx;

   for(pipeIdx=0; pipeIdx < max_pipes; pipeIdx++)
   {
      if(NULL != pipes[pipeIdx])
      {
         pipes[pipeIdx]->handleSongSpeedChanged();
      }
   }
}
