/******************************Module*Header*******************************\
* Module Name: database.c
*
* This module implements the database routines for the CD Audio app.
* The information is stored in the ini file "cdaudio.ini" which should
* be located in the nt\windows directory.
*
* Warning:
*   These functions ARE NOT THREAD safe.
*   The functions in this file MUST only be called on the UI thread.  Before
*   calling any of the functions the CALLER MUST call LockTableOfContents
*   for the specified cdrom device.
*
* Author:
*   Rick Turner (ricktu) 31-Jan-1992
*
*
* Revision History:
*
*   04-Aug-1992 (ricktu)    Incorperated routines from old cdaudio.c,
*                           and made work w/new child window framework.
*
*
* Copyright (c) 1993 - 1995 Microsoft Corporation.  All rights reserved.
\**************************************************************************/
#pragma warning( once : 4201 4214 )

#define NOOLE
#include <windows.h>
#include "playres.h"
#include "cdapi.h"
#include "cdplayer.h"
#include "database.h"
#include "literals.h"


#include <string.h>
#include <stdio.h>
#include <tchar.h>

#include "trklst.h"

#include "..\cdnet\cdnet.h"
#include "..\cdopt\cdopt.h"

/* -------------------------------------------------------------------------
** Private entry points
** -------------------------------------------------------------------------
*/
DWORD
ComputeOldDiscId(
    int cdrom
    );


BOOL
ReadEntry(
    int cdrom,
    DWORD dwId);


