/**
 * @file music.c
 * @brief Control all the music playing.
 */

#include <SDL/SDL_mixer.h>
#include <SDL.h>

#include "llua.h"
#include "lluadef.h"
#include "misn_lua.h"

#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "music.h"

#define MUSIC_PREFIX    "../snd/music/" /**< Prefix of where to find music. */
#define MUSIC_SUFFIX    ".ogg"          /**< Suffix of music. */

#define MUSIC_LUA_PATH  "../snd/music.lua" /**< Lua music control file. */

int music_disabled = 0; /**< Whether or not music is disabled. */

/* Global music lua. */
static lua_State* music_lua = NULL; /**< The Lua music control state. */
/* Functions. */
static int musicL_load(lua_State* L);
static int musicL_play(lua_State* L);
static int musicL_stop(lua_State* L);
static const luaL_reg music_methods[] = {
  { "load",   musicL_load },
  { "play",   musicL_play },
  { "stop",   musicL_stop },
  {0, 0}
}; /**< Music specific methods. */

/* What is available? */
static char** music_selection = NULL; /**< Available music selection. */
static int nmusic_selection = 0;      /**< Size of available music selection. */

static void* music_data       = NULL; /**< Current music data. */
static SDL_RWops* music_rw    = NULL; /**< Current music RWops. */
static Mix_Music* music_music = NULL; /**< Current music. */

/* Music stuff. */
static int music_find(void);
static void music_free(void);
/* Lua stuff. */
static void music_rechoose(void);
static int music_luaInit(void);
static void music_luaQuit(void);

/**
 * @fn int music init(void)
 *
 * @brief Initializes the music subsystem.
 *    @return 0 on success.
 */
int music_init(void) {
  if(music_disabled) return 0;

  if(music_find() < 0)    return -1;
  if(music_luaInit() < 0) return -1;
  music_volume(0.8);

  return 0;
}

/**
 * @fn void music_exit(void)
 *
 * @brief Exits the music subsystem.
 */
void music_exit(void) {
  music_free();
}

/**
 * @fn static void music_free(void)
 *
 * @brief Free the current playing music.
 */
static void music_free(void) {
  if(music_music != NULL) {
    Mix_HookMusicFinished(NULL);
    Mix_HaltMusic();
    Mix_FreeMusic(music_music);
    /*SDL_FreeRW(music_rw);*/ /*FreeMusic frees it itself. */
    free(music_data);
    music_music = NULL;
    music_rw    = NULL;
    music_data  = NULL;
  }
}

/**
 * @fn static int music_find(void)
 *
 * @brief Internal music loading routines.
 *    @return 0 on success.
 */
static int music_find(void) {
  char** files;
  uint32_t nfiles, i;
  char tmp[64];
  int len;

  if(music_disabled) return 0;

  /* Get the file list. */
  files = pack_listfiles(data, &nfiles);

  /* Load the profiles. */
  for(i = 0; i < nfiles; i++)
    if((strncmp(files[i], MUSIC_PREFIX, strlen(MUSIC_PREFIX))==0) &&
       (strncmp(files[i] + strlen(files[i]) - strlen(MUSIC_SUFFIX),
                MUSIC_SUFFIX, strlen(MUSIC_SUFFIX))==0)) {

      /* Grow the selection size. */
      music_selection = realloc(music_selection,++nmusic_selection*sizeof(char*));

      /* Remove the prefix and suffix. */
      len = strlen(files[i]) - strlen(MUSIC_SUFFIX MUSIC_PREFIX);
      strncpy(tmp, files[i]  + strlen(MUSIC_PREFIX), len);
      tmp[MIN(len, 64-1)] = '\0';

      music_selection[nmusic_selection-1] = strdup(tmp);
    }
  /* Free the char* allocated by pack. */
  for(i = 0; i < nfiles; i++)
    free(files[i]);
  free(files);

  DEBUG("Loaded %d song%c", nmusic_selection, (nmusic_selection==1)?' ':'s');

  return 0;
}

/**
 * @fn int music_volume(const double vol)
 *
 * @brief Set the music volume.
 *    @param vol Volume to set to (between 0 and 1).
 *    @return 0 on success.
 */
int music_volume(const double vol) {
  if(music_disabled) return 0;

  return Mix_VolumeMusic(MIX_MAX_VOLUME*vol);
}

/**
 * @fn void music_load(const char* name)
 *
 * @brief Load the music by name.
 *    @param name Name of the file to load.
 */
