/** Copyright (c) 2020-2023 The Creators of Simphone

    class SoundEffects: play sound notifications

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "sounds.h"

#include "contacts.h"
#include "chatwindow.h"
#include "chatframe.h"

static simunsigned g_logonTime = 0;

QSet<QString> SoundEffects::mc_noSoundMap;
QHash<int, SoundEffects::E_SoundType> SoundEffects::mc_soundMap;
int SoundEffects::mc_soundCount[sound_nSounds];

static bool checkPlayTime(simunsigned * lastTime, int timeout)
{
  simunsigned time = sim_get_tick();
  bool result = *lastTime + timeout < time;
  *lastTime = time;
  return result;
}

SoundEffects::SoundEffects()
{
  for (int i = 0; i < sound_nSounds; i++) mc_soundCount[i] = 0;
}

void SoundEffects::stopSound(int contactId, const char * name, E_SoundType sound)
{
  if (mc_soundMap[contactId] == sound) {
    mc_soundMap[contactId] = sound_none;
    if (!--mc_soundCount[sound]) sim_sound_stop_(name);
  }
}

void SoundEffects::playSound(int contactId, const char * name, E_SoundType sound)
{
  if (mc_soundMap[contactId] == sound_none) {
    if (sim_sound_start_(name, 0) == SIM_OK) {
      mc_soundMap[contactId] = sound;
      mc_soundCount[sound]++;
    }
  }
}

void SoundEffects::playIncomingCallSound(int contactId)
{
  if (checkVisibleSound("call") && checkSound(contactId, "sound.play.call")) playSound(contactId, "call", sound_call);
}

void SoundEffects::playOutgoingCallSound(int contactId)
{
  playSound(contactId, "ring", sound_ring);
}

void SoundEffects::playOutgoingConnectSound(int contactId)
{
  if (checkSound(contactId, "sound.play.connect")) playSound(contactId, "connect", sound_connect);
}

void SoundEffects::playLogonSound(int contactId)
{
  if (checkVisibleSound("logon") && checkSound(contactId, "sound.play.logon")) {
    if (checkPlayTime(&g_logonTime, SimParam::get("sound.delay.logon"))) sim_sound_start_("logon", 1);
  }
}

void SoundEffects::playLogoffSound(int contactId)
{
  if (checkVisibleSound("logoff") && checkSound(contactId, "sound.play.logoff")) {
    sim_sound_start_("logoff", 1);
  }
}

void SoundEffects::playMsgSound(int contactId, bool focused)
{
  int status = SimCore::getMyStatus();

  if (checkVisibleSound("msgrecv") && contactId >= 0) {
    Contact * contact = SimCore::getContact(contactId);
    if (contact) {
      bool play_always = checkSound(contactId, "sound.play.msg");
      bool play_unfocused = checkSound(contactId, "sound.play.msgrecv");

      if (!play_always && !play_unfocused) return; // play never

      if (play_unfocused) {
        if (focused) {
          if (!play_always || status != SIM_STATUS_IDLE) return;
          // else play if focused but idle (both settings are true)
        } // else unfocused so play
      }   // else play always

      if (checkPlayTime(&contact->m_soundTimes[sound_msg], SimParam::get("sound.delay.msg"))) {
        sim_sound_start_(focused && status != SIM_STATUS_IDLE ? "msgrecv" : "msg", 1);
      }
    }
  }
}

void SoundEffects::playMsgNotsentSound(int contactId)
{
  if (checkSound(contactId, "sound.play.msgnotsent")) sim_sound_start_("msgnotsent", 1);
}

void SoundEffects::playMsgResentSound(int contactId)
{
  if (checkVisibleSound("msgresent") && checkSound(contactId, "sound.play.msgresent")) sim_sound_start_("msgresent", 1);
}

void SoundEffects::playFileSound(int contactId, int sound)
{
  static const char * sounds[] = { "receive", "received", "sent", "cancelled" };

  if (checkVisibleSound(sounds[sound]) && contactId >= 0) {
    QString qs = "sound.play.";
    if (checkSound(contactId, qs.append(sounds[sound]).toUtf8().data())) {
      Contact * contact = SimCore::getContact(contactId);
      if (contact && checkPlayTime(&contact->m_soundTimes[sound], SimParam::get("sound.delay.xfer"))) {
        sim_sound_start_(sounds[sound], 1);
      }
    }
  }
}

void SoundEffects::playBusySound(int /*contactId*/)
{
  sim_sound_start_("busy", 1);
}