/*****************************Private*Routine******************************\
* ComputeOldDiscId
*
* This routine computes a unique ID based on the information
* in the table of contexts a given disc.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
DWORD
ComputeOldDiscId(
    int cdrom
    )
{
    int NumTracks,i;
    DWORD DiscId = 0;


    NumTracks = g_Devices[ cdrom ]->toc.LastTrack -
		g_Devices[ cdrom ]->toc.FirstTrack;

    for ( i = 0; i < NumTracks; i++ )  {
	DiscId += ( (TRACK_M(cdrom,i) << 16) +
		    (TRACK_S(cdrom,i) <<  8) +
		     TRACK_F(cdrom,i) );
    }

    return DiscId;

}


/******************************Public*Routine******************************\
* ComputeNewDiscId
*
* Just call mci to get the product ID.
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
DWORD
ComputeNewDiscId(
    int cdrom
    )
{
#ifdef USE_IOCTLS
    return ComputeOrgDiscId( cdrom );
#else

    MCI_INFO_PARMS  mciInfo;
    TCHAR           szBuffer[32];
    DWORD           dwRet;

    mciInfo.lpstrReturn = szBuffer;
    mciInfo.dwRetSize   = sizeof(szBuffer)/sizeof(TCHAR);

    dwRet = mciSendCommand( g_Devices[cdrom]->hCd, MCI_INFO,
			    MCI_INFO_MEDIA_IDENTITY,
			    (DWORD_PTR)(LPVOID)&mciInfo );

    if ( dwRet != MMSYSERR_NOERROR ) {
	return 0L;
    }

    _stscanf(szBuffer, TEXT("%ld"), &dwRet );

    return dwRet;

#endif
}



/*****************************Private*Routine******************************\
* ComputeOrgDiscId
*
* This routine computes a unique ID based on the information
* in the table of contexts a given disc. This is done by taking
* the TMSF value for each track and XOR'ing it with the previous
* quantity shifted left one bit.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
DWORD
ComputeOrgDiscId(
    int cdrom
    )
{
    int NumTracks,i;
    DWORD DiscId = 0;


    NumTracks = g_Devices[ cdrom ]->toc.LastTrack -
		g_Devices[ cdrom ]->toc.FirstTrack + 1;

    for ( i = 0; i < NumTracks; i++ ) {
	DiscId = (DiscId << 1) ^
		   ((i<<24) +
		    (TRACK_M(cdrom,i) << 16) +
		    (TRACK_S(cdrom,i) <<  8) +
		     TRACK_F(cdrom,i) );
    }

    return DiscId;

}


/*****************************Private*Routine******************************\
* ErasePlayList
*
* Erases the current play list.  This includes freeing the memory
* for the nodes in the play list, and resetting the current track
* pointer to NULL.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
VOID
ErasePlayList(
    int cdrom
    )
{

    PTRACK_PLAY temp, temp1;

    //
    // Free memory for each track in play list
    //

    temp = PLAYLIST( cdrom );
    while (temp!=NULL) {

	temp1 = temp->nextplay;
	LocalFree( (HLOCAL)temp );
	temp = temp1;

    }

    //
    // Reset pointers
    //

    PLAYLIST( cdrom ) = NULL;
    CURRTRACK( cdrom ) = NULL;
}



/******************************Public*Routine******************************\
* EraseSaveList
*
* Erases the current save list.  This includes freeing the memory
* for the nodes in the save list.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
VOID
EraseSaveList(
    int cdrom
    )
{

    PTRACK_PLAY temp, temp1;

    //
    // Free memory for each track in play list
    //


    temp = SAVELIST( cdrom );
    while ( temp != NULL ) {

	temp1 = temp->nextplay;
	LocalFree( (HLOCAL)temp );
	temp = temp1;

    }

    //
    // Reset pointers
    //

    SAVELIST( cdrom ) = NULL;

}


/*****************************Private*Routine******************************\
* EraseTrackList
*
* Erases the current track list.  This includes freeing the memory
* for the nodes in the track list, and resetting the track list
* pointer to NULL.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
VOID
EraseTrackList(
    int cdrom
    )
{

    PTRACK_INF temp, temp1;

    //
    // Free memory for each track in track list
    //

    temp = ALLTRACKS( cdrom );
    while ( temp != NULL ) {

	temp1 = temp->next;
	LocalFree( (HLOCAL)temp );
	temp = temp1;
    }

    //
    // Reset pointers
    //

    ALLTRACKS( cdrom ) = NULL;

}

/******************************Public*Routine******************************\
* CopyPlayList
*
* Returns a copy of the playlist pointed to by p.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
PTRACK_PLAY
CopyPlayList(
    PTRACK_PLAY p
    )
{

    PTRACK_PLAY t,t1,tend,tret;

    tret = tend = NULL;

    //
    // Duplicate list pointed to by p.
    //

    t = p;
    while( t!=NULL ) {

	t1 = (PTRACK_PLAY)AllocMemory( sizeof(TRACK_PLAY) );
	t1->TocIndex = t->TocIndex;
	t1->min = t->min;
	t1->sec = t->sec;
	t1->nextplay = NULL;
	t1->prevplay = tend;

	if (tret==NULL) {

	    tret = tend = t1;
	}
	else {

	    tend->nextplay = t1;
	    tend = t1;
	}

	t = t->nextplay;

    }

    return(tret);

}


/*****************************Private*Routine******************************\
* ResetPlayList
*
* Resets play order for the disc.  Used to initialize/reset
* the play list.  This is done by reseting the doubly-linked list
* of tracks in the g_Devices[...]->CdInfo.PlayList.[prevplay,nextplay]
* pointers.  All the tracks on the CD are stored in a singly linked list
* pointed to by g_Devices[...]->CdInfo.AllTracks pointer.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
VOID
ResetPlayList(
     int cdrom
    )
{
    PTRACK_INF t;
    PTRACK_PLAY temp, prev;


    //
    // Kill old play list
    //

    ErasePlayList( cdrom );
    EraseSaveList( cdrom );

    //
    // Duplicate each node in AllTracks and insert in-order
    // in SaveList list.  The SaveList is the master which is
    // used for the playlist.
    //

    t = ALLTRACKS( cdrom );
    prev = NULL;

    while (t!=NULL) {

	temp = (PTRACK_PLAY)AllocMemory( sizeof(TRACK_PLAY) );

	temp->TocIndex = t->TocIndex;
	temp->min = 0;
	temp->sec = 0;
	temp->prevplay = prev;
	temp->nextplay = NULL;

	if (prev!=NULL) {

	    prev->nextplay = temp;
	}
	else {

	    SAVELIST( cdrom ) = temp;
	}

	prev = temp;
	t=t->next;

    }

    PLAYLIST( cdrom ) = CopyPlayList( SAVELIST( cdrom) );
}

void CreateNewEntry(int cdrom, DWORD key, PCDROM_TOC lpTOC)
{
    /*
    ** This is a new entry, fill it in but don't store it in the database.
    */

    PTRACK_INF  temp, temp1;
    PTRACK_PLAY pTempCurrTrack = CURRTRACK(cdrom);
    int i, num;
    int nSaveTrack = -1;

    if (CURRTRACK(cdrom))
    {
        nSaveTrack = CURRTRACK(cdrom)->TocIndex;
    }

    ErasePlayList( cdrom );
    EraseSaveList( cdrom );
    EraseTrackList( cdrom );

    g_Devices[ cdrom ]->CdInfo.iFrameOffset = NEW_FRAMEOFFSET;
	g_Devices[ cdrom ]->CdInfo.IsVirginCd = TRUE;
    g_Devices[ cdrom ]->CdInfo.Id = key;

	wsprintf( (LPTSTR)ARTIST( cdrom ), IdStr( STR_NEW_ARTIST ) );
	wsprintf( (LPTSTR)TITLE( cdrom ), IdStr( STR_NEW_TITLE ) );

	if (lpTOC)
    {
        NUMTRACKS( cdrom ) = num = lpTOC->LastTrack - lpTOC->FirstTrack + 1;
    }
    else
    {
        num = NUMTRACKS( cdrom );
    }


	/*
	** Create generic playlist, which is all audio tracks
	** played in the order they are on the CD.  First, create
	** a singly linked list that contains all the tracks.
	** Then, create a double linked list, using the nodes of
	** from the single linked list for the play list.
	*/


	for( i = 0; i < num; i++ ) {

	    /*
	    ** Create storage for track
	    */

	    temp = (PTRACK_INF)AllocMemory( sizeof(TRACK_INF) );

	    /*
	    ** Initialize information (storage already ZERO initialized)
	    */

	    wsprintf( (LPTSTR)temp->name, IdStr( STR_INIT_TRACK ), i+1 );
	    temp->TocIndex = i;
	    temp->next = NULL;

	    /*
	    ** Add node to singly linked list of all tracks
	    */

	    if (i == 0) {

		temp1 = ALLTRACKS( cdrom ) = temp;
	    }
	    else {

		temp1->next = temp;
		temp1 = temp;
	    }
	}

	/*
	** Generate generic play list (all tracks in order)
	*/

	ResetPlayList( cdrom );

    if (nSaveTrack > -1)
    {
	    PTRACK_PLAY  playlist;
	    for( playlist = PLAYLIST(cdrom);
             playlist != NULL;
             playlist = playlist->nextplay )
	    {
            if (playlist->TocIndex == nSaveTrack)
            {
                CURRTRACK(cdrom) = playlist;
            }
        }
    }
}

