/*************************************************************************************
 * Original code copyright (C) 2012 Steve Folta
 * Converted to Juce module (C) 2016 Leo Olivers
 * Forked from https://github.com/stevefolta/SFZero
 * For license info please see the LICENSE file distributed with this source code
 *************************************************************************************/

#include "SFZSynth.h"
#include "SFZSound.h"
#include "SFZVoice.h"

namespace sfzero
{

Synth::Synth() : Synthesiser()
{
    carla_zeroStructs(noteVelocities_, 128);
}

void Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity)
{
  int i;

  int midiVelocity = static_cast<int>(velocity * 127);

  // First, stop any currently-playing sounds in the group.
  //*** Currently, this only pays attention to the first matching region.
  int group = 0;
  Sound *sound = dynamic_cast<Sound *>(getSound(0));

  if (sound)
  {
    Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity);
    if (region)
    {
      group = region->group;
    }
  }
  if (group != 0)
  {
    for (i = voices.size(); --i >= 0;)
    {
      Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i));
      if (voice == nullptr)
      {
        continue;
      }
      if (voice->getGroup() == group)
      {
        voice->stopNoteForGroup();
      }
    }
  }

  // Are any notes playing?  (Needed for first/legato trigger handling.)
  // Also stop any voices still playing this note.
  bool anyNotesPlaying = false;
  for (i = voices.size(); --i >= 0;)
  {
    Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i));
    if (voice == nullptr)
    {
      continue;
    }
    if (voice->isPlayingChannel(midiChannel))
    {
      if (voice->isPlayingNoteDown())
      {
        if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
        {
          if (!voice->isPlayingOneShot())
          {
            voice->stopNoteQuick();
          }
        }
        else
        {
          anyNotesPlaying = true;
        }
      }
    }
  }

  // Play *all* matching regions.
  Region::Trigger trigger = (anyNotesPlaying ? Region::legato : Region::first);
  if (sound)
  {
    int numRegions = sound->getNumRegions();
    for (i = 0; i < numRegions; ++i)
    {
      Region *region = sound->regionAt(i);
      if (region->matches(midiNoteNumber, midiVelocity, trigger))
      {
        Voice *voice =
            dynamic_cast<Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, isNoteStealingEnabled()));
        if (voice)
        {
          voice->setRegion(region);
          startVoice(voice, sound, midiChannel, midiNoteNumber, velocity);
        }
      }
    }
  }

  noteVelocities_[midiNoteNumber] = midiVelocity;
}

void Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
{
  Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff);

  // Start release region.
  Sound *sound = dynamic_cast<Sound *>(getSound(0));
  if (sound)
  {
    Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], Region::release);
    if (region)
    {
      Voice *voice = dynamic_cast<Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, false));
      if (voice)
      {
        // Synthesiser is too locked-down (ivars are private rt protected), so
        // we have to use a "setRegion()" mechanism.
        voice->setRegion(region);
        startVoice(voice, sound, midiChannel, midiNoteNumber, noteVelocities_[midiNoteNumber] / 127.0f);
      }
    }
  }
}

int Synth::numVoicesUsed()
{
  int numUsed = 0;

  for (int i = voices.size(); --i >= 0;)
  {
    if (voices.getUnchecked(i)->getCurrentlyPlayingNote() >= 0)
    {
      numUsed += 1;
    }
  }

  return numUsed;
}

water::String Synth::voiceInfoString()
{
  enum
  {
    maxShownVoices = 20,
  };

  water::StringArray lines;
  int numUsed = 0, numShown = 0;

  for (int i = voices.size(); --i >= 0;)
  {
    Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i));
    if (voice->getCurrentlyPlayingNote() < 0)
    {
      continue;
    }
    numUsed += 1;
    if (numShown >= maxShownVoices)
    {
      continue;
    }
    lines.add(voice->infoString());
  }

  lines.insert(0, "voices used: " + water::String(numUsed));
  return lines.joinIntoString("\n");
}

}