void SoundEffects::playFailedSound(int contactId)
{
  if (checkSound(contactId, "sound.play.failed")) sim_sound_start_("failed", 1);
}

void SoundEffects::playDisconnectSound(int contactId)
{
  if (checkSound(contactId, "sound.play.disconnect")) sim_sound_start_("disconnect", 1);
}

void SoundEffects::playHangupSound(int contactId)
{
  if (checkSound(contactId, "sound.play.hangup")) sim_sound_start_("hangup", 1);
}

void SoundEffects::playNewContactSound(int /*contactId*/)
{
  if (checkVisibleSound("contact") && checkSound(-1, "sound.play.contact")) sim_sound_start_("contact", 1);
}

void SoundEffects::playKeygenSound(int /*contactId*/)
{
  if (checkSound(-1, "sound.play.keygen")) sim_sound_start_("keygen", 1);
}

void SoundEffects::playFakeKeySound(int /*contactId*/)
{
  if (checkErrorSound("tofu")) sim_sound_start_("tofu", 0);
}

void SoundEffects::playProtocolSound(int /*contactId*/, int count)
{
  if (checkErrorSound("protocol")) sim_sound_start_("protocol", count);
}

void SoundEffects::playUpSound(bool init)
{
  sim_sound_stop_("down");
  if (checkVisibleSound("up") && checkSound(-1, "sound.play.up")) {
    if (!init || SimParam::get("sound.play.up") & 8) sim_sound_start_("up", 1);
  }
}

void SoundEffects::playDownSound(bool /*init*/)
{
  sim_sound_stop_("up");
  if (checkVisibleSound("down") && checkSound(-1, "sound.play.down")) sim_sound_start_("down", 1);
}

void SoundEffects::stopIncomingCallSound(int contactId)
{
  stopSound(contactId, "call", sound_call);
}

void SoundEffects::stopOutgoingCallSound(int contactId)
{
  stopSound(contactId, "connect", sound_connect);
  stopSound(contactId, "ring", sound_ring);
}

void SoundEffects::stopOutgoingConnectSound(int contactId)
{
  stopSound(contactId, "connect", sound_connect);
}

void SoundEffects::stopSounds(int /*contactId*/)
{
  sim_sound_stop_("call");
  sim_sound_stop_("ring");
  sim_sound_stop_("connect");

  for (int i = 0; i < sound_nSounds; i++) mc_soundCount[i] = 0;
  mc_soundMap.clear();
}

void SoundEffects::stopInvisibleSound(const char * name)
{
  mc_noSoundMap.insert(name);
}

void SoundEffects::stopInvisibleSound()
{
  static const char * names[] = {
    "call", "msgrecv", "msgresent", "receive", "received", "sent", "cancelled",
    "contact", "logon", "logoff", "up", "down", "protocol", "tofu", 0
  };

  for (const char ** name = names; *name; name++) stopInvisibleSound(*name++);
}

bool SoundEffects::checkVisibleSound(const char * name)
{
  if (mc_noSoundMap.contains(name)) {
    if (!SimCore::isHidden()) {
      if (!Contacts::get()->isVisible()) return false;
    } else {
      if (SimCore::isHidden() == 'h') return false;
    }
  }

  return SimCore::getMyStatus() != SIM_STATUS_BUSY;
}

bool SoundEffects::checkSound(int contactId, const char * name)
{
  QWidget * window = Contacts::get();
  QWidget * frame = window;
  Contact * c = contactId < 0 ? 0 : SimCore::getContact(contactId);

  if (!strcmp(name, "sound.play.logon")) {
    if (c && c->m_rights & CONTACT_FLAG_SOUND_ON_Y) return true;
    if (c && c->m_rights & CONTACT_FLAG_SOUND_ON_N) return false;
  } else if (!strcmp(name, "sound.play.logoff")) {
    if (c && c->m_rights & CONTACT_FLAG_SOUND_OFF_Y) return true;
    if (c && c->m_rights & CONTACT_FLAG_SOUND_OFF_N) return false;
  }

  if (c) {
    if (Contacts::get()->isSplitMode()) {
      window = frame = c->m_chatWindow;
    } else {
      frame = c->m_chatFrame;
    }
  }

  int mask = !frame || !frame->isVisible() ? 1 : window->isMinimized() ? 2 : 4;
  int bits = SimParam::get(name);

  return bits > 0 && bits & mask;
}

bool SoundEffects::checkErrorSound(const char * name)
{
  return SimCore::isHidden() != 'h' || !mc_noSoundMap.contains(name);
}