void RecomputePlayTimes(int dCdrom)
{
    /*
    ** Compute PLAY length
    */
    PTRACK_PLAY pp;
    int m, s, mtemp, stemp;
    
    m = s = 0;
    for( pp = PLAYLIST(dCdrom); pp != NULL; pp = pp->nextplay )
    {

        FigureTrackTime( dCdrom, pp->TocIndex, &mtemp, &stemp );

        m+=mtemp;
        s+=stemp;

        pp->min = mtemp;
        pp->sec = stemp;
    }

    //also refresh the savelist
    for( pp = SAVELIST(dCdrom); pp != NULL; pp = pp->nextplay )
    {

        FigureTrackTime( dCdrom, pp->TocIndex, &mtemp, &stemp );

        pp->min = mtemp;
        pp->sec = stemp;
    }

    m += (s / 60);
    s =  (s % 60);

    CDTIME(dCdrom).TotalMin = m;
    CDTIME(dCdrom).TotalSec = s;

    /*
    ** Make sure that the track time displayed in the LED and the
    ** status bar is correct.  If we have a current track and the
    ** CD is playing or paused then everything is OK.  Otherwise, we
    ** have to reset the track times.
    */
    if ( CURRTRACK( dCdrom ) != NULL ) {

        if ( STATE(dCdrom) & CD_STOPPED ) {

            CDTIME(dCdrom).TrackTotalMin = CURRTRACK( dCdrom )->min;
            CDTIME(dCdrom).TrackRemMin   = CURRTRACK( dCdrom )->min;

            CDTIME(dCdrom).TrackTotalSec = CURRTRACK( dCdrom )->sec;
            CDTIME(dCdrom).TrackRemSec   = CURRTRACK( dCdrom )->sec;
        }

    }
    else {

        CDTIME(dCdrom).TrackTotalMin = 0;
        CDTIME(dCdrom).TrackRemMin   = 0;
        CDTIME(dCdrom).TrackTotalSec = 0;
        CDTIME(dCdrom).TrackRemSec   = 0;
    }

    if (dCdrom == g_CurrCdrom)
    {
        UpdateDisplay( DISPLAY_UPD_DISC_TIME );
    }
}