void music_load(const char* name) {
  unsigned int size;
  char filename[PATH_MAX];

  if(music_disabled) return;

  music_free();

  /* Load the data. */
  snprintf(filename, PATH_MAX, MUSIC_PREFIX"%s"MUSIC_SUFFIX, name);
  music_data = pack_readfile(DATA, filename, &size);
  music_rw = SDL_RWFromMem(music_data, size);
  music_music = Mix_LoadMUS_RW(music_rw);
  if(music_music == NULL)
    WARN("SDL_Mixer: %s", Mix_GetError());

  Mix_HookMusicFinished(music_rechoose);
}

/**
 * @fn void music_play(void)
 *
 * @brief Play the loaded music.
 */
void music_play(void) {
  if(music_music == NULL) return;

  if(Mix_FadeInMusic(music_music, 0, 500) < 0)
    WARN("SDL_Mixer: %s", Mix_GetError());
}

/**
 * @fn void music_stop(void)
 *
 * @brief Stop the loaded music.
 */
void music_stop(void) {
  if(music_music == NULL) return;

  if(Mix_FadeOutMusic(500) < 0)
    WARN("SDL_Mixer: %s", Mix_GetError());
}

/* Music lua stuff. */

/**
 * @fn static int music_luaInit(void)
 *
 * @brief Initializes the music Lua control system.
 *    @return 0 on success.
 */
static int music_luaInit(void) {
  char* buf;
  uint32_t bufsize;

  if(music_lua != NULL)
    music_luaQuit();

  music_lua = llua_newState();

  /*luaL_openlibs(music_lua); */

  lua_loadSpace(music_lua, 1);  /* Space and time are readonly. */
  lua_loadTime(music_lua, 1);
  lua_loadRnd(music_lua);
  lua_loadVar(music_lua, 1);    /* Also read only. */
  lua_loadMusic(music_lua, 0);  /* Write it. */

  /* Load the actual lua music code. */
  buf = pack_readfile(DATA, MUSIC_LUA_PATH, &bufsize);
  if(luaL_dobuffer(music_lua, buf, bufsize, MUSIC_LUA_PATH) != 0) {
    ERR("Error loading music file: %s\n"
        "%s\n"
        "Most likely Lua file has improper syntax, please check",
        MUSIC_LUA_PATH, lua_tostring(music_lua, -1));
    return -1;
  }
  free(buf);

  return 0;
}

/**
 * @fn static void music_luaQuit(void)
 *
 * @brief Quit the music Lua control system.
 */
static void music_luaQuit(void) {
  if(music_lua == NULL)
    return;

  lua_close(music_lua);
  music_lua = NULL;
}

/**
 * @fn int lua_loadMusic(lua_State* L, int read_only)
 *
 * @brief Load the music functions into a lua_State.
 *    @param L Lua State to load the music functions into.
 *    @param read_only Load the write functions?
 *    @return  0 on success.
 */
int lua_loadMusic(lua_State* L, int read_only) {
  (void)read_only; /* Future proof. */
  luaL_register(L, "music", music_methods);
  return 0;
}

/**
 * @fn int music_choose(char* situation)
 *
 * @brief Actually run the music stuff, based on situation.
 *    @param situation choose a new music to play.
 *    @return 0 on success.
 */
int music_choose(char* situation) {
  if(music_disabled) return 0;

  lua_getglobal(music_lua, "choose");
  lua_pushstring(music_lua, situation);
  if(lua_pcall(music_lua, 1, 0, 0)) {
    /* Error occured. */
    WARN("Error while choosing music: %s", (char*)lua_tostring(music_lua, -1));
    return -1;
  }
  return 0;
}

/**
 * @fn static void music_rechoose(void)
 *
 * @brief Attempts to rechoose the music.
 */
static void music_rechoose(void) {
  music_choose("idle");
}

/* The music lua functions. */
static int musicL_load(lua_State* L) {
  char* str;

  /* Check parameters. */
  LLUA_MIN_ARGS(1);
  if(lua_isstring(L, 1)) str = (char*)lua_tostring(L, 1);
  else LLUA_INVALID_PARAMETER();

  music_load(str);
  return 0;
}

static int musicL_play(lua_State* L) {
  (void)L;
  music_play();
  return 0;
}

static int musicL_stop(lua_State* L) {
  (void)L;
  music_stop();
  return 0;
}

