/*
 *  libMirage: CDI image: parser
 *  Copyright (C) 2007-2026 Rok Mandeljc
 *
 *  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 "image-cdi.h"

#define __debug__ "CDI-Parser"


/**********************************************************************\
 *                  Object and its private structure                  *
\**********************************************************************/
struct _MirageParserCdiPrivate
{
    MirageDisc *disc;

    MirageStream *cdi_stream;

    gboolean medium_type_set;

    guint64 cur_offset; /* Current offset within track file */

    guint8 *cdi_data;
    guint8 *cur_ptr;
};


G_DEFINE_DYNAMIC_TYPE_EXTENDED(
    MirageParserCdi,
    mirage_parser_cdi,
    MIRAGE_TYPE_PARSER,
    0,
    G_ADD_PRIVATE_DYNAMIC(MirageParserCdi)
)

void mirage_parser_cdi_type_register (GTypeModule *type_module)
{
    mirage_parser_cdi_register_type(type_module);
}


/* NOTE: as far as my experiments show, descriptor has the following structure:
 *  - number of sessions: first byte
 *  - session descriptor: 15 bytes; one for every session, followed by track
 *    descriptors for tracks in that session
 *  - track descriptor: 228 bytes; this is "bare" track descriptor length,
 *    meaning it doesn't account for filename length, nor for index descriptors,
 *    nor CD-Text block... even though, it should be noted that seemingly there
 *    are always at least two index entries present
 *  - at the end of session/track descriptors, there seems to be another session
 *    descriptor, with 0 tracks
 *  - disc descriptor: 114 bytes; located at the end; this is also "bare" length,
 *    and it does include the descriptor lenght field at the end of file
 *
 *  -- Rok */

/* Self-explanatory */
const gboolean WHINE_ON_UNEXPECTED = TRUE;

typedef struct
{
    gint offset;
    gint expected;
} ExpectedField;

static void mirage_parser_cdi_whine_on_unexpected (MirageParserCdi *self, guint8 *data, ExpectedField *fields, gint fields_len, gchar *func_name, gchar *extra_comment)
{
    for (gint z = 0; z < fields_len; z++) {
        if (data[fields[z].offset] != fields[z].expected) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: data[%i] = 0x%X (expected 0x%X); extra comment: %s; seems we have a problem there, Dave...", func_name, fields[z].offset, data[fields[z].offset], fields[z].expected, extra_comment);
        }
    }
}

static void mirage_parser_cdi_decode_medium_type (MirageParserCdi *self, gint medium_type)
{
    /* Decode and set medium type only if we haven't done it yet */
    if (!self->priv->medium_type_set) {
        switch (medium_type) {
            case 0x98: {
                /* CD-ROM */
                mirage_disc_set_medium_type(self->priv->disc, MIRAGE_MEDIUM_CD);
                break;
            }
            case 0x38: {
                /* DVD-ROM */
                mirage_disc_set_medium_type(self->priv->disc, MIRAGE_MEDIUM_DVD);
                break;
            }
            default: {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: invalid medium type: 0x%X!", __debug__, medium_type);
            }
        }

        self->priv->medium_type_set = TRUE;
    }
}

static gboolean mirage_parser_cdi_decode_track_mode (MirageParserCdi *self, gint raw_mode, gint *decoded_mode, gint *main_format, GError **error)
{
    /* Simple; raw mode represents track mode. And if it happens to be audio, guess
     * what the data format will be? */
    switch (raw_mode) {
        case 0: {
            *decoded_mode = MIRAGE_SECTOR_AUDIO;
            *main_format = MIRAGE_MAIN_DATA_FORMAT_AUDIO;
            break;
        }
        case 1: {
            *decoded_mode = MIRAGE_SECTOR_MODE1;
            *main_format = MIRAGE_MAIN_DATA_FORMAT_DATA;
            break;
        }
        case 2: {
            *decoded_mode = MIRAGE_SECTOR_MODE2_MIXED;
            *main_format = MIRAGE_MAIN_DATA_FORMAT_DATA;
            break;
        }
        default: {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: invalid track mode: %d!", __debug__, raw_mode);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_PARSER_ERROR, Q_("Invalid track mode: %d!"), raw_mode);
            return FALSE;
        }
    }

    return TRUE;
}


