/*++

File: cdlib.c

    Read the AsimTunes (which is the AsimCDFS CDDA player) disc library.

    Creates listings on stdout and can also export track and program data
    in the widely used "ID files" format.

--*/

char *pcVersion = "$VER: CdLib 1.1 (16.04.2000) "
                  "by Wilhelm Nöker <wnoeker@t-online.de>";


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


typedef int BOOL;
#define FALSE   0
#define TRUE    1

char *pcIdxF = "AsimCDFS_Buffer:CD_Library.index";
char *pcDatF = "AsimCDFS_Buffer:CD_Library.data";
char *pcTrkF = "CdLib_Temp_Trk";
char *pcPrgF = "CdLib_Temp_Prg";
FILE *fpIdx, *fpDat;



/*++

Function: Help()

    Print command line usage.

--*/

void Help()
    {
    fprintf( stderr, "CdLib: list the AsimCDFS audio disc library\n" );
    fprintf( stderr, "Usage:\n  CdLib [options] >listfile\n" );
    fprintf( stderr, "where options are\n" );
    fprintf( stderr, "  -d    list discs\n" );
    fprintf( stderr, "  -t    list tracks\n" );
    fprintf( stderr, "  -v/-V verbose track listing\n" );
    fprintf( stderr, "  -u    show user settings\n" );
    fprintf( stderr, "  -i    create ID files (in the current dir.)\n" );
    }



/*++

Function: DiscInfo()

    Print information about one CD in the database.

Arguments:

    lRecord - record number, zero-based

    nFlags - option bitmap: 1=discs, 2=tracks, 4=user settings,
        8=create an ID (and PR) file

    nLevel - level of track listing verbosity: 1=add album, 2=add artist

Return Value:

    FALSE, if no such record exists.

--*/

BOOL DiscInfo( long lRecord, int nFlags, int nLevel )
    {
    long lOffset, lTotal, lPos = 0, lThirdPos=0, lFirstPos=0;
    int i;
    char caName[ 50 ], caTitle[ 50 ], caTrack[ 50 ], caBuf[ 50 ];
    unsigned char baPrg[ 256 ];
    unsigned char cMode, cTrks, cMin, cSec, cTick, cFlags, cPos;
    int nSkipped = 0;
    FILE *fpTrk = NULL, *fpPrg = NULL;

    if( fseek( fpIdx, 4 * lRecord, SEEK_SET ) != 0 )
        return FALSE;
    if( fread( &lOffset, sizeof( long ), 1, fpIdx ) < 1 )
        return FALSE;
    fseek( fpDat, lOffset, SEEK_SET );
    fread( caName, sizeof( char ), 41, fpDat );
    fread( caTitle, sizeof( char ), 34, fpDat );
    cFlags = fgetc( fpDat );
    cTrks = fgetc( fpDat );
    cMin  = fgetc( fpDat );
    cSec  = fgetc( fpDat );
    cTick  = fgetc( fpDat );
    lTotal = (cMin * 60 + cSec) * 75 + cTick;
    fseek( fpDat, 9, SEEK_CUR );
    cMode = fgetc( fpDat );
    if( nFlags & 1 )
        {                       /* print information about the disc */
        if( nFlags & 4 )
            {
            if( cFlags & 1 )
                printf( "A%c/%c%c ", " PRO"[ cMode ],
                        (cFlags & 2) ? 'S':'s', (cFlags & 4) ? 'R':'r' );
            else
                printf( "      " );
            }
        printf( "%2d:%02d ", cMin, cSec );
        printf( "%2d%23s ", cTrks, "" );
        printf( "%-30s %s", caTitle, caName );
        putchar( '\n' );
        }
    if( nFlags & 8 )
        {
        fpPrg = fopen( pcPrgF, "w" );
        memset( baPrg, 0, sizeof( baPrg ) );
        fpTrk = fopen( pcTrkF, "w" );
        fprintf( fpTrk, "%s\n%s\n", caName, caTitle );
        }
    if( nFlags & (2 | 8) )
        {                       /* examine ther track entries */
        fseek( fpDat, 34, SEEK_CUR );
        for( i = 0; i < cTrks; i++ )
            {
            fread( caTrack, sizeof( char ), 27, fpDat );
            lPos = fgetc( fpDat );
            lPos = (lPos << 8) | fgetc( fpDat );
            lPos = (lPos << 8) | fgetc( fpDat );
            cMin  = fgetc( fpDat );
            cSec  = fgetc( fpDat );
            cTick  = fgetc( fpDat );
            fseek( fpDat, 4, SEEK_CUR );
            cPos   = fgetc( fpDat );
            cFlags = fgetc( fpDat );
            fseek( fpDat, 15, SEEK_CUR );
            if( nFlags & 2 )
                {
                if( nFlags & 4 )
                    {
                    printf( ( cPos <= cTrks ) ? "%3d" : "   ", cPos );
                    printf( " %c ", (cFlags & 1) ? 'x':' ' );
                    }
                printf( "%2d:%02d ", cMin, cSec );
                printf( "%-25s ", caTrack );
                if( nLevel > 0 )
                    printf( "%-30s %s", caTitle, (nLevel > 1) ? caName : "" );
                putchar( '\n' );
                }
            if( nFlags & 8 )
                {
                fprintf( fpTrk, "%s\n", caTrack );  /* add an ID line */
                if( cMode == 3 )        /* i.e. "play in order" */
                    {
                    if( cPos <= cTrks ) /* prepare an ordered list */
                        baPrg[ cPos ] = i + 1;
                    }
                else
                    {
                    if( cFlags & 1 )
                        nSkipped++;
                    else                /* add a prg item */
                        fputc( i + 1, fpPrg );
                    }
                if( i == 0 )
                    lFirstPos = lPos;
                if( i == 2 )
                    lThirdPos = lPos;
                }
            }
        }
    if( nFlags & 8 )
        {                       /* cleanup for the ID file(s) */
        if( cMode == 3 )        /* dump the playlist that we built */
            for( i = 0; i < 256; i++ )
                if( baPrg[ i ] != 0 )
                    fputc( baPrg[ i ], fpPrg );
        lFirstPos = 0x96;       /* seems wrong, but it works */
        sprintf( caBuf, "ID%02d%06lX%06lX", cTrks,
                lThirdPos - lFirstPos, lTotal - lFirstPos );
        fprintf( stderr, "%ld: %s, %-30s\r", lRecord, caBuf, caTitle );
        fflush( stderr );
        /* Close and rename the temp files. */
        fclose( fpTrk );
        rename( pcTrkF, caBuf );
        strncpy( caBuf, "PR", 2 );
        fclose( fpPrg );
        if( cMode == 3 || nSkipped > 0 )
            rename( pcPrgF, caBuf );
        }

    return TRUE;
    }