BOOL GetInternetDatabase(int cdrom, DWORD key, BOOL fHitNet, BOOL fManual, HWND hwndCallback, void* pData)
{
    if (!g_fBlockNetPrompt)
    {
        if (fHitNet)
        {
            ICDNet* pICDNet = NULL;
            if (SUCCEEDED(CDNET_CreateInstance(NULL, IID_ICDNet, (void**)&pICDNet)))
            {
		            pICDNet->SetOptionsAndData((void*)g_pSink->GetOptions(),(void*)g_pSink->GetData());
                    pICDNet->Download(g_Devices[ cdrom ]->hCd,g_Devices[cdrom]->drive,key,(LPCDTITLE)pData,fManual,hwndCallback);
                    pICDNet->Release();
            }
            return FALSE; //couldn't find title just now, catch it on the callback
        }
    }

    if ( ReadEntry(cdrom, key))
    {
        return TRUE;
    }

    if (!fHitNet)
    {
        CreateNewEntry(cdrom, key, NULL);
        RecomputePlayTimes(cdrom);
    }

    return FALSE;
}

/*****************************Private*Routine******************************\
* ReadEntry
*
* Try to read entry for new disc from  database ini file.
* The section name we are trying to read is a hex
* value of the disc id.  If the sections is found,
* fill in the data for the disc in the cdrom drive.
*
* This function uses over 16K of stack space !!
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
BOOL
ReadEntry(
    int cdrom,
    DWORD dwId)
{
    UINT        i;
    int         numtracks, numplay;
    PTRACK_INF  temp, curr;
    PTRACK_PLAY temp1, prev;
    BOOL        fRewriteEntry = FALSE;
    LPCDDATA    pData = NULL;

    g_Devices[ cdrom ]->CdInfo.iFrameOffset = NEW_FRAMEOFFSET;

    pData = (LPCDDATA)g_pSink->GetData();
    if( !pData )
    {
        //Can't create the options object, so fail
        return (FALSE);
    }

    pData->AddRef();
    
    //
    // Try to read in title from the options database
    //

    if (!pData->QueryTitle(dwId))
    {
        pData->Release();
        return (FALSE);
    }

    //
    // We found an entry for this disc, so copy all the information
    // from the title database

    LPCDTITLE pCDTitle = NULL;

    if (FAILED(pData->LockTitle(&pCDTitle,dwId)))
    {
        pData->Release();
        return FALSE;
    }

    _tcscpy(g_Devices[ cdrom ]->CdInfo.Title,pCDTitle->szTitle); 

    numtracks = pCDTitle->dwNumTracks;

    // Make sure there is at least one track!!!
    if (0 == numtracks)
    {
    	fRewriteEntry = TRUE;
    }

    g_Devices[ cdrom ]->CdInfo.NumTracks = numtracks;
    _tcscpy(g_Devices[ cdrom ]->CdInfo.Artist,pCDTitle->szArtist);

    //
    // Validate the stored track numbers
    //
    if (g_Devices[cdrom]->fIsTocValid)
    {
	    int    maxTracks;  // validate the high point in database

	    maxTracks = g_Devices[cdrom]->toc.LastTrack;

	    if (numtracks > maxTracks)
        {
	        // Current database contains invalid data
	        // this can result in the CD not playing at all as the end
	        // point is likely to be invalid
	        g_Devices[ cdrom ]->CdInfo.NumTracks
	          = numtracks
	          = maxTracks;
	        fRewriteEntry = TRUE;
	    }
    }

    //
    // Read the track list information
    //

    for (i=0, curr = NULL; i < (UINT)numtracks; i++)
    {
	    temp = (PTRACK_INF)AllocMemory( sizeof(TRACK_INF) );
	    temp->TocIndex = i;
	    temp->next = NULL;

        _tcscpy(temp->name,pCDTitle->pTrackTable[i].szName);

	    if (curr==NULL)
        {
	        ALLTRACKS( cdrom ) = curr = temp;

	    } else
        {

	        curr->next = temp;
	        curr = temp;

	    }
    }

    // Make sure there is at least one entry in TRACK list
    if (ALLTRACKS(cdrom) == NULL)
    {
	    fRewriteEntry = TRUE;
    }

    //
    // if we detected a problem in the ini file, or the entry is an
    // old format, rewrite the section.
    //

    if (fRewriteEntry)
    {
	    //
	    // Generate generic play list (all tracks in order)
	    //

	    ResetPlayList( cdrom );

    }
    else
    {
	    //
	    // Read play list (order) information and construct play list doubly
	    // linked list
	    //

	    numplay = pCDTitle->dwNumPlay;
        if (numplay == 0)
        {
            numplay = pCDTitle->dwNumTracks;
        }

	    prev = NULL;

        int iCurrTrack = -1;

        if (CURRTRACK(cdrom)!=NULL)
        {
            iCurrTrack = CURRTRACK(cdrom)->TocIndex;
        }

        EraseSaveList(cdrom);
        ErasePlayList(cdrom);

	    for (int tr_index = 0; tr_index < numplay; tr_index++)
        {
	        /*
	        ** Assert that i is a valid index.
	        **  ie 0 <= i <= (numtracks - 1)
	        */

            if (pCDTitle->dwNumPlay > 0)
            {
                i = pCDTitle->pPlayList[tr_index];
            }
            else
            {
                i = tr_index;
            }

	        i = min( ((UINT)numtracks - 1), max( 0, i ) );

	        temp1 = (PTRACK_PLAY)AllocMemory( sizeof(TRACK_PLAY) );
	        temp1->TocIndex = (int)i;
	        temp1->min = 0;
	        temp1->sec = 0;
	        temp1->prevplay = prev;
	        temp1->nextplay = NULL;

	        if (prev==NULL)
            {
    		    SAVELIST( cdrom ) = temp1;
	        }
            else
            {
    		    prev->nextplay  = temp1;
	        }
	        prev = temp1;
	    }

	    // Make sure there is at least one entry in SAVED list
	    if (SAVELIST(cdrom) == NULL)
	    {
	        // Nope, use default list instead
	        ResetPlayList( cdrom );
	    }

	    PLAYLIST( cdrom ) = CopyPlayList( SAVELIST( cdrom ) );

        //reset current track if necessary
        if (iCurrTrack != -1)
        {
            BOOL fFound = FALSE;

            for (PTRACK_PLAY pp = PLAYLIST(cdrom); pp != NULL; pp = pp->nextplay )
            {
                if ( pp->TocIndex == iCurrTrack )
                {
                    fFound = TRUE;
                    break;
                }
            } //end for

            if (fFound)
            {
                CURRTRACK(cdrom) = pp;
            }
            else
            {
                /*
                ** If the track was not found in the new track list and this
                ** cd is currently playing then stop it.
                */
                if ( (STATE(cdrom) & (CD_PLAYING | CD_PAUSED)) )
                {

                    SendDlgItemMessage( g_hwndApp, IDM_PLAYBAR_STOP,
                                        WM_LBUTTONDOWN, 1, 0 );

                    SendDlgItemMessage( g_hwndApp, IDM_PLAYBAR_STOP,
                                        WM_LBUTTONUP, 1, 0 );
                }

                CURRTRACK(cdrom) = PLAYLIST(cdrom);
            }
        }

        /*
        ** If we were previously in "Random" mode shuffle the new
        ** playlist.
        */
        if (!g_fSelectedOrder)
        {
            ComputeSingleShufflePlayList(cdrom);
        }

        RecomputePlayTimes(cdrom);
    }

    //unlock title data, don't persist it
    pData->UnlockTitle(pCDTitle,FALSE);
    pData->Release();

    return TRUE;
}