static gboolean mirage_parser_cdi_decode_read_mode (MirageParserCdi *self, gint read_mode, gint *main_size, gint *subchannel_size, gint *subchannel_format, GError **error)
{
    switch (read_mode) {
        case 0: {
            /* 2048-byte sectors (valid only with Mode 1 tracks) */
            *main_size = 2048;
            break;
        }
        case 1: {
            /* 2336-byte sectors (valid only for Mode 2 tracks) */
            *main_size = 2336;
            break;
        }
        case 2: {
            /* 2352-byte sectors (Audio tracks or any other read in raw mode) */
            *main_size = 2352;
            break;
        }
        case 3: {
            /* 2352+16-byte sectors (any track read raw + Q subchannel) */
            *main_size = 2352;
            *subchannel_size = 16;
            *subchannel_format = MIRAGE_SUBCHANNEL_DATA_FORMAT_Q16 | MIRAGE_SUBCHANNEL_DATA_FORMAT_INTERNAL; /* Q, internal */
            break;
        }
        case 4: {
            /* 2352+96-byte sectors (any track read raw + PW subchannel) */
            *main_size = 2352;
            *subchannel_size = 96;
            *subchannel_format = MIRAGE_SUBCHANNEL_DATA_FORMAT_PW96_INTERLEAVED | MIRAGE_SUBCHANNEL_DATA_FORMAT_INTERNAL; /* PW96 interleaved, internal */
            break;
        }
        default: {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: invalid read mode: %d!", __debug__, read_mode);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_PARSER_ERROR, Q_("Invalid read mode: %d!"), read_mode);
            return FALSE;
        }
    }

    return TRUE;
}

static gint mirage_parser_cdi_decode_session_type (MirageParserCdi *self, gint raw_session_type)
{
    switch (raw_session_type) {
        case 0: return MIRAGE_SESSION_CDDA; /* CD-DA */
        case 1: return MIRAGE_SESSION_CDROM; /* CD-ROM */
        case 2: return MIRAGE_SESSION_CDROM_XA; /* CD-ROM XA */
    }

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: invalid session type: %d: returning CD-ROM!", __debug__, raw_session_type);
    return MIRAGE_SESSION_CDROM;
}

/* Function for parsing header that appears at the beginning of every track block
 * and at the beginning of the disc block */
