//============================================================================
//
//  project:    freedb
//  file:       extract-records.c
//  author:     Andrew Smith
//  date:       2004-12-11
//  language:   C
//
//  NOTES
//
//  FreeDB music CD database conversion.
//
//  Copyright 2004, Andrew Smith <freedb@asmith.id.au>
//
//----------------------------------------------------------------------------
//
//  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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//============================================================================


#include <stdio.h>

#define kMaxBuffer 1000000

enum tBoolean {FALSE, TRUE};

enum tFields
{
    kLength, kRevision, kProcessor, kSubmitter,
    kDiscId, kDArtist, kDTitle, kDYear, kDGenre, kExtD,
    kFrame, kTArtist, kTTitle, kExtT,
    kMaxField
};

enum tErrors
{
    kLineError = 1,
    kFileError,
    kFieldError,
    kBufferError,
    kCommentError
};

enum tTolerance {kNeeded, kEolOk, kEofOk};

static char *vNext, vBuffer[kMaxBuffer], *vFields[kMaxField];
static int  vField, vTrack, vCount = 0;


//----------------------------------------------------------------------------
// initialise variables

static void
Initialise(void)
{
    for (vField = 0; vField < kMaxField; ++vField)
        vFields[vField] = NULL;
    vNext = vBuffer;
    vTrack = -1;
    vCount++;
}


//----------------------------------------------------------------------------
// output records
// disc records have an empty track field

static void
Output(void)
{
    int aTrack;
    printf("\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
           vCount,
           vFields[kLength] ? vFields[kLength] : "",
           vFields[kRevision] ? vFields[kRevision] : "0",
           vFields[kProcessor] ? vFields[kProcessor] : "",
           vFields[kSubmitter] ? vFields[kSubmitter] : "",
           vFields[kDiscId] ? vFields[kDiscId] : "",
           vFields[kDTitle] ? vFields[kDTitle] : "",
           vFields[kDYear] ? vFields[kDYear] : "",
           vFields[kDGenre] ? vFields[kDGenre] : "",
           vFields[kExtD] ? vFields[kExtD] : "");

    for (aTrack = 0; aTrack <= vTrack; ++aTrack)
    {
        printf("%d\t%d\t%s\t%s\t%s\n",
               aTrack,
               vCount,
               vFields[kFrame] ? vFields[kFrame] : "",
               vFields[kTTitle] ? vFields[kTTitle] : "",
               vFields[kExtT] ? vFields[kExtT] : "");
        if (vFields[kFrame]) while (*vFields[kFrame]++);
        if (vFields[kTTitle]) while (*vFields[kTTitle]++);
        if (vFields[kExtT]) while (*vFields[kExtT]++);
    }

    Initialise();
}


//----------------------------------------------------------------------------
// handle an error

static void
HandleError(Error)
    enum tErrors Error;
{
    if (Error)
    {
        char *vThis;
        for (vThis = vBuffer; vThis < vNext; ++vThis)
            putchar(*vThis);
    }
    exit(Error);
}


//----------------------------------------------------------------------------
// retrieve the next character

static int
GetNext(vTolerance)
    enum tTolerance vTolerance;
{
    int vChar = getchar();

    switch (vChar)
    {
        case EOF:
            HandleError(vTolerance < kEofOk || vNext > vBuffer ? kFileError : 0);

        case '\n':
            if (vTolerance < kEolOk)
                HandleError(kLineError);

        default:
            *vNext = vChar;
            break;
    }
    return vChar;
}


//----------------------------------------------------------------------------
// commit a character, escaping special characters

static void
PutNext(void)
{
    static int vSlashF = FALSE;

    if (vNext >= vBuffer + kMaxBuffer - 2)
    {
        Output();
        HandleError(kBufferError);
    }

    // put out a pending back slash

    if (vSlashF)
    {
        vSlashF = FALSE;
        switch (*vNext)
        {
            case 't':
            case 'r':
            case 'n':
            case '\\':
                ++vNext;
                return;

            default:
                *(vNext+1) = *vNext;
                *vNext++ = '\\';
                break;
        }
    }

    // escape tabs, returns and backslashes
    // null terminate lines

    switch (*vNext)
    {
        case '\n':
            *vNext++ = 0;
            break;

        case '\t':
            *vNext++ = '\\';
            *vNext++ = 't';
            break;

        case '\r':
            *vNext++ = '\\';
            *vNext++ = 'r';
            break;

        case '\\':
            vSlashF = TRUE;

        default:
            ++vNext;
            break;
    }
}


//----------------------------------------------------------------------------
// save a line, escaping special characters

static void
CopyLine(void)
{
    do PutNext();
    while (GetNext(kEolOk) != '\n');
    PutNext();
}


//----------------------------------------------------------------------------
// discard a line

static void
SkipLine(void)
{
    while (GetNext(kEolOk) != '\n');
}