/******************************Public*Routine******************************\
* AddFindEntry
*
* Search the database file for the current disc,  if found, read the
* information, otherwise, generate some default artist and track names etc.
* but don't store this in the database.  A new entry is only added to the
* database after the user has used the "Edit Track Titles" dialog box.
*
* The design of this function is complicated by the fact that we have to
* support two previous attempts at generating CDplayer keys.  Also, we try
* to support the MusicBox key format now that it is compatible with the
* new CDplayer format.
*
*
* History:
* 18-11-93 - ricktu - Created
*
\**************************************************************************/
VOID
AddFindEntry(
    int cdrom,
    DWORD key,
    PCDROM_TOC lpTOC
    )
{
    /*
    ** Kill off old PlayList, Save lists if they exists.
    */

    ErasePlayList( cdrom );
    EraseSaveList( cdrom );
    EraseTrackList( cdrom );

    /*
    ** Check ini file for an existing entry
    **
    ** First Look in cdplayer.ini using the new key, return if found
    */

    // We initialize this field early, otherwise ReadEntry will not be
    // able to save any new information (or lose invalid).
    g_Devices[ cdrom ]->CdInfo.save = TRUE;

    if ( ReadEntry(cdrom, key) )
    {
	    return;
    }

    /*
    ** Initialize these fields
    */
    g_Devices[ cdrom ]->CdInfo.IsVirginCd = FALSE;
    g_Devices[ cdrom ]->CdInfo.Id = key;

	/*
	** dstewart: Try to get the info from the net
	*/
	if ( GetInternetDatabase(cdrom, key, TRUE, FALSE, GetParent(g_hwndApp),NULL) )
	{
        return;
    }
	/*
	** end dstewart
	*/

    CreateNewEntry(cdrom,key,lpTOC);
}