static gboolean mirage_parser_cdi_parse_header (MirageParserCdi *self, GError **error G_GNUC_UNUSED)
{
    /* Recongised fields */
    gint num_all_tracks = 0;
    gint filename_length = 0;
    //gchar *filename = NULL;
    gint disc_capacity = 0;
    gint medium_type = 0;

    /* The header seems to consist of following:
     *  - 16 bytes forming what seems a fixed pattern
     *  - 1 byte representing filename length
     *  - filename of variable length
     *  - 29 bytes with fixed values
     *  - 2 bytes that form up medium type */

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0xFF},
            {1, 0xFF},
            {2, 0x00},
            {3, 0x00},
            {4, 0x01},
            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0xFF},
            {9, 0xFF},
            {10, 0xFF},
            {11, 0xFF},
            /* The following values vary; the last one is positively number of all
               tracks on the disc */
            /*{12, 0x64},
            {13, 0x05},
            {14, 0x2A},
            {15, 0x06}*/
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "Pre-filename fields");
    }

    num_all_tracks = MIRAGE_CAST_DATA(self->priv->cur_ptr, 15, guint8);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of all tracks: %d", __debug__, num_all_tracks);
    self->priv->cur_ptr += 16;

    /* 17th byte is filename length */
    filename_length = MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint8);
    self->priv->cur_ptr += sizeof(guint8);

    /* At 18th byte, filename starts (NOTE: it seems that the filename
     * can be in any encoding (e.g. Japanese), so priting here could
     * cause a crash... */
    //filename = MIRAGE_CAST_PTR(self->priv->cur_ptr, 0, gchar *);
    //MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: filename length: %d", __debug__, filename_length);
    //MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: filename: %s", __debug__, tmp_filename);
    self->priv->cur_ptr += filename_length;

    /* 31 bytes after filename aren't deciphered yet */

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x00},
            {2, 0x00},
            {3, 0x00},
            {4, 0x00},
            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0x00},
            {9, 0x00},
            {10, 0x00},
            {11, 0x02},
            {12, 0x00},
            {13, 0x00},
            {14, 0x00},
            {15, 0x00},
            {16, 0x00},
            {17, 0x00},
            {18, 0x00},
            {19, 0x00},
            {20, 0x00},
            {21, 0x00},
            {22, 0x80},
            /* Following fields almost positively represent disc capacity */
            /*{23, 0x40},
            {24, 0x7E},
            {25, 0x05},
            {26, 0x00},*/
            {27, 0x00},
            {28, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "Post-filename fields");
    }

    disc_capacity = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 23, guint32));
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: disc capacity: 0x%X", __debug__, disc_capacity);
    self->priv->cur_ptr += 29;

    /* Medium type */
    medium_type = GUINT16_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint16));
    self->priv->cur_ptr += sizeof(guint16);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: medium type: 0x%X", __debug__, medium_type);

    mirage_parser_cdi_decode_medium_type(self, medium_type);

    return TRUE;
}

static void mirage_parser_cdi_parse_cdtext (MirageParserCdi *self)
{
    /* It seems that each CD-TEXT block for track consists of 18 bytes, each (?)
     * denoting length of field it represents; if it's non-zero, it's followed by declared
     * size of bytes... */
    for (gint i = 0; i < 18; i++) {
        gint length = MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint8);
        self->priv->cur_ptr += sizeof(guint8);
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: field [%i] length: %i", __debug__, i, length);
        if (length) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: field [%i] data: %.*s", __debug__, i, length, self->priv->cur_ptr);
            self->priv->cur_ptr += length;
        }

    }
}

