/*
 * cd_sdl.c
 *
 * Copyright (C) 1996-1997  Id Software, Inc.
 * Taken from the Twilight project with modifications
 * to make it work with Hexen II: Hammer of Thyrion.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include <SDL2/SDL.h>

#ifndef SDL_INIT_CDROM

/* SDL dropped support for
   cd audio since v1.3.0 */
// #pragma message("Warning: SDL CDAudio support disabled")
#include "cd_null.cpp"

#else /* SDL_INIT_CDROM */

#include "cmd.hpp"
#include "common.hpp"
#include "cdaudio.hpp"
#include "console.hpp"

static bool cdValid = false;
static bool playing = false;
static bool wasPlaying = false;
static bool enabled = true;
static bool playLooping = false;
static byte remap[100];
static byte playTrack;
static double endOfTrack = -1.0, pausetime = -1.0;
static SDL_CD* cd_handle;
static int cd_dev = -1;
static float old_cdvolume;
static bool hw_vol_works = true;


static void CDAudio_Eject()
{
    if(!cd_handle || !enabled) return;

#ifdef __linux__
    SDL_CDStop(cd_handle); /* see CDAudio_Stop() */
#endif
    if(SDL_CDEject(cd_handle) < 0)
        Con_Printf("Unable to eject CD-ROM: %s\n", SDL_GetError());
}

static int CDAudio_GetAudioDiskInfo()
{
    cdValid = false;

    if(!cd_handle) return -1;

    if(!CD_INDRIVE(SDL_CDStatus(cd_handle))) return -1;

    cdValid = true;

    return 0;
}

int CDAudio_Play(byte track, bool looping)
{
    int len_m, len_s, len_f;

    if(!cd_handle || !enabled) return -1;

    if(!cdValid)
    {
        CDAudio_GetAudioDiskInfo();
        if(!cdValid) return -1;
    }

    track = remap[track];

    if(track < 1 || track > cd_handle->numtracks)
    {
        Con_Printf("CDAudio_Play: Bad track number %d.\n", track);
        return -1;
    }

    if(cd_handle->track[track - 1].type == SDL_DATA_TRACK)
    {
        Con_Printf("CDAudio_Play: track %d is not audio\n", track);
        return -1;
    }

    if(playing)
    {
        if(playTrack == track) return 0;
        CDAudio_Stop();
    }

    if(SDL_CDPlay(cd_handle, cd_handle->track[track - 1].offset,
           cd_handle->track[track - 1].length) < 0)
    {
        Con_Printf("CDAudio_Play: Unable to play track %d: %s\n", track,
            SDL_GetError());
        return -1;
    }

    playLooping = looping;
    playTrack = track;
    playing = true;

    FRAMES_TO_MSF(cd_handle->track[track - 1].length, &len_m, &len_s, &len_f);
    endOfTrack = realtime + ((double)len_m * 60.0) + (double)len_s +
                 (double)len_f / (double)CD_FPS;

    /* Add the pregap for the next track.  This means that disc-at-once CDs
     * won't loop smoothly, but they wouldn't anyway so it doesn't really
     * matter.  SDL doesn't give us pregap information anyway, so you'll
     * just have to live with it.  */
    endOfTrack += 2.0;
    pausetime = -1.0;

    if(bgmvolume.value == 0) /* don't bother advancing */
        CDAudio_Pause();

    return 0;
}

