// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 1998-2000 by DooM Legacy Team.
//
// 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.
//-----------------------------------------------------------------------------
/// \file
/// \brief cd music interface (uses MCI).

#include "../doomdef.h"
#include "win_main.h"
#include <mmsystem.h>

#include "../command.h"
#include "../doomtype.h"
#include "../i_sound.h"
#include "../i_system.h"

#include "../s_sound.h"

#define MAX_CD_TRACKS       255

typedef struct {
	BOOL    IsAudio;
	DWORD   Start, End;
	DWORD   Length;         // minutes
} CDTrack;

// -------
// private
// -------
static  CDTrack          m_nTracks[MAX_CD_TRACKS];
static  int              m_nTracksCount;             // up to MAX_CD_TRACKS
static  MCI_STATUS_PARMS m_MCIStatus;
static  MCI_OPEN_PARMS   m_MCIOpen;

// ------
// protos
// ------
static void Command_Cd_f (void);


// -------------------
// MCIErrorMessageBox
// Retrieve error message corresponding to return value from
//  mciSendCommand() or mciSenString()
// -------------------
static VOID MCIErrorMessageBox (MCIERROR iErrorCode)
{
	char szErrorText[128];
	if (!mciGetErrorStringA (iErrorCode, szErrorText, sizeof (szErrorText)))
		wsprintfA(szErrorText,"MCI CD Audio Unknow Error #%d\n", iErrorCode);
	CONS_Printf (szErrorText);
	/*MessageBox (GetActiveWindow(), szTemp+1, "LEGACY",
				MB_OK | MB_ICONSTOP);*/
}


// --------
// CD_Reset
// --------
static void CD_Reset (void)
{
	// no win32 equivalent
	//faB: for DOS, some odd drivers like to be reset sometimes.. useless in MCI I guess
}


// ----------------
// CD_ReadTrackInfo
// Read in number of tracks, and length of each track in minutes/seconds
// returns true if error
// ----------------
static BOOL CD_ReadTrackInfo (void)
{
	int         i;
	int         nTrackLength;
	MCIERROR    iErr;

	m_nTracksCount = 0;

	m_MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
	if (iErr)
	{
		MCIErrorMessageBox (iErr);
		return FALSE;
	}
	m_nTracksCount = (int)m_MCIStatus.dwReturn;
	if (m_nTracksCount > MAX_CD_TRACKS)
		m_nTracksCount = MAX_CD_TRACKS;

	for (i = 0; i < m_nTracksCount; i++)
	{
		m_MCIStatus.dwTrack = (DWORD)(i+1);
		m_MCIStatus.dwItem = MCI_STATUS_LENGTH;
		iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_TRACK|MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
		if (iErr)
		{
			MCIErrorMessageBox (iErr);
			return FALSE;
		}
		nTrackLength = (DWORD)(MCI_MSF_MINUTE(m_MCIStatus.dwReturn)*60 + MCI_MSF_SECOND(m_MCIStatus.dwReturn));
		m_nTracks[i].Length = nTrackLength;

		m_MCIStatus.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
		iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_TRACK|MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
		if (iErr)
		{
			MCIErrorMessageBox (iErr);
			return FALSE;
		}
		m_nTracks[i].IsAudio = (m_MCIStatus.dwReturn == MCI_CDA_TRACK_AUDIO);
	}

	return TRUE;
}


// ------------
// CD_TotalTime
// returns total time for all audio tracks in seconds
// ------------
static int CD_TotalTime (void)
{
	int nTotalLength = 0;
	int nTrack;
	for (nTrack = 0; nTrack < m_nTracksCount; nTrack++)
	{
		if (m_nTracks[nTrack].IsAudio)
			nTotalLength = nTotalLength + m_nTracks[nTrack].Length;
	}
	return nTotalLength;
}


//======================================================================
//                   CD AUDIO MUSIC SUBSYSTEM
//======================================================================

UINT8  cdaudio_started = 0;   // for system startup/shutdown

static boolean cdPlaying = false;
static int     cdPlayTrack;         // when cdPlaying is true
static boolean cdLooping = false;
static UINT8   cdRemap[MAX_CD_TRACKS];
static boolean cdEnabled = true;      // cd info available
static boolean cdValid;             // true when last cd audio info was ok
static boolean wasPlaying;
//static int     cdVolume = 0;          // current cd volume (0-31)

// 0-31 like Music & Sfx, though CD hardware volume is 0-255.
consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};

// allow Update for next/loop track
// some crap cd drivers take up to
// a second for a simple 'busy' check..
// (on those Update can be disabled)
consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};