static gboolean mirage_parser_cdi_load_track (MirageParserCdi *self, GError **error)
{
    /* Recognized fields */
    gint num_indices = 0;
    gint *indices = NULL;
    gint track_mode = 0;
    gint track_idx = 0;
    gint session_idx = 0;
    gint num_cdtext_blocks = 0;
    gint start_address = 0;
    gint track_length = 0;
    gint read_mode = 0;
    gint track_ctl = 0;
    gchar *isrc = NULL;
    gint isrc_valid = 0;
    /* These are the ones I'm not quite certain of */
    gint session_type = 0;
    gint not_last_track = 0;
    gint address_at_the_end = 0;

    /******************************************************************\
     *                       Track data parsing                       *
    \******************************************************************/

    /* Header */
    if (!mirage_parser_cdi_parse_header(self, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to parse header!", __debug__);
        return FALSE;
    }

    /* Index fields follow */
    num_indices = GUINT16_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint16));
    self->priv->cur_ptr += sizeof(guint16);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of indices: %d", __debug__, num_indices);

    indices = g_new0(gint, num_indices);
    for (gint i = 0; i < num_indices; i++) {
        indices[i] = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
        self->priv->cur_ptr += sizeof(guint32);
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: index %i: 0x%X", __debug__, i, indices[i]);
    }

    /* Next is (4-byte?) field that, if set to 1, indicates presence of CD-Text data */
    num_cdtext_blocks = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of CD-TEXT blocks: %i", __debug__, num_cdtext_blocks);

    for (gint i = 0; i < num_cdtext_blocks; i++) {
        /* Parse CD-Text */
        mirage_parser_cdi_parse_cdtext(self);
    }

    /* 2 bytes after indices are undeciphered yet */

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "2 bytes after CD-TEXT");
    }

    self->priv->cur_ptr += 2;


    /* Track mode follows (FIXME: is it really 4-byte?) */
    track_mode = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track mode: %i", __debug__, track_mode);

    /* 4 bytes follow that have not been deciphered yet */

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x00},
            {2, 0x00},
            {3, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "4 bytes after track mode");
    }

    self->priv->cur_ptr += 4;


    /* Session index (i.e. which session block this track block belongs to)... */
    session_idx = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: session index: %i", __debug__, session_idx);

    /* Track index (i.e. which track block is this)... */
    track_idx = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track index: %i", __debug__, track_idx);

    /* Next is track start address... */
    start_address = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track start: 0x%X", __debug__, start_address);

    /* ... followed by track length */
    track_length = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track length: 0x%X", __debug__, track_length);


    /* 16 undeciphered bytes */

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x00},
            {2, 0x00},
            {3, 0x00},
            {4, 0x00},
            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0x00},
            {9, 0x00},
            {10, 0x00},
            {11, 0x00},
            {12, 0x00},
            {13, 0x00},
            {14, 0x00},
            {15, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "16 bytes after track length");
    }

    self->priv->cur_ptr += 16;


    /* Field that indicates read mode */
    read_mode = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: read mode: %d", __debug__, read_mode);


    /* Field that has track's CTL stored */
    track_ctl = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track's CTL: %X", __debug__, track_ctl);


    /* 9 undeciphered bytes */
    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            /* These seem to be a repeated track length */
            {1, (track_length & 0x000000FF) >> 0},
            {2, (track_length & 0x0000FF00) >> 8},
            {3, (track_length & 0x00FF0000) >> 16},
            {4, (track_length & 0xFF000000) >> 24},

            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "9 bytes after track CTL");
    }

    self->priv->cur_ptr += 9;


    /* ISRC and ISRC valid */
    isrc = MIRAGE_CAST_PTR(self->priv->cur_ptr, 0, gchar *);
    self->priv->cur_ptr += 12;
    isrc_valid = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: ISRC valid: %i", __debug__, isrc_valid);
    if (isrc_valid) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: ISRC: %.12s", __debug__, isrc);
    }


    /* Remaining 99 undeciphered bytes */
    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0xFF},
            {2, 0xFF},
            {3, 0xFF},
            {4, 0xFF},
            {5, 0xFF},
            {6, 0xFF},
            {7, 0xFF},
            {8, 0xFF},
            {9, 0x01},
            {10, 0x00},
            {11, 0x00},
            {12, 0x00},
            {13, 0x80},
            {14, 0x00},
            {15, 0x00},
            {16, 0x00},
            {17, 0x02},
            {18, 0x00},
            {19, 0x00},
            {20, 0x00},
            {21, 0x10},
            {22, 0x00},
            {23, 0x00},
            {24, 0x00},
            {25, 0x44},
            {26, 0xAC},
            {27, 0x00},
            {28, 0x00},
            {29, 0x00},
            {30, 0x00},
            {31, 0x00},
            {32, 0x00},
            {33, 0x00},
            {34, 0x00},
            {35, 0x00},
            {36, 0x00},
            {37, 0x00},
            {38, 0x00},
            {39, 0x00},
            {40, 0x00},
            {41, 0x00},
            {42, 0x00},
            {43, 0x00},
            {44, 0x00},
            {45, 0x00},
            {46, 0x00},
            {47, 0x00},
            {48, 0x00},
            {49, 0x00},
            {50, 0x00},
            {51, 0x00},
            {52, 0x00},
            {53, 0x00},
            {54, 0x00},
            {55, 0x00},
            {56, 0x00},
            {57, 0x00},
            {58, 0x00},
            {59, 0x00},
            {60, 0x00},
            {61, 0x00},
            {62, 0x00},
            {63, 0x00},
            {64, 0x00},
            {65, 0x00},
            {66, 0x00},
            {67, 0x00},
            {68, 0x00},
            {69, 0x00},
            {70, 0x00},
            {71, 0xFF},
            {72, 0xFF},
            {73, 0xFF},
            {74, 0xFF},
            {75, 0x00},
            {76, 0x00},
            {77, 0x00},
            {78, 0x00},
            {79, 0x00},
            {80, 0x00},
            {81, 0x00},
            {82, 0x00},
            {83, 0x00},
            {84, 0x00},
            {85, 0x00},
            {86, 0x00},
            /* Session type, in case it's last track of a session */
            /*{87, 0x00},*/
            {88, 0x00},
            {89, 0x00},
            {90, 0x00},
            {91, 0x00},
            {92, 0x00},
            /* This one's set to 0 in last track for any session; otherwise it's 1 */
            /*{93, 0x01},*/
            {94, 0x00},
            /* These seem to be some sort of an address for last track of a session...
             * otherwise, they're set to 00 00 FF FF */
            /*{95, 0x00},
            {96, 0x00},
            {97, 0xFF},
            {98, 0xFF},*/
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "99 bytes at the end");
    }

    session_type = MIRAGE_CAST_DATA(self->priv->cur_ptr, 87, guint8);
    not_last_track = MIRAGE_CAST_DATA(self->priv->cur_ptr, 93, guint8);
    address_at_the_end = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 95, guint32));
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: session type: %i", __debug__, session_type);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: not the last track: %i", __debug__, not_last_track);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: address at the end: 0x%X", __debug__, address_at_the_end);
    self->priv->cur_ptr += 99;

    /**********************************************************************\
     *                         Track data setting                         *
    \**********************************************************************/
    gint main_size = 0;
    guint64 main_offset = self->priv->cur_offset;
    gint main_format = 0;

    gint subchannel_size = 0;
    gint subchannel_format = 0;

    gint fragment_len = track_length;

    MirageFragment *fragment;

    MirageSession *session;
    MirageTrack *track;


    /* Track mode; also determines BINARY format */
    gint decoded_mode = 0;
    if (!mirage_parser_cdi_decode_track_mode(self, track_mode, &decoded_mode, &main_format, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failed to decode track mode!", __debug__);
        g_free(indices);
        return FALSE;
    }

    /* Read mode; determines sector size for both main channel and subchannel */
    if (!mirage_parser_cdi_decode_read_mode(self, read_mode, &main_size, &subchannel_size, &subchannel_format, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failed to decode read mode!", __debug__);
        g_free(indices);
        return FALSE;
    }

    /* Fetch current session */
    session = mirage_disc_get_session_by_index(self->priv->disc, -1, error);
    if (!session) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failed to get last session!", __debug__);
        g_free(indices);
        return FALSE;
    }

    /* Add track */
    track = g_object_new(MIRAGE_TYPE_TRACK, NULL);
    mirage_session_add_track_by_index(session, -1, track);

    /* Set track mode */
    mirage_track_set_sector_type(track, decoded_mode);

    /* Create BINARY fragment */
    fragment = g_object_new(MIRAGE_TYPE_FRAGMENT, NULL);

    mirage_fragment_set_length(fragment, fragment_len);

    mirage_fragment_main_data_set_stream(fragment, self->priv->cdi_stream);
    mirage_fragment_main_data_set_offset(fragment, main_offset);
    mirage_fragment_main_data_set_size(fragment, main_size);
    mirage_fragment_main_data_set_format(fragment, main_format);

    mirage_fragment_subchannel_data_set_size(fragment, subchannel_size);
    mirage_fragment_subchannel_data_set_format(fragment, subchannel_format);

    mirage_track_add_fragment(track, -1, fragment);

    g_object_unref(fragment);


    /* Set track flags, based on CTL */
    mirage_track_set_ctl(track, track_ctl);

    /* Set ISRC */
    if (isrc_valid) {
        /* Don't check for error here; if fragment was created with subchannel
         * data, then this call will fail, but it doesn't matter anyway... */
        mirage_track_set_isrc(track, isrc);
    }

    /* Indices; each entry represents length of corresponding index, whereas
     * libMirage uses index starting points to denote indices. In CDI, there
     * always seem to be at least two entries present; first one is for track
     * start (or rather, length of start pregap), and the second is for index 1.
     * So here we loop over everything in between first and last entry; first
     * entry is used to set track start outside the loop, whereas the last entry
     * isn't needed, because it spans to the end of the track, anyway */
    gint index_address = indices[0];
    mirage_track_set_track_start(track, indices[0]);
    for (gint i = 1; i < num_indices - 1; i++) {
        index_address += indices[i];
        mirage_track_add_index(track, index_address, NULL);
    }

    /* Set session type, if this is the last track in session */
    if (!not_last_track) {
        session_type = mirage_parser_cdi_decode_session_type(self, session_type);
        mirage_session_set_session_type(session, session_type);
    }


    /* Update current offset within image */
    self->priv->cur_offset += (main_size + subchannel_size) * fragment_len;

    g_object_unref(session);
    g_object_unref(track);

    g_free(indices);

    return TRUE;
}

