// Hyperbolic Rogue -- sound effects and music
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details

/** \file sound.cpp
 *  \brief sound effects and music
 */

#include "hyper.h"
namespace hr {

#if HDR
void playSound(cell *c, const string& fname, int vol = 100);
void resetmusic();
#endif

#if HDR
/** RogueViz may be used for situations where music does not correspond to lands, so we allow extra IDs */
static constexpr int MUSIC_MAX = 500;
#endif

EX const char *musicfile = "";
EX bool audio;
EX string musiclicense;
EX string musfname[MUSIC_MAX];
EX int musicvolume = 60;
EX int effvolume = 60;
EX bool music_available;
EX int musiclength[MUSIC_MAX];

EX eLand getCurrentLandForMusic() {
  eLand id = ((anims::center_music()) && centerover) ? centerover->land : cwt.at->land;
  if(isHaunted(id)) id = laHaunted;
  if(id == laWarpSea) id = laWarpCoast;
  if(id == laMercuryRiver) id = laTerracotta;
  return id;
  }

EX void playSeenSound(cell *c) {
  if(!c->monst) return;
  bool nearme = c->cpdist <= 7;
  forCellEx(c2, c) if(c2->cpdist <= 7) nearme = true; 
  if(!nearme) return;
  if(among(c->monst, moEagle, moWindCrow, moAcidBird))
    playSound(c, "seen-eagle");
  else if(c->monst == moEarthElemental) 
    playSound(c, "seen-earth");
  else if(c->monst == moAirElemental) 
    playSound(c, "seen-air");
  else if(c->monst == moPhaser) 
    playSound(c, "seen-frog1");
  else if(c->monst == moFrog) 
    playSound(c, "seen-frog2");
  else if(c->monst == moVaulter) 
    playSound(c, "seen-frog3");
  else if(c->monst == moWaterElemental)
    playSound(c, "seen-water");
  else if(c->monst == moFireElemental)
    playSound(c, "fire");
  else if(c->monst == moDragonHead)
    playSound(c, "seen-dragon");
  else if(c->monst == moWorm)
    playSound(c, "seen-sandworm");
  else if(c->monst == moSkeleton && c->land != laDungeon)
    playSound(c, "seen-skeleton");
  else if(c->monst == moHexSnake)
    playSound(c, "seen-snake");
  else if(c->monst == moWolf || c->monst == moRedFox)
    playSound(c, "seen-woof");
  else if(isTroll(c->monst))
    playSound(c, "seen-troll");
  else if(c->monst == moNecromancer)
    playSound(c, "seen-necromancer");
  else if(c->monst == moGhost)
    playSound(c, "seen-ghost");
  else if(c->monst == moRoseBeauty) 
    playSound(c, princessgender() ? "seen-rosebeauty" : "seen-gardener");
  else if(c->monst == moVizier) 
    playSound(c, "seen-vizier");
  else if(c->monst == moFireFairy)
    playSound(c, "seen-fairy");
  else if(c->monst == moCultist) 
    playSound(c, "seen-cultist");
  else if(c->monst == moPyroCultist) 
    playSound(c, "seen-cultistfire");
  else if(c->monst == moCultistLeader)
    playSound(c, "seen-cultistleader");
  }

#if CAP_SDLAUDIO

bool loaded[MUSIC_MAX];
Mix_Music* music[MUSIC_MAX];
EX int musicpos[MUSIC_MAX];
EX int musstart;
int musfadeval = 2000;

eLand cid = laNone;

EX hookset<bool(eLand&)> hooks_music;
EX hookset<void(eLand&)> hooks_sync_music;

EX bool music_out_of_focus = false;

EX void handlemusic() {
  DEBBI(DF_GRAPH, ("handle music"));
  if(audio && musicvolume) {
    eLand id = getCurrentLandForMusic();
    if(callhandlers(false, hooks_music, id)) return;
    if(outoffocus && !music_out_of_focus) id = eLand(0);
    if(musfname[id] == "LAST") id = cid;
    if(!loaded[id] && !memory_issues()) {
      loaded[id] = true;
      // printf("loading (%d)> %s\n", id, musfname[id].c_str());      
      // reuse music
      if(musfname[id] != "") {
        for(int i=0; i<landtypes; i++)
          if(music[i] && musfname[i] == musfname[id])
            music[id] = music[i];
        }
      if(!music[id]) {
        memory_for_lib();
        music[id] = Mix_LoadMUS(musfname[id].c_str());
        if(!music[id]) {
           printf("Mix_LoadMUS: %s\n", Mix_GetError());
           }
        }
      }
    if(cid != id && !musfadeval) {
      musicpos[cid] = SDL_GetTicks() - musstart;
      musfadeval = musicpos[id] ? 500 : 2000;
      Mix_FadeOutMusic(musfadeval);
      // printf("fadeout %d, pos %d\n", musfadeval, musicpos[cid]);
      }
    if(music[id] && !Mix_PlayingMusic()) {
      callhooks(hooks_sync_music, id);
      cid = id;
      Mix_VolumeMusic(musicvolume);
      Mix_FadeInMusicPos(music[id], -1, musfadeval, musicpos[id] / 1000.0);
      // printf("fadein %d, pos %d\n", musfadeval, musicpos[cid]);
      musstart = SDL_GetTicks() - musicpos[id];
      musicpos[id] = 0;
      musfadeval = 0;
      }
    }
  }

hookset<bool(eLand&)> hooks_resetmusic;

EX void resetmusic() {
  if(audio && musicvolume) {
    Mix_FadeOutMusic(3000);
    cid = laNone;
    for(int i=0; i<landtypes; i++) musicpos[i] = 0;
    musfadeval = 2000;
    }
  }

EX bool loadMusicInfo(string dir) {
  DEBBI(DF_INIT, ("load music info"));
  if(dir == "") return false;
  FILE *f = fopen(dir.c_str(), "rt");
  if(f) {
    string dir2;
    for(int i=0; i<isize(dir); i++) if(dir[i] == '/' || dir[i] == '\\')
      dir2 = dir.substr(0, i+1);
    char buf[1000];
    while(fgets(buf, 800, f) != NULL) {
      for(int i=0; buf[i]; i++) if(buf[i] == 10 || buf[i] == 13) buf[i] = 0;
      if(buf[0] == '[' && buf[3] == ']') {
        int id = (buf[1] - '0') * 10 + buf[2] - '0';
        if(id >= 0 && id < MUSIC_MAX) {
          if(buf[5] == 'L' && buf[6] == '=') musiclength[id] = atoi(buf+7);
          else if(buf[5] == '*' && buf[6] == '/') musfname[id] = dir2 + (buf+7);
          else musfname[id] = buf+5;
          music_available = true;
          }
        else {
          fprintf(stderr, "warning: bad soundtrack id, use the following format:\n");
          fprintf(stderr, "[##] */filename\n");
          fprintf(stderr, "where ## are two digits, and */ is optional and replaced by path to the music\n");
          fprintf(stderr, "alternatively LAST = reuse the last track instead of having a special one");
          }
        // printf("[%2d] %s\n", id, buf);
        }
      else if(buf[0] == '#') {
        }
      else {
        musiclicense += buf;
        musiclicense += "\n";
        }
      }
    fclose(f);
    return true;
    }
  return false;
  }

EX bool loadMusicInfo() {
  return
    loadMusicInfo(musicfile)
    || loadMusicInfo(HYPERPATH "hyperrogue-music.txt") 
    || loadMusicInfo("./hyperrogue-music.txt") 
    || loadMusicInfo("music/hyperrogue-music.txt")
// Destination set by ./configure (in the GitHub repository)
#ifdef MUSICDESTDIR
    || loadMusicInfo(MUSICDESTDIR)
#endif
#ifdef FHS
    || loadMusicInfo("/usr/share/hyperrogue/hyperrogue-music.txt") 
    || (getenv("HOME") && loadMusicInfo(s0 + getenv("HOME") + "/.hyperrogue-music.txt"))
#endif
    ;
  }

EX void initAudio() {
  audio = loadMusicInfo();

  if(audio) {
    if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 2048) != 0) {
      fprintf(stderr, "Unable to initialize audio: %s\n", Mix_GetError());
      audio = false;
      }
    else {
      audio = true;
      Mix_AllocateChannels(16);
      }
    }
  }

