MOBI_RET mobi_reconstruct_parts(MOBIRawml *rawml) {
    MOBI_RET ret;
    if (rawml->flow == NULL) {
        debug_print("%s", "Flow structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    /* take first part, xhtml */
    MOBIBuffer *buf = mobi_buffer_init_null(rawml->flow->data, rawml->flow->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    rawml->markup = calloc(1, sizeof(MOBIPart));
    if (rawml->markup == NULL) {
        debug_print("%s", "Memory allocation for markup part failed\n");
        mobi_buffer_free_null(buf);
        return MOBI_MALLOC_FAILED;
    }
    MOBIPart *curr = rawml->markup;
    /* not skeleton data, just copy whole part to markup */
    if (rawml->skel == NULL) {
        unsigned char *data = malloc(buf->maxlen);
        if (data == NULL) {
            debug_print("%s", "Memory allocation failed\n");
            mobi_buffer_free_null(buf);
            return MOBI_MALLOC_FAILED;
        }
        memcpy(data, buf->data, buf->maxlen);
        curr->uid = 0;
        curr->size = buf->maxlen;
        curr->data = data;
        curr->type = rawml->flow->type;
        curr->next = NULL;
        mobi_buffer_free_null(buf);
        return MOBI_SUCCESS;
    }
    /* parse skeleton data */
    size_t i = 0;
    size_t j = 0;
    size_t curr_position = 0;
    size_t total_fragments_count = rawml->frag->total_entries_count;
    while (i < rawml->skel->entries_count) {
        const MOBIIndexEntry *entry = &rawml->skel->entries[i];
        uint32_t fragments_count;
        ret = mobi_get_indxentry_tagvalue(&fragments_count, entry, INDX_TAG_SKEL_COUNT);
        if (ret != MOBI_SUCCESS) {
            mobi_buffer_free_null(buf);
            return ret;
        }
        if (fragments_count > total_fragments_count) {
            debug_print("%s", "Wrong count of fragments\n");
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        total_fragments_count -= fragments_count;
        uint32_t skel_position;
        ret = mobi_get_indxentry_tagvalue(&skel_position, entry, INDX_TAG_SKEL_POSITION);
        if (ret != MOBI_SUCCESS) {
            mobi_buffer_free_null(buf);
            return ret;
        }
        uint32_t skel_length;
        ret = mobi_get_indxentry_tagvalue(&skel_length, entry, INDX_TAG_SKEL_LENGTH);
        if (ret != MOBI_SUCCESS || skel_position + skel_length > buf->maxlen) {
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        debug_print("%zu\t%s\t%i\t%i\t%i\n", i, entry->label, fragments_count, skel_position, skel_length);
        mobi_buffer_setpos(buf, skel_position);
        
        unsigned char *frag_buffer = mobi_buffer_getpointer(buf, skel_length);
        if (frag_buffer == NULL) {
            debug_print("%s\n", "Fragment data beyond buffer");
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        MOBIFragment *first_fragment = mobi_list_add(NULL, 0, frag_buffer, skel_length, false);
        MOBIFragment *current_fragment = first_fragment;
        while (fragments_count--) {
            entry = &rawml->frag->entries[j];
            uint32_t insert_position = (uint32_t) strtoul(entry->label, NULL, 10);
            if (insert_position < curr_position) {
                debug_print("Insert position (%u) before part start (%zu)\n", insert_position, curr_position);
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return MOBI_DATA_CORRUPT;
            }
            uint32_t file_number;
            ret = mobi_get_indxentry_tagvalue(&file_number, entry, INDX_TAG_FRAG_FILE_NR);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return ret;
            }
            if (file_number != i) {
                debug_print("%s", "SKEL part number and fragment sequence number don't match\n");
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return MOBI_DATA_CORRUPT;
            }
            uint32_t frag_length;
            ret = mobi_get_indxentry_tagvalue(&frag_length, entry, INDX_TAG_FRAG_LENGTH);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return ret;
            }
#if (MOBI_DEBUG)
            /* FIXME: this fragment metadata is currently unused */
            uint32_t seq_number;
            ret = mobi_get_indxentry_tagvalue(&seq_number, entry, INDX_TAG_FRAG_SEQUENCE_NR);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return ret;
            }
            uint32_t frag_position;
            ret = mobi_get_indxentry_tagvalue(&frag_position, entry, INDX_TAG_FRAG_POSITION);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return ret;
            }
            uint32_t cncx_offset;
            ret = mobi_get_indxentry_tagvalue(&cncx_offset, entry, INDX_TAG_FRAG_AID_CNCX);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return ret;
            }
            const MOBIPdbRecord *cncx_record = rawml->frag->cncx_record;
            char *aid_text = mobi_get_cncx_string(cncx_record, cncx_offset);
            if (aid_text == NULL) {
                mobi_buffer_free_null(buf);
                debug_print("%s\n", "Memory allocation failed");
                mobi_list_del_all(first_fragment);
                return MOBI_MALLOC_FAILED;
            }
            debug_print("posfid[%zu]\t%i\t%i\t%s\t%i\t%i\t%i\t%i\n", j, insert_position, cncx_offset, aid_text, file_number, seq_number, frag_position, frag_length);
            free(aid_text);
#endif
            
            insert_position -= curr_position;
            if (skel_length < insert_position) {
                debug_print("Insert position (%u) after part end (%u)\n", insert_position, skel_length);
                // FIXME: shouldn't the fragment be ignored?
                // For now insert it at the end.
                insert_position = skel_length;
            }
            skel_length += frag_length;
            
            frag_buffer = mobi_buffer_getpointer(buf, frag_length);
            if (frag_buffer == NULL) {
                debug_print("%s\n", "Fragment data beyond buffer");
                mobi_buffer_free_null(buf);
                mobi_list_del_all(first_fragment);
                return MOBI_DATA_CORRUPT;
            }
            current_fragment = mobi_list_insert(current_fragment, insert_position, frag_buffer, frag_length, false, insert_position);
            j++;
            
        }
        char *skel_text = malloc(skel_length);
        if (skel_text == NULL) {
            debug_print("%s", "Memory allocation for markup data failed\n");
            mobi_buffer_free_null(buf);
            mobi_list_del_all(first_fragment);
            return MOBI_MALLOC_FAILED;
        }
        char *p = skel_text;
        while (first_fragment) {
            if (first_fragment->fragment) {
                memcpy(p, first_fragment->fragment, first_fragment->size);
                p += first_fragment->size;
            } else {
                debug_print("Skipping broken fragment in part %zu\n", i);
            }
            first_fragment = mobi_list_del(first_fragment);
        }
        if (i > 0) {
            curr->next = calloc(1, sizeof(MOBIPart));
            if (curr->next == NULL) {
                debug_print("%s", "Memory allocation for markup part failed\n");
                free(skel_text);
                mobi_buffer_free_null(buf);
                return MOBI_MALLOC_FAILED;
            }
            curr = curr->next;
        }
        curr->uid = i;
        curr->size = skel_length;
        curr->data = (unsigned char *) skel_text;
        curr->type = T_HTML;
        curr->next = NULL;
        curr_position += skel_length;
        i++;
    }
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}