static gboolean mirage_parser_cdi_load_session (MirageParserCdi *self, GError **error)
{
    gint num_tracks = 0;

    /* As far as session descriptor goes, second byte is number of tracks... */
    num_tracks = MIRAGE_CAST_DATA(self->priv->cur_ptr, 1, guint8);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of tracks: %d", __debug__, num_tracks);

    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            /* 1: Known */
            {2, 0x00},
            {3, 0x00},
            {4, 0x00},
            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0x00},
            {9, 0x01},
            {10, 0x00},
            {11, 0x00},
            {12, 0x00},
            {13, 0xFF},
            {14, 0xFF},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "Session fields");
    }

    self->priv->cur_ptr += 15;

    if (num_tracks) {
        /* Add session */
        MirageSession *session = g_object_new(MIRAGE_TYPE_SESSION, NULL);
        mirage_disc_add_session_by_index(self->priv->disc, -1, session);
        g_object_unref(session);

        /* Load tracks */
        for (gint i = 0; i < num_tracks; i++) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: *** Loading track %i ***", __debug__, i);
            if (!mirage_parser_cdi_load_track(self, error)) {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failed to load track!", __debug__);
                return FALSE;
            }
        }
    } else {
        /* This is expected; it would seem that the session block that follows
         * last track in last session has 0 tracks... so we do nothing here */
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of tracks in session is 0... this is alright if this is the descriptor that follows last track entry in last session... otherwise we might have a problem, Dave...", __debug__);
    }

    return TRUE;
}