/*++

Function: main()

    Uses unix style command line parsing, see Help() for explanation
    of the expected parameters.

--*/

int main( int argc, char *argv[] )
    {
    long lDiscs, lIdx;
    int nFlags = 0, nLevel = 0;
    char *pcTemp;

    while( --argc )
        {
        argv++;
        if( *argv[ 0 ] == '-' )
            switch( argv[ 0 ][ 1 ] )
                {
                case 'd':
                    nFlags |= 1;
                    break;
                case 't':
                    nFlags |= 2;
                    break;
                case 'u':
                    nFlags |= 4;
                    break;
                case 'i':
                    nFlags |= 8;
                    break;
                case 'v':
                    nLevel = 1;
                    break;
                case 'V':
                    nLevel = 2;
                    break;
                default:
                    Help();
                    return 5;
                }
        else
            {
            Help();
            return 5;
            }
        }

    /* Try to open the database files in the current directory first, */
    /* use absolute pathname only if that fails. */
    pcTemp = strchr( pcIdxF, ':' );
    fpIdx = pcTemp ? fopen( pcTemp + 1, "rb" ) : NULL;
    if( fpIdx == NULL )
        fpIdx = fopen( pcIdxF, "rb" );
    if( fpIdx == NULL )
        {
        fprintf( stderr, "file not found: \"%s\"\n", pcIdxF );
        return 10;
        }
    pcTemp = strchr( pcDatF, ':' );
    fpDat = pcTemp ? fopen( pcTemp + 1, "rb" ) : NULL;
    if( fpDat == NULL )
        fpDat = fopen( pcDatF, "rb" );
    if( fpDat == NULL )
        {
        fprintf( stderr, "file not found: \"%s\"\n", pcDatF );
        return 10;
        }
    fseek( fpIdx, 0, SEEK_END );
    lDiscs = ftell( fpIdx ) / 4;
    printf( "%ld audio CDs listed\n", lDiscs );
    if( nFlags > 0 )
        for( lIdx = 0; lIdx < lDiscs; lIdx++ )
            DiscInfo( lIdx, nFlags, nLevel );

    fclose( fpIdx );
    fclose( fpDat );

    return 0;
    }