// hour,minutes,seconds
static char *hms(int seconds)
{
	int hours, minutes;
	static char s[9];

	minutes = seconds / 60;
	seconds %= 60;
	hours = minutes / 60;
	minutes %= 60;
	if (hours > 0)
		sprintf (s, "%d:%02d:%02d", hours, minutes, seconds);
	else
		sprintf (s, "%2d:%02d", minutes, seconds);
	return s;
}

static void Command_Cd_f (void)
{
	LPCSTR    s;
	int       i,j;

	if (!cdaudio_started)
		return;

	if (COM_Argc()<2)
	{
		CONS_Printf ("cd [on] [off] [remap] [reset] [open]\n"
		             "   [info] [play <track>] [loop <track>]\n"
		             "   [stop] [resume]\n");
		return;
	}

	s = COM_Argv(1);

	// activate cd music
	if (!strncmp(s,"on",2))
	{
		cdEnabled = true;
		return;
	}

	// stop/deactivate cd music
	if (!strncmp(s,"off",3))
	{
		if (cdPlaying)
			I_StopCD ();
		cdEnabled = false;
		return;
	}

	// remap tracks
	if (!strncmp(s,"remap",5))
	{
		i = (int)COM_Argc() - 2;
		if (i <= 0)
		{
			CONS_Printf ("CD tracks remapped in that order :\n");
			for (j = 1; j < MAX_CD_TRACKS; j++)
				if (cdRemap[j] != j)
					CONS_Printf (" %2d -> %2d\n", j, cdRemap[j]);
			return;
		}
		for (j = 1; j <= i; j++)
			cdRemap[j] = (UINT8)atoi (COM_Argv (j+1));
		return;
	}

	// reset the CD driver, useful on some odd cd's
	if (!strncmp(s,"reset",5))
	{
		cdEnabled = true;
		if (cdPlaying)
			I_StopCD ();
		for (i = 0; i < MAX_CD_TRACKS; i++)
			cdRemap[i] = (UINT8)i;
		CD_Reset();
		cdValid = CD_ReadTrackInfo();
		return;
	}

	// any other command is not allowed until we could retrieve cd information
	if (!cdValid)
	{
		CONS_Printf ("CD is not ready.\n");
		return;
	}

	/* faB: not with MCI, didn't find it, useless anyway
	if (!strncmp(s,"open",4))
	{
		if (cdPlaying)
			I_StopCD ();
		bcd_open_door();
		cdValid = false;
		return;
	}*/

	if (!strncmp(s,"info",4))
	{
		if (!CD_ReadTrackInfo())
		{
			cdValid = false;
			return;
		}

		cdValid = true;

		if (m_nTracksCount <= 0)
			CONS_Printf ("No audio tracks\n");
		else
		{
			// display list of tracks
			// highlight current playing track
			for (i = 0; i < m_nTracksCount; i++)
			{
				CONS_Printf("%s%2d. %s  %s\n",
				            cdPlaying && (cdPlayTrack == i) ? "\2 " : " ",
				            i+1, m_nTracks[i].IsAudio ? "audio" : "data ",
				            hms(m_nTracks[i].Length));
			}
			CONS_Printf ("\2Total time : %s\n", hms(CD_TotalTime()));
		}
		if (cdPlaying)
		{
			CONS_Printf ("%s track : %d\n", cdLooping ? "looping" : "playing",
			             cdPlayTrack);
		}
		return;
	}

	if (!strncmp(s,"play",4))
	{
		I_PlayCD ((UINT8)atoi(COM_Argv (2)), false);
		return;
	}

	if (!strncmp(s,"stop",4))
	{
		I_StopCD ();
		return;
	}

	if (!strncmp(s,"loop",4))
	{
		I_PlayCD ((UINT8)atoi(COM_Argv (2)), true);
		return;
	}

	if (!strncmp(s,"resume",4))
	{
		I_ResumeCD ();
		return;
	}

	CONS_Printf ("cd command '%s' unknown\n", s);
}


// ------------
// I_ShutdownCD
// Shutdown CD Audio subsystem, release whatever was allocated
// ------------
void I_ShutdownCD (void)
{
	MCIERROR    iErr;

	if (!cdaudio_started)
		return;

	CONS_Printf("I_ShutdownCD()\n");

	I_StopCD();

	// closes MCI CD
	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_CLOSE, 0, 0);
	if (iErr)
		MCIErrorMessageBox (iErr);
}