static gboolean mirage_parser_cdi_load_disc (MirageParserCdi *self, GError **error)
{
    gboolean succeeded = TRUE;
    gint num_sessions = 0;

    /* First byte seems to be number of sessions */
    num_sessions = MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint8);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: number of sessions: %d", __debug__, num_sessions);

    /* Load sessions (note that the equal sign in for loop is there to account
     * for the last, empty session) */
    self->priv->cur_ptr += 1; /* Set pointer at start of first session descriptor */
    for (gint i = 0; i <= num_sessions; i++) {
        /* Load session */
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: *** Loading session %i ***", __debug__, i);
        if (!mirage_parser_cdi_load_session(self, error)) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failed to load session!", __debug__);
            succeeded = FALSE;
            goto end;
        }
    }

    /* Disc descriptor */
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: *** Parsing disc block ***", __debug__);

    gint disc_length = 0;
    gint volume_id_length = 0;
    gchar *volume_id = NULL;
    gint mcn_valid = 0;
    gchar *mcn = NULL;
    gint cdtext_length = 0;
    guint8 *cdtext_data = NULL;

    /* Header */
    if (!mirage_parser_cdi_parse_header(self, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: failes to parse header!", __debug__);
        succeeded = FALSE;
        goto end;
    }

    /* First 4 bytes seem to be overall size of the disc */
    disc_length = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: disc length: 0x%X", __debug__, disc_length);

    /* One byte that follows is length of volume identifier... this is ISO9660
     * volume identifier, found on data discs */
    volume_id_length = MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint8);
    self->priv->cur_ptr += sizeof(guint8);

    volume_id = MIRAGE_CAST_PTR(self->priv->cur_ptr, 0, gchar *);
    self->priv->cur_ptr += volume_id_length;

    if (volume_id_length) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: volume ID: %.*s", __debug__, volume_id_length, volume_id);
    }


    /* 14 undeciphered bytes */
    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x01},
            {2, 0x00},
            {3, 0x00},
            {4, 0x00},
            {5, 0x01},
            {6, 0x00},
            {7, 0x00},
            {8, 0x00},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "9 bytes after volume ID");
    }

    self->priv->cur_ptr += 9;

    /* MCN and MCN valid */
    mcn = MIRAGE_CAST_PTR(self->priv->cur_ptr, 0, gchar *);
    self->priv->cur_ptr += 13;

    mcn_valid = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: MCN valid: %i", __debug__, mcn_valid);
    if (mcn_valid) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: MCN: %.13s", __debug__, mcn);
    }

    /* CD-TEXT */
    cdtext_length = GUINT32_FROM_LE(MIRAGE_CAST_DATA(self->priv->cur_ptr, 0, guint32));
    self->priv->cur_ptr += sizeof(guint32);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: raw CD-TEXT data length: %i", __debug__, cdtext_length);
    cdtext_data = MIRAGE_CAST_PTR(self->priv->cur_ptr, 0, guint8 *);

    if (cdtext_length) {
        /* FIXME: CD-TEXT data is for the first session only, I think... */
        MirageSession *first_session = mirage_disc_get_session_by_index(self->priv->disc, 0, NULL);
        if (!mirage_session_set_cdtext_data(first_session, cdtext_data, cdtext_length, NULL)) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to load CD-TEXT!", __debug__);
        }
        g_object_unref(first_session);

        self->priv->cur_ptr += cdtext_length;
    }


    /* Last 12 bytes are undeciphered as well */
    if (WHINE_ON_UNEXPECTED) {
        ExpectedField fields[] = {
            {0, 0x00},
            {1, 0x00},
            {2, 0x00},
            {3, 0x00},
            {4, 0x00},
            {5, 0x00},
            {6, 0x00},
            {7, 0x00},
            {8, 0x06},
            {9, 0x00},
            {10, 0x00},
            {11, 0x80},
        };

        mirage_parser_cdi_whine_on_unexpected(self, self->priv->cur_ptr, fields, G_N_ELEMENTS(fields), (gchar *)__debug__, "Last 12 bytes");
    }

    self->priv->cur_ptr += 12;

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: done parsing disc block", __debug__);