map<string, Mix_Chunk*> chunks;

#ifdef SOUNDDESTDIR
string wheresounds = SOUNDDESTDIR;
#else
string wheresounds = HYPERPATH "sounds/";
#endif

hookset<bool(const string& s, int vol)> hooks_sound;

EX void playSound(cell *c, const string& fname, int vol) {
  LATE( hr::playSound(c, fname, vol); )
  if(effvolume == 0) return;
  if(callhandlers(false, hooks_sound, fname, vol)) return;
  // printf("Play sound: %s\n", fname.c_str());
  if(!chunks.count(fname)) {
    string s = wheresounds+fname+".ogg";
    if(memory_issues()) return;
    memory_for_lib();
    chunks[fname] = Mix_LoadWAV(s.c_str());
    // printf("Loading, as %p\n", chunks[fname]);
    }
  Mix_Chunk *chunk = chunks[fname];
  if(chunk) {
    Mix_VolumeChunk(chunk, effvolume * vol / 100);
    Mix_PlayChannel(-1, chunk, 0);
    }
  }

EX void reuse_music_memory() {
  for(int i=0; i<landtypes; i++)
    if(music[i] && music[i] != music[cid]) {
      Mix_Music *which = music[i];
      println(hlog, "freeing music for ", dnameof(eLand(i)));
      Mix_FreeMusic(which);
      for(int j=0; j<landtypes; j++) if(music[j] == which) {
        println(hlog, "... which equals ", dnameof(eLand(j)));
        music[j] = NULL;
        }
      }
  set<Mix_Chunk*> currently_played;
  for(int ch=0; ch<16; ch++) currently_played.insert(Mix_GetChunk(ch));
  set<string> to_free;
  for(auto& p: chunks) 
    if(p.second) {
      if(currently_played.count(p.second)) {
        println(hlog, p.first, ": currently played");
        }
      else {
        Mix_FreeChunk(p.second);
        to_free.insert(p.first); 
        println(hlog, p.first, ": freed");
        }
      }
  for(auto& s: to_free) chunks.erase(s);
  }

#endif

#if CAP_COMMANDLINE
int read_sound_args() {
  using namespace arg;
  if(argis("-m")) { PHASE(1); shift(); musicfile = argcs(); }
#if CAP_SDLAUDIO
  else if(argis("-se")) { PHASE(1); shift(); wheresounds = args(); }
  else if(argis("-musicfocus")) { music_out_of_focus = true; }
#endif
  else if(argis("-svol")) { PHASEFROM(2); shift(); effvolume = argi(); }
  else if(argis("-mvol")) { PHASEFROM(2); shift(); musicvolume = argi(); }
  else return 1;
  return 0;
  }

#if CAP_SDLAUDIO
auto ah_sound = addHook(hooks_args, 0, read_sound_args) + addHook(hooks_clear_cache, 0, reuse_music_memory);
#endif

#endif

#if !CAP_AUDIO
EX void playSound(cell *c, const string& fname, int vol) { }
EX void resetmusic() { }
#endif

}