void CDAudio_Stop()
{
    if(!cd_handle || !enabled) return;

    if(!playing) return;

#ifdef __linux__
    /* Don't really stop, but just pause: On some devices, the CDROMSTOP
     * ioctl causes any followup ioctls to fail for a considerable time.
     * observed with a TSSTcorp CDW/DVD SH-M522C drive with TS05 and TS08
     * firmware versions running under a 2.6.27.25 kernel, and with a
     * Samsung DVD r/w drive running under 2.6.35.6 kernel.
     * Therefore, avoid dead stops if playback may be resumed shortly. */
    if(SDL_CDPause(cd_handle) < 0)
        Con_Printf(
            "CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError());
#else
    if(SDL_CDStop(cd_handle) < 0)
        Con_Printf(
            "CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError());
#endif

    wasPlaying = false;
    playing = false;
    pausetime = -1.0;
    endOfTrack = -1.0;
}

void CDAudio_Pause()
{
    if(!cd_handle || !enabled) return;

    if(!playing) return;

    if(SDL_CDPause(cd_handle) < 0)
        Con_Printf("Unable to pause CD-ROM: %s\n", SDL_GetError());

    wasPlaying = playing;
    playing = false;
    pausetime = realtime;
}

void CDAudio_Resume()
{
    if(!cd_handle || !enabled) return;

    if(!cdValid) return;

    if(!wasPlaying) return;

    if(SDL_CDResume(cd_handle) < 0)
        Con_Printf("Unable to resume CD-ROM: %s\n", SDL_GetError());
    playing = true;
    endOfTrack += realtime - pausetime;
    pausetime = -1.0;
}

static int get_first_audiotrk()
{
    int i;
    for(i = 0; i < cd_handle->numtracks; ++i)
        if(cd_handle->track[i].type != SDL_DATA_TRACK) return ++i;
    return 1;
}

static void CD_f()
{
    const char* command;
    int ret, n;

    if(Cmd_Argc() < 2)
    {
        Con_Printf("commands:");
        Con_Printf("on, off, reset, remap, \n");
        Con_Printf("play, stop, loop, pause, resume\n");
        Con_Printf("eject, info, next, prev\n");
        return;
    }

    command = Cmd_Argv(1);

    if(q_strcasecmp(command, "on") == 0)
    {
        enabled = true;
        return;
    }

    if(q_strcasecmp(command, "off") == 0)
    {
        if(playing) CDAudio_Stop();
        enabled = false;
        return;
    }

    if(q_strcasecmp(command, "reset") == 0)
    {
        enabled = true;
        if(playing) CDAudio_Stop();
        for(n = 0; n < 100; n++) remap[n] = n;
        CDAudio_GetAudioDiskInfo();
        return;
    }

    if(q_strcasecmp(command, "remap") == 0)
    {
        ret = Cmd_Argc() - 2;
        if(ret <= 0)
        {
            for(n = 1; n < 100; n++)
                if(remap[n] != n) Con_Printf("  %u -> %u\n", n, remap[n]);
            return;
        }
        for(n = 1; n <= ret; n++) remap[n] = atoi(Cmd_Argv(n + 1));
        return;
    }

    if(!cdValid)
    {
        CDAudio_GetAudioDiskInfo();
        if(!cdValid)
        {
            Con_Printf("No CD in player.\n");
            return;
        }
    }

    if(q_strcasecmp(command, "play") == 0)
    {
        n = atoi(Cmd_Argv(2));
        if(n == 0) n = 1;
        CDAudio_Play((byte)n, false);
        return;
    }

    if(q_strcasecmp(command, "loop") == 0)
    {
        CDAudio_Play((byte)atoi(Cmd_Argv(2)), true);
        return;
    }

    if(q_strcasecmp(command, "stop") == 0)
    {
        CDAudio_Stop();
        return;
    }

    if(q_strcasecmp(command, "pause") == 0)
    {
        CDAudio_Pause();
        return;
    }

    if(q_strcasecmp(command, "resume") == 0)
    {
        CDAudio_Resume();
        return;
    }

    if(q_strcasecmp(command, "eject") == 0)
    {
        if(playing) CDAudio_Stop();
        CDAudio_Eject();
        cdValid = false;
        return;
    }

    if(q_strcasecmp(command, "info") == 0)
    {
        int current_min, current_sec, current_frame;
        int length_min, length_sec, length_frame;

        Con_Printf("%u tracks\n", cd_handle->numtracks);

        if(playing)
            Con_Printf("Currently %s track %u\n",
                playLooping ? "looping" : "playing", playTrack);
        else if(wasPlaying)
            Con_Printf("Paused %s track %u\n",
                playLooping ? "looping" : "playing", playTrack);

        if(playing || wasPlaying)
        {
            SDL_CDStatus(cd_handle);
            FRAMES_TO_MSF(cd_handle->cur_frame, &current_min, &current_sec,
                &current_frame);
            FRAMES_TO_MSF(cd_handle->track[playTrack - 1].length, &length_min,
                &length_sec, &length_frame);

            Con_Printf("Current position: %d:%02d.%02d (of %d:%02d.%02d)\n",
                current_min, current_sec, current_frame * 60 / CD_FPS,
                length_min, length_sec, length_frame * 60 / CD_FPS);
        }
        Con_Printf("Volume is %f\n", bgmvolume.value);

        return;
    }

    if(q_strcasecmp(command, "next") == 0)
    {
        if(playTrack == cd_handle->numtracks)
            playTrack = get_first_audiotrk() - 1;
        CDAudio_Play(playTrack + 1, playLooping);
        return;
    }

    if(q_strcasecmp(command, "prev") == 0)
    {
        if(playTrack == get_first_audiotrk())
            playTrack = cd_handle->numtracks + 1;
        CDAudio_Play(playTrack - 1, playLooping);
        return;
    }

    Con_Printf("cd: unknown command \"%s\"\n", command);
}

static bool CD_GetVolume(void* unused)
{
    (void)unused;

    /* FIXME: write proper code in here when SDL
       supports cdrom volume control some day. */
    return false;
}

static bool CD_SetVolume(void* unused)
{
    (void)unused;

    /* FIXME: write proper code in here when SDL
       supports cdrom volume control some day. */
    return false;
}

static bool CDAudio_SetVolume(float value)
{
    if(!cd_handle || !enabled) return false;

    old_cdvolume = value;

    if(value == 0.0f)
        CDAudio_Pause();
    else
        CDAudio_Resume();

    if(!hw_vol_works)
    {
        return false;
    }
    else
    {
        /* FIXME: write proper code in here when SDL
           supports cdrom volume control some day. */
        return CD_SetVolume(nullptr);
    }
}

void CDAudio_Update()
{
    CDstatus curstat;
    /*	static double	lastchk;*/

    if(!cd_handle || !enabled) return;

    if(old_cdvolume != bgmvolume.value) CDAudio_SetVolume(bgmvolume.value);

    /*	if (playing && realtime > lastchk)*/
    if(playing && realtime > endOfTrack)
    {
        /*	lastchk = realtime + 2;*/ /* two seconds between chks */
        curstat = SDL_CDStatus(cd_handle);
        if(curstat != CD_PLAYING && curstat != CD_PAUSED)
        {
            playing = false;
            endOfTrack = -1.0;
            if(playLooping) CDAudio_Play(playTrack, true);
        }
    }
}

static const char* get_cddev_arg(const char* arg)
{
#if defined(_WIN32)
    /* arg should be like "D:\", make sure it is so,
     * but tolerate args like "D" or "D:", as well. */
    static char drive[4];
    if(!arg || !*arg) return nullptr;
    if(arg[1] != '\0')
    {
        if(arg[1] != ':') return nullptr;
        if(arg[2] != '\0')
        {
            if(arg[2] != '\\' && arg[2] != '/') return nullptr;
            if(arg[3] != '\0') return nullptr;
        }
    }
    if(*arg >= 'A' && *arg <= 'Z')
    {
        drive[0] = *arg;
        drive[1] = ':';
        drive[2] = '\\';
        drive[3] = '\0';
        return drive;
    }
    else if(*arg >= 'a' && *arg <= 'z')
    {
        /* make it uppercase for SDL */
        drive[0] = *arg - ('a' - 'A');
        drive[1] = ':';
        drive[2] = '\\';
        drive[3] = '\0';
        return drive;
    }
    return nullptr;
#else
    if(!arg || !*arg) return nullptr;
    return arg;
#endif
}

static void export_cddev_arg()
{
/* Bad ugly hack to workaround SDL's cdrom device detection.
 * not needed for windows due to the way SDL_cdrom works. */
#if !defined(_WIN32)
    int i = COM_CheckParm("-cddev");
    if(i != 0 && i < com_argc - 1 && com_argv[i + 1][0] != '\0')
    {
        static char arg[64];
        q_snprintf(arg, sizeof(arg), "SDL_CDROM=%s", com_argv[i + 1]);
        putenv(arg);
    }
#endif
}

int CDAudio_Init()
{
    int i, sdl_num_drives;

    if(safemode || COM_CheckParm("-nocdaudio")) return -1;

    export_cddev_arg();

    if(SDL_InitSubSystem(SDL_INIT_CDROM) < 0)
    {
        Con_Printf("Couldn't init SDL cdrom: %s\n", SDL_GetError());
        return -1;
    }

    sdl_num_drives = SDL_CDNumDrives();
    Con_Printf("SDL detected %d CD-ROM drive%c\n", sdl_num_drives,
        sdl_num_drives == 1 ? ' ' : 's');

    if(sdl_num_drives < 1) return -1;

    if((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1)
    {
        const char* userdev = get_cddev_arg(com_argv[i + 1]);
        if(!userdev)
        {
            Con_Printf("Invalid argument to -cddev\n");
            return -1;
        }
        for(i = 0; i < sdl_num_drives; i++)
        {
            if(!q_strcasecmp(SDL_CDName(i), userdev))
            {
                cd_dev = i;
                break;
            }
        }
        if(cd_dev == -1)
        {
            Con_Printf("SDL couldn't find cdrom device %s\n", userdev);
            return -1;
        }
    }

    if(cd_dev == -1) cd_dev = 0; /* default drive */

    cd_handle = SDL_CDOpen(cd_dev);
    if(!cd_handle)
    {
        Con_Printf("CDAudio_Init: Unable to open CD-ROM drive %s (%s)\n",
            SDL_CDName(cd_dev), SDL_GetError());
        return -1;
    }

    for(i = 0; i < 100; i++) remap[i] = i;
    enabled = true;
    old_cdvolume = bgmvolume.value;

    Con_Printf("CDAudio initialized (SDL, using %s)\n", SDL_CDName(cd_dev));

    if(CDAudio_GetAudioDiskInfo())
    {
        Con_Printf("CDAudio_Init: No CD in drive\n");
        cdValid = false;
    }

    Cmd_AddCommand("cd", CD_f);

    hw_vol_works = CD_GetVolume(nullptr); /* no SDL support at present. */
    if(hw_vol_works) hw_vol_works = CDAudio_SetVolume(bgmvolume.value);

    return 0;
}

void CDAudio_Shutdown()
{
    if(!cd_handle) return;
    CDAudio_Stop();
    if(hw_vol_works) CD_SetVolume(nullptr); /* no SDL support at present. */
#ifdef __linux__
    SDL_CDStop(cd_handle);                  /* see CDAudio_Stop() */
#endif
    SDL_CDClose(cd_handle);
    cd_handle = nullptr;
    cd_dev = -1;
    SDL_QuitSubSystem(SDL_INIT_CDROM);
}

#endif /* SDL_INIT_CDROM */