end:
    return succeeded;
}

/**********************************************************************\
 *                MirageParser methods implementation                *
\**********************************************************************/
static MirageDisc *mirage_parser_cdi_load_image (MirageParser *_self, MirageStream **streams, GError **error)
{
    MirageParserCdi *self = MIRAGE_PARSER_CDI(_self);
    const gchar *cdi_filename;
    gboolean succeeded = TRUE;
    guint64 offset;
    gint32 descriptor_length;

    /* Check if we can load the file; we check the suffix */
    cdi_filename = mirage_stream_get_filename(streams[0]);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: checking if parser can handle given image...", __debug__);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: verifying image file's suffix...", __debug__);
    if (!mirage_helper_has_suffix(cdi_filename, ".cdi")) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: parser cannot handle given image: invalid suffix (not a *.cdi file!)!", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_CANNOT_HANDLE, Q_("Parser cannot handle given image: invalid suffix!"));
        return FALSE;
    }
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: parser can handle given image!", __debug__);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing the image...", __debug__);

    /* Add reference to stream */
    self->priv->cdi_stream = g_object_ref(streams[0]);

    /* Create disc */
    self->priv->disc = g_object_new(MIRAGE_TYPE_DISC, NULL);
    mirage_object_set_parent(MIRAGE_OBJECT(self->priv->disc), self);

    mirage_disc_set_filename(self->priv->disc, cdi_filename);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: CDI filename: %s", __debug__, cdi_filename);

    /* The descriptor is stored at the end of CDI image; I'm quite positive that
     * last four bytes represent length of descriptor data */
    offset = -(guint64)(sizeof(descriptor_length));
    mirage_stream_seek(self->priv->cdi_stream, offset, G_SEEK_END, NULL);
    if (mirage_stream_read(self->priv->cdi_stream, &descriptor_length, sizeof(descriptor_length), NULL) != sizeof(descriptor_length)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read descriptor length!", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read descriptor length!"));
        succeeded = FALSE;
        goto end;
    }
    descriptor_length = GUINT32_FROM_LE(descriptor_length);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: CDI descriptor length: 0x%X", __debug__, descriptor_length);

    /* Allocate descriptor data and read it */
    self->priv->cur_ptr = self->priv->cdi_data = g_malloc(descriptor_length);
    offset = -(guint64)(descriptor_length);
    mirage_stream_seek(self->priv->cdi_stream, offset, G_SEEK_END, NULL);
    if (mirage_stream_read(self->priv->cdi_stream, self->priv->cdi_data, descriptor_length, NULL) != descriptor_length) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read descriptor!", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read descriptor!"));
        succeeded = FALSE;
        goto end;
    }

    /* Parse the descriptor */
    succeeded = mirage_parser_cdi_load_disc(self, error);

    /* Dirty test: check if size of parsed descriptor equals to declared size
     * (minus 4 bytes which make up declared size...) */
    if (self->priv->cur_ptr - self->priv->cdi_data != descriptor_length - 4) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: size of parsed descriptor mismatch! Expect trouble... (%" G_GINTPTR_MODIFIER "d != %d)", __debug__, (gintptr)self->priv->cur_ptr - (gintptr)self->priv->cdi_data, descriptor_length);
    } else {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: disc seems to have been loaded successfully", __debug__);
    }

    /* Make disc start at -150... it seems both CD and DVD images start at -150
     * in CDI (regardless of medium, there's 150 sectors pregap at the beginning) */
    mirage_disc_layout_set_start_sector(self->priv->disc, -150);