// --------
// I_InitCD
// Init CD Audio subsystem
// --------
void I_InitCD (void)
{
	MCI_SET_PARMS   mciSet;
	MCIERROR    iErr;
	int         i;

	// We don't have an open device yet
	m_MCIOpen.wDeviceID = 0;
	m_nTracksCount = 0;

	cdaudio_started = false;

	m_MCIOpen.lpstrDeviceType = (LPCTSTR)MCI_DEVTYPE_CD_AUDIO;
	iErr = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID, (DWORD_PTR)&m_MCIOpen);
	if (iErr)
	{
		MCIErrorMessageBox (iErr);
		return;
	}

	// Set the time format to track/minute/second/frame (TMSF).
	mciSet.dwTimeFormat = MCI_FORMAT_TMSF;
	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&mciSet);
	if (iErr)
	{
		MCIErrorMessageBox (iErr);
		mciSendCommand(m_MCIOpen.wDeviceID, MCI_CLOSE, 0, 0);
		return;
	}

	I_AddExitFunc (I_ShutdownCD);
	cdaudio_started = true;

	CONS_Printf("I_InitCD: Init CD audio\n");

	// last saved in config.cfg
	i = cd_volume.value;
	//I_SetVolumeCD (0);   // initialize to 0 for some odd cd drivers
	I_SetVolumeCD (i);   // now set the last saved volume

	for (i = 0; i < MAX_CD_TRACKS; i++)
		cdRemap[i] = (UINT8)i;

	if (!CD_ReadTrackInfo())
	{
		CONS_Printf("\2I_InitCD: no CD in player.\n");
		cdEnabled = false;
		cdValid = false;
	}
	else
	{
		cdEnabled = true;
		cdValid = true;
	}

	COM_AddCommand ("cd", Command_Cd_f);
}



// loop/go to next track when track is finished (if cd_update var is true)
// update the volume when it has changed (from console/menu)
void I_UpdateCD (void)
{
		/// \todo check for cd change and restart music ?
}


//
void I_PlayCD (UINT8 nTrack, UINT8 bLooping)
{
	MCI_PLAY_PARMS  mciPlay;
	MCIERROR        iErr;

	if (!cdaudio_started || !cdEnabled)
		return;

	//faB: try again if it didn't work (just free the user of typing 'cd reset' command)
	if (!cdValid)
		cdValid = CD_ReadTrackInfo();
	if (!cdValid)
		return;

	// tracks start at 0 in the code..
	nTrack--;
	if (nTrack < 0 || nTrack >= m_nTracksCount)
		nTrack = nTrack % m_nTracksCount;

	nTrack = cdRemap[nTrack];

	if (cdPlaying)
	{
		if (cdPlayTrack == nTrack)
			return;
		I_StopCD ();
	}

	cdPlayTrack = nTrack;

	if (!m_nTracks[nTrack].IsAudio)
	{
		//CONS_Printf ("\2CD Play: not an audio track\n"); // Tails 03-25-2001
		return;
	}

	cdLooping = bLooping;

	//faB: stop MIDI music, MIDI music will restart if volume is upped later
	cv_digmusicvolume.value = 0;
	cv_midimusicvolume.value = 0;
	I_StopSong (0);

	//faB: I don't use the notify message, I'm trying to minimize the delay
	mciPlay.dwCallback = (DWORD_PTR)((size_t)hWndMain);
	mciPlay.dwFrom = MCI_MAKE_TMSF(nTrack+1, 0, 0, 0);
	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_PLAY, MCI_FROM|MCI_NOTIFY, (DWORD_PTR)&mciPlay);
	if (iErr)
	{
		MCIErrorMessageBox (iErr);
		cdValid = false;
		cdPlaying = false;
		return;
	}

	cdPlaying = true;
}


// pause cd music
void I_StopCD (void)
{
	MCIERROR    iErr;

	if (!cdaudio_started || !cdEnabled)
		return;

	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_PAUSE, MCI_WAIT, 0);
	if (iErr)
		MCIErrorMessageBox (iErr);
	else
	{
		wasPlaying = cdPlaying;
		cdPlaying = false;
	}
}


// continue after a pause
void I_ResumeCD (void)
{
	MCIERROR    iErr;

	if (!cdaudio_started || !cdEnabled)
		return;

	if (!cdValid)
		return;

	if (!wasPlaying)
		return;

	iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_RESUME, MCI_WAIT, 0);
	if (iErr)
		MCIErrorMessageBox (iErr);
	else
		cdPlaying = true;
}


// volume : logical cd audio volume 0-31 (hardware is 0-255)
int I_SetVolumeCD (int volume)
{
	(void)volume;
	return 1;
}