//----------------------------------------------------------------------------
// save a field

static void
CopyField(void)
{
    if (*vNext == '\n')
        PutNext();
    else
        CopyLine();
}


//----------------------------------------------------------------------------
// skip a disc field and output the rest of the line

static void
SkipDisc(aField)
    enum tFields aField;
{
    while (GetNext(kNeeded) != '=');

    if (aField == vField)
        --vNext;
    else
    {
        vFields[aField] = vNext;
        vField = aField;
    }

    GetNext(kEolOk);
    CopyField();
}


//----------------------------------------------------------------------------
// skip a track field and output the rest of the line

static void
SkipTrack(aField)
    enum tFields aField;
{
    int aTrack = 0;

    while (GetNext(kNeeded), *vNext < '0' || *vNext > '9');

    do aTrack = aTrack * 10 + (*vNext - '0');
    while (GetNext(kNeeded), *vNext >= '0' && *vNext <= '9');

    if (*vNext != '=')
        HandleError(kFieldError);

    if (aField == vField && aTrack == vTrack)
        --vNext;
    else
    {
        if (aField != vField)
        {
            vFields[aField] = vNext;
            vField = aField;
        }
        vTrack = aTrack;
    }

    GetNext(kEolOk);
    CopyField();
}


//----------------------------------------------------------------------------
// skip past a colon and white space*/

static int
SkipColon(aField)
    enum tFields aField;
{
    while (GetNext(kEolOk), *vNext != ':' && *vNext != '\n');

    if (*vNext == '\n')
        return FALSE;

    while (GetNext(kEolOk), *vNext == ' ' || *vNext == '\t');

    vFields[aField] = vNext;
    vField = aField;
    return TRUE;
}


//----------------------------------------------------------------------------
// send only a number

static void
CopyNumber(void)
{
    do PutNext();
    while (GetNext(kEolOk), *vNext >= '0' && *vNext <= '9');

    while (*vNext != '\n')
        GetNext(kEolOk);

    PutNext();
}


//----------------------------------------------------------------------------
// skip blanks and analyse a comment line

static void
SkipComment(void)
{
    while (GetNext(kEolOk), *vNext == ' ' || *vNext == '\t');
    switch (*vNext)
    {
        case 'D': //# Disc length:
            if (SkipColon(kLength))
                CopyNumber();
            break;

        case 'R': //# Revision:
            if (SkipColon(kRevision))
                CopyNumber();
            break;

        case 'P': //# Processed by:
            if (SkipColon(kProcessor))
                CopyField();
            break;

        case 'S': //# Submitted via:
            if (SkipColon(kSubmitter))
                CopyField();
            break;

        case '\n':
            break;

        default: //# [0-9]+
            if (*vNext >= '0' && *vNext <= '9')
            {
                if (vField != kFrame)
                {
                    vFields[kFrame] = vNext;
                    vField = kFrame;
                }
                CopyNumber();
            }
            else
                SkipLine();
            break;
    }
}


//----------------------------------------------------------------------------
// process a cddb file

static void
CopyFile(void)
{
    while (TRUE)
        switch (GetNext(kEofOk))
        {
            case '#':
                SkipComment();
                break;

            case 'D':
                switch (GetNext(kNeeded))
                {
                    case 'I': //DISCID=
                        SkipDisc(kDiscId);
                        break;

                    case 'T': //DTITLE[0-9]+=
                        SkipDisc(kDTitle);
                        break;

                    case 'Y': //DYEAR=
                        SkipDisc(kDYear);
                        break;

                    case 'G': //DGENRE=
                        SkipDisc(kDGenre);
                        break;

                    default:
                        HandleError(kFieldError);
                }
                break;

            case 'T': //TTITLE[0-9]+=
                SkipTrack(kTTitle);
                break;

            case 'E':
                GetNext(kNeeded);
                GetNext(kNeeded);
                switch (GetNext(kNeeded))
                {
                    case 'D': //EXTD=
                        SkipDisc(kExtD);
                        break;

                    case 'T': //EXTT=
                        SkipTrack(kExtT);
                        break;

                    default:
                        HandleError(kFieldError);
                }
                break;

            case 'P': //PLAYORDER=
                Output();
                SkipLine();
                return;

            default:
                SkipLine();
                break;
        }
}


//----------------------------------------------------------------------------
// search for the start of cddb file marker

extern int
main(void)
{
    Initialise();
    while (TRUE)
    {
        while (TRUE)
            if (GetNext(kEofOk) == '#' &&
                GetNext(kEofOk) == ' ' &&
                GetNext(kEofOk) == 'x' &&
                GetNext(kEofOk) == 'm' &&
                GetNext(kEofOk) == 'c' &&
                GetNext(kEofOk) == 'd')
                break;
        SkipLine();
        CopyFile();
    }
}


//============================================================================