end:
    /* Return disc */
    if (succeeded) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing completed successfully", __debug__);
        return self->priv->disc;
    } else {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing failed!", __debug__);
        g_object_unref(self->priv->disc);
        return NULL;
    }
}


/**********************************************************************\
 *                             Object init                            *
\**********************************************************************/
static void mirage_parser_cdi_init (MirageParserCdi *self)
{
    self->priv = mirage_parser_cdi_get_instance_private(self);

    mirage_parser_generate_info(MIRAGE_PARSER(self),
        "PARSER-CDI",
        Q_("CDI Image Parser"),
        1,
        Q_("DiscJuggler images (*.cdi)"), "application/x-cdi"
    );
}

static void mirage_parser_cdi_dispose (GObject *gobject)
{
    MirageParserCdi *self = MIRAGE_PARSER_CDI(gobject);

    if (self->priv->cdi_stream) {
        g_object_unref(self->priv->cdi_stream);
        self->priv->cdi_stream = NULL;
    }

    /* Chain up to the parent class */
    G_OBJECT_CLASS(mirage_parser_cdi_parent_class)->dispose(gobject);
}

static void mirage_parser_cdi_class_init (MirageParserCdiClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    MirageParserClass *parser_class = MIRAGE_PARSER_CLASS(klass);

    gobject_class->dispose = mirage_parser_cdi_dispose;

    parser_class->load_image = mirage_parser_cdi_load_image;
}

static void mirage_parser_cdi_class_finalize (MirageParserCdiClass *klass G_GNUC_UNUSED)
{
}
