/*
 * This file is part of libbluray
 * Copyright (C) 2009-2010  John Stebbins
 * Copyright (C) 2012-2013  Petri Hintukainen <phintuka@users.sourceforge.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "clpi_parse.h"
#include "clpi_data.h"

#include "extdata_parse.h"
#include "bdmv_parse.h"

#include "disc/disc.h"

#include "file/file.h"
#include "util/refcnt.h"
#include "util/bits.h"
#include "util/macro.h"
#include "util/logging.h"

#include <stdlib.h>
#include <string.h>

#define CLPI_SIG1  ('H' << 24 | 'D' << 16 | 'M' << 8 | 'V')

static int
_parse_stream_attr(BITSTREAM *bits, CLPI_PROG_STREAM *ss)
{
    int64_t pos;
    int len;

    if (!bs_is_align(bits, 0x07)) {
        BD_DEBUG(DBG_NAV | DBG_CRIT, "_parse_stream_attr(): Stream alignment error\n");
    }

    len = bs_read(bits, 8);
    pos = bs_pos(bits) >> 3;

    ss->lang[0] = '\0';
    memset(ss->isrc,'\0',12);
    ss->coding_type = bs_read(bits, 8);
    switch (ss->coding_type) {
        case 0x01:
        case 0x02:
        case 0xea:
        case 0x1b:
        case 0x20:
        case 0x24:
            ss->format = bs_read(bits, 4);
            ss->rate   = bs_read(bits, 4);
            ss->aspect = bs_read(bits, 4);
            bs_skip(bits, 2);
            ss->oc_flag = bs_read(bits, 1);
            if (ss->coding_type == 0x24) {
                ss->cr_flag = bs_read(bits, 1);
                ss->dynamic_range_type = bs_read(bits, 4);
                ss->color_space = bs_read(bits, 4);
                ss->hdr_plus_flag = bs_read(bits, 1);
                bs_skip(bits, 7);
            } else {
                bs_skip(bits, 17);
            }
            break;

        case 0x03:
        case 0x04:
        case 0x80:
        case 0x81:
        case 0x82:
        case 0x83:
        case 0x84:
        case 0x85:
        case 0x86:
        case 0xa1:
        case 0xa2:
            ss->format = bs_read(bits, 4);
            ss->rate   = bs_read(bits, 4);
            bs_read_string(bits, ss->lang, 3);
            break;

        case 0x90:
        case 0x91:
        case 0xa0:
            bs_read_string(bits, ss->lang, 3);
            bs_skip(bits, 8);
            break;

        case 0x92:
            ss->char_code = bs_read(bits, 8);
            bs_read_string(bits, ss->lang, 3);
            break;

        default:
            BD_DEBUG(DBG_NAV | DBG_CRIT, "_parse_stream_attr(): unrecognized coding type %02x\n", ss->coding_type);
            break;
    };
    ss->lang[3] = '\0';
    bs_read_bytes(bits, ss->isrc, 12);

    // Skip over any padding
    if (bs_seek_byte(bits, pos + len) < 0) {
        return 0;
    }
    return 1;
}

static int
_parse_header(BITSTREAM *bits, CLPI_CL *cl)
{
    cl->type_indicator = CLPI_SIG1;
    if (!bdmv_parse_header(bits, cl->type_indicator, &cl->type_indicator2)) {
        return 0;
    }

    if (bs_avail(bits) < 5 * 32) {
        BD_DEBUG(DBG_NAV | DBG_CRIT, "_parse_header: unexpected end of file\n");
        return 0;
    }

    cl->sequence_info_start_addr = bs_read(bits, 32);
    cl->program_info_start_addr = bs_read(bits, 32);
    cl->cpi_start_addr = bs_read(bits, 32);
    cl->clip_mark_start_addr = bs_read(bits, 32);
    cl->ext_data_start_addr = bs_read(bits, 32);
    return 1;
}

static int
_parse_clipinfo(BITSTREAM *bits, CLPI_CL *cl)
{
    int64_t pos;
    int len;
    int ii;

    if (bs_seek_byte(bits, 40) < 0) {
        return 0;
    }

    // ClipInfo len
    bs_skip(bits, 32);
    // reserved
    bs_skip(bits, 16);
    cl->clip.clip_stream_type = bs_read(bits, 8);
    cl->clip.application_type = bs_read(bits, 8);
    // skip reserved 31 bits
    bs_skip(bits, 31);
    cl->clip.is_atc_delta       = bs_read(bits, 1);
    cl->clip.ts_recording_rate  = bs_read(bits, 32);
    cl->clip.num_source_packets = bs_read(bits, 32);

    // Skip reserved 128 bytes
    bs_skip(bits, 128 * 8);

    // ts type info block
    len = bs_read(bits, 16);
    pos = bs_pos(bits) >> 3;
    if (len) {
        cl->clip.ts_type_info.validity = bs_read(bits, 8);
        bs_read_string(bits, cl->clip.ts_type_info.format_id, 4);
        // Seek past the stuff we don't know anything about
        if (bs_seek_byte(bits, pos + len) < 0) {
            return 0;
        }
    }
    if (cl->clip.is_atc_delta) {
        // Skip reserved bytes
        bs_skip(bits, 8);
        cl->clip.atc_delta_count = bs_read(bits, 8);
        cl->clip.atc_delta = 
            malloc(cl->clip.atc_delta_count * sizeof(CLPI_ATC_DELTA));
        if (cl->clip.atc_delta_count && !cl->clip.atc_delta) {
            BD_DEBUG(DBG_CRIT, "out of memory\n");
            return 0;
        }
        for (ii = 0; ii < cl->clip.atc_delta_count; ii++) {
            cl->clip.atc_delta[ii].delta = bs_read(bits, 32);
            bs_read_string(bits, cl->clip.atc_delta[ii].file_id, 5);
            bs_read_string(bits, cl->clip.atc_delta[ii].file_code, 4);
            bs_skip(bits, 8);
        }
    }

    // font info
    if (cl->clip.application_type == 6 /* Sub TS for a sub-path of Text subtitle */) {
        CLPI_FONT_INFO *fi = &cl->clip.font_info;
        bs_skip(bits, 8);
        fi->font_count = bs_read(bits, 8);
        if (fi->font_count) {
            fi->font = malloc(fi->font_count * sizeof(CLPI_FONT));
            if (!fi->font) {
                BD_DEBUG(DBG_CRIT, "out of memory\n");
                return 0;
            }
            for (ii = 0; ii < fi->font_count; ii++) {
                bs_read_string(bits, fi->font[ii].file_id, 5);
                bs_skip(bits, 8);
            }
        }
    }

    return 1;
}

static int
_parse_sequence(BITSTREAM *bits, CLPI_CL *cl)
{
    int ii, jj;

    if (bs_seek_byte(bits, cl->sequence_info_start_addr) < 0) {
        return 0;
    }

    // Skip the length field, and a reserved byte
    bs_skip(bits, 5 * 8);
    // Then get the number of sequences
    cl->sequence.num_atc_seq = bs_read(bits, 8);

    CLPI_ATC_SEQ *atc_seq;
    atc_seq = calloc(cl->sequence.num_atc_seq, sizeof(CLPI_ATC_SEQ));
    cl->sequence.atc_seq = atc_seq;
    if (cl->sequence.num_atc_seq && !atc_seq) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < cl->sequence.num_atc_seq; ii++) {
        atc_seq[ii].spn_atc_start = bs_read(bits, 32);
        atc_seq[ii].num_stc_seq   = bs_read(bits, 8);
        atc_seq[ii].offset_stc_id = bs_read(bits, 8);

        CLPI_STC_SEQ *stc_seq;
        stc_seq = malloc(atc_seq[ii].num_stc_seq * sizeof(CLPI_STC_SEQ));
        if (atc_seq[ii].num_stc_seq && !stc_seq) {
            BD_DEBUG(DBG_CRIT, "out of memory\n");
            return 0;
        }
        atc_seq[ii].stc_seq = stc_seq;
        for (jj = 0; jj < atc_seq[ii].num_stc_seq; jj++) {
            stc_seq[jj].pcr_pid                 = bs_read(bits, 16);
            stc_seq[jj].spn_stc_start           = bs_read(bits, 32);
            stc_seq[jj].presentation_start_time = bs_read(bits, 32);
            stc_seq[jj].presentation_end_time   = bs_read(bits, 32);
        }
    }
    return 1;
}

static int
_parse_program(BITSTREAM *bits, CLPI_PROG_INFO *program)
{
    int ii, jj;

    // Skip the length field, and a reserved byte
    bs_skip(bits, 5 * 8);
    // Then get the number of sequences
    program->num_prog = bs_read(bits, 8);

    CLPI_PROG *progs;
    progs = calloc(program->num_prog, sizeof(CLPI_PROG));
    program->progs = progs;
    if (program->num_prog && !progs) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < program->num_prog; ii++) {
        progs[ii].spn_program_sequence_start = bs_read(bits, 32);
        progs[ii].program_map_pid            = bs_read(bits, 16);
        progs[ii].num_streams                = bs_read(bits, 8);
        progs[ii].num_groups                 = bs_read(bits, 8);

        CLPI_PROG_STREAM *ps;
        ps = calloc(progs[ii].num_streams, sizeof(CLPI_PROG_STREAM));
        if (progs[ii].num_streams && !ps) {
            BD_DEBUG(DBG_CRIT, "out of memory\n");
            return 0;
        }
        progs[ii].streams = ps;
        for (jj = 0; jj < progs[ii].num_streams; jj++) {
            ps[jj].pid = bs_read(bits, 16);
            if (!_parse_stream_attr(bits, &ps[jj])) {
                return 0;
            }
        }
    }
    return 1;
}

static int
_parse_program_info(BITSTREAM *bits, CLPI_CL *cl)
{
    if (bs_seek_byte(bits, cl->program_info_start_addr) < 0) {
        return 0;
    }

    return _parse_program(bits, &cl->program);
}

static int
_parse_ep_map_stream(BITSTREAM *bits, CLPI_EP_MAP_ENTRY *ee)
{
    uint32_t          fine_start;
    int               ii;
    CLPI_EP_COARSE   * coarse;
    CLPI_EP_FINE     * fine;

    if (bs_seek_byte(bits, ee->ep_map_stream_start_addr) < 0) {
        return 0;
    }
    fine_start = bs_read(bits, 32);

    if (bs_avail(bits)/(8*8) < ee->num_ep_coarse) {
        BD_DEBUG(DBG_HDMV|DBG_CRIT, "clpi_parse: unexpected EOF (EP coarse)\n");
        return 0;
    }

    coarse = malloc(ee->num_ep_coarse * sizeof(CLPI_EP_COARSE));
    ee->coarse = coarse;
    if (ee->num_ep_coarse && !coarse) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < ee->num_ep_coarse; ii++) {
        coarse[ii].ref_ep_fine_id = bs_read(bits, 18);
        coarse[ii].pts_ep         = bs_read(bits, 14);
        coarse[ii].spn_ep         = bs_read(bits, 32);
    }

    if (bs_seek_byte(bits, (int64_t)ee->ep_map_stream_start_addr+fine_start) < 0) {
        return 0;
    }

    if (bs_avail(bits)/(8*4) < ee->num_ep_fine) {
        BD_DEBUG(DBG_HDMV|DBG_CRIT, "clpi_parse: unexpected EOF (EP fine)\n");
        return 0;
    }

    fine = malloc(ee->num_ep_fine * sizeof(CLPI_EP_FINE));
    ee->fine = fine;
    if (ee->num_ep_fine && !fine) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < ee->num_ep_fine; ii++) {
        fine[ii].is_angle_change_point = bs_read(bits, 1);
        fine[ii].i_end_position_offset = bs_read(bits, 3);
        fine[ii].pts_ep                = bs_read(bits, 11);
        fine[ii].spn_ep                = bs_read(bits, 17);
    }
    return 1;
}

static int
_parse_cpi(BITSTREAM *bits, CLPI_CPI *cpi)
{
    int ii;
    uint32_t ep_map_pos, len;

    len = bs_read(bits, 32);
    if (len == 0) {
        return 1;
    }

    bs_skip(bits, 12);
    cpi->type = bs_read(bits, 4);
    ep_map_pos = (uint32_t)(bs_pos(bits) >> 3);

    // EP Map starts here
    bs_skip(bits, 8);
    cpi->num_stream_pid = bs_read(bits, 8);

    CLPI_EP_MAP_ENTRY *entry;
    entry = calloc(cpi->num_stream_pid, sizeof(CLPI_EP_MAP_ENTRY));
    cpi->entry = entry;
    if (cpi->num_stream_pid && !entry) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < cpi->num_stream_pid; ii++) {
        entry[ii].pid                      = bs_read(bits, 16);
        bs_skip(bits, 10);
        entry[ii].ep_stream_type           = bs_read(bits, 4);
        entry[ii].num_ep_coarse            = bs_read(bits, 16);
        entry[ii].num_ep_fine              = bs_read(bits, 18);
        entry[ii].ep_map_stream_start_addr = bs_read(bits, 32) + ep_map_pos;
    }
    for (ii = 0; ii < cpi->num_stream_pid; ii++) {
        if (!_parse_ep_map_stream(bits, &cpi->entry[ii])) {
            return 0;
        }
    }
    return 1;
}

static int
_parse_cpi_info(BITSTREAM *bits, CLPI_CL *cl)
{
    if (bs_seek_byte(bits, cl->cpi_start_addr) < 0) {
        return 0;
    }

    return _parse_cpi(bits, &cl->cpi);
}

uint32_t
clpi_find_stc_spn(const CLPI_CL *cl, uint8_t stc_id)
{
    int ii;
    CLPI_ATC_SEQ *atc;

    for (ii = 0; ii < cl->sequence.num_atc_seq; ii++) {
        atc = &cl->sequence.atc_seq[ii];
        if (stc_id < atc->offset_stc_id + atc->num_stc_seq) {
            return atc->stc_seq[stc_id - atc->offset_stc_id].spn_stc_start;
        }
    }
    return 0;
}

// Looks up the start packet number for the timestamp
// Returns the spn for the entry that is closest to but
// before the given timestamp
uint32_t
clpi_lookup_spn(const CLPI_CL *cl, uint32_t timestamp, int before, uint8_t stc_id)
{
    const CLPI_EP_MAP_ENTRY *entry;
    const CLPI_CPI *cpi = &cl->cpi;
    int ii, jj;
    uint32_t coarse_pts, pts; // 45khz timestamps
    uint32_t spn, coarse_spn, stc_spn;
    int start, end;
    int ref;

    if (cpi->num_stream_pid < 1 || !cpi->entry) {
        if (before) {
            return 0;
        }
        return cl->clip.num_source_packets;
    }

    // Assumes that there is only one pid of interest
    entry = &cpi->entry[0];

    // Use sequence info to find spn_stc_start before doing
    // PTS search. The spn_stc_start defines the point in
    // the EP map to start searching.
    stc_spn = clpi_find_stc_spn(cl, stc_id);
    for (ii = 0; ii < entry->num_ep_coarse; ii++) {
        ref = entry->coarse[ii].ref_ep_fine_id;
        if (entry->coarse[ii].spn_ep >= stc_spn) {
            // The desired starting point is either after this point
            // or in the middle of the previous coarse entry
            break;
        }
    }
    if (ii >= entry->num_ep_coarse) {
        return cl->clip.num_source_packets;
    }
    pts = ((uint64_t)(entry->coarse[ii].pts_ep & ~0x01) << 18) +
          ((uint64_t)entry->fine[ref].pts_ep << 8);
    if (pts > timestamp && ii) {
        // The starting point and desired PTS is in the previous coarse entry
        ii--;
        coarse_pts = (uint32_t)(entry->coarse[ii].pts_ep & ~0x01) << 18;
        coarse_spn = entry->coarse[ii].spn_ep;
        start = entry->coarse[ii].ref_ep_fine_id;
        end = entry->coarse[ii+1].ref_ep_fine_id;
        // Find a fine entry that has bothe spn > stc_spn and ptc > timestamp
        for (jj = start; jj < end; jj++) {

            pts = coarse_pts + ((uint32_t)entry->fine[jj].pts_ep << 8);
            spn = (coarse_spn & ~0x1FFFF) + entry->fine[jj].spn_ep;
            if (stc_spn >= spn && pts > timestamp)
                break;
        }
        goto done;
    }

    // If we've gotten this far, the desired timestamp is somewhere
    // after the coarse entry we found the stc_spn in.
    start = ii;
    for (ii = start; ii < entry->num_ep_coarse; ii++) {
        ref = entry->coarse[ii].ref_ep_fine_id;
        pts = ((uint64_t)(entry->coarse[ii].pts_ep & ~0x01) << 18) +
                ((uint64_t)entry->fine[ref].pts_ep << 8);
        if (pts > timestamp) {
            break;
        }
    }
    // If the timestamp is before the first entry, then return
    // the beginning of the clip
    if (ii == 0) {
        return 0;
    }
    ii--;
    coarse_pts = (uint32_t)(entry->coarse[ii].pts_ep & ~0x01) << 18;
    start = entry->coarse[ii].ref_ep_fine_id;
    if (ii < entry->num_ep_coarse - 1) {
        end = entry->coarse[ii+1].ref_ep_fine_id;
    } else {
        end = entry->num_ep_fine;
    }
    for (jj = start; jj < end; jj++) {

        pts = coarse_pts + ((uint32_t)entry->fine[jj].pts_ep << 8);
        if (pts > timestamp)
            break;
    }

done:
    if (before) {
        jj--;
    }
    if (jj == end) {
        ii++;
        if (ii >= entry->num_ep_coarse) {
            // End of file
            return cl->clip.num_source_packets;
        }
        jj = entry->coarse[ii].ref_ep_fine_id;
    }
    spn = (entry->coarse[ii].spn_ep & ~0x1FFFF) + entry->fine[jj].spn_ep;
    return spn;
}

// Looks up the start packet number that is closest to the requested packet
// Returns the spn for the entry that is closest to but
// before the given packet
uint32_t
clpi_access_point(const CLPI_CL *cl, uint32_t pkt, int next, int angle_change, uint32_t *time)
{
    const CLPI_EP_MAP_ENTRY *entry;
    const CLPI_CPI *cpi = &cl->cpi;
    int ii, jj;
    uint32_t coarse_spn, spn;
    int start, end;
    int ref;

    // Assumes that there is only one pid of interest
    entry = &cpi->entry[0];

    for (ii = 0; ii < entry->num_ep_coarse; ii++) {
        ref = entry->coarse[ii].ref_ep_fine_id;
        spn = (entry->coarse[ii].spn_ep & ~0x1FFFF) + entry->fine[ref].spn_ep;
        if (spn > pkt) {
            break;
        }
    }
    // If the timestamp is before the first entry, then return
    // the beginning of the clip
    if (ii == 0) {
        *time = 0;
        return 0;
    }
    ii--;
    coarse_spn = (entry->coarse[ii].spn_ep & ~0x1FFFF);
    start = entry->coarse[ii].ref_ep_fine_id;
    if (ii < entry->num_ep_coarse - 1) {
        end = entry->coarse[ii+1].ref_ep_fine_id;
    } else {
        end = entry->num_ep_fine;
    }
    for (jj = start; jj < end; jj++) {

        spn = coarse_spn + entry->fine[jj].spn_ep;
        if (spn >= pkt) {
            break;
        }
    }
    if (jj == end && next) {
        ii++;
        jj = 0;
    } else if (spn != pkt && !next) {
        jj--;
    }
    if (ii == entry->num_ep_coarse) {
        *time = 0;
        return cl->clip.num_source_packets;
    }
    coarse_spn = (entry->coarse[ii].spn_ep & ~0x1FFFF);
    if (angle_change) {
        // Keep looking till there's an angle change point
        for (; jj < end; jj++) {

            if (entry->fine[jj].is_angle_change_point) {
                *time = ((uint64_t)(entry->coarse[ii].pts_ep & ~0x01) << 18) +
                        ((uint64_t)entry->fine[jj].pts_ep << 8);
                return coarse_spn + entry->fine[jj].spn_ep;
            }
        }
        for (ii++; ii < entry->num_ep_coarse; ii++) {
            start = entry->coarse[ii].ref_ep_fine_id;
            if (ii < entry->num_ep_coarse - 1) {
                end = entry->coarse[ii+1].ref_ep_fine_id;
            } else {
                end = entry->num_ep_fine;
            }
            for (jj = start; jj < end; jj++) {

                if (entry->fine[jj].is_angle_change_point) {
                    *time = ((uint64_t)(entry->coarse[ii].pts_ep & ~0x01) << 18) +
                            ((uint64_t)entry->fine[jj].pts_ep << 8);
                    return coarse_spn + entry->fine[jj].spn_ep;
                }
            }
        }
        *time = 0;
        return cl->clip.num_source_packets;
    }
    *time = ((uint64_t)(entry->coarse[ii].pts_ep & ~0x01) << 18) +
            ((uint64_t)entry->fine[jj].pts_ep << 8);
    return coarse_spn + entry->fine[jj].spn_ep;
}

static int
_parse_extent_start_points(BITSTREAM *bits, CLPI_EXTENT_START *es)
{
    unsigned int ii;

    bs_skip(bits, 32); // length
    es->num_point = bs_read(bits, 32);

    es->point = calloc(es->num_point, sizeof(uint32_t));
    if (es->num_point && !es->point) {
        es->num_point = 0;
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return 0;
    }
    for (ii = 0; ii < es->num_point; ii++) {
        es->point[ii] = bs_read(bits, 32);
    }

    return 1;
}

static int _parse_clpi_extension(BITSTREAM *bits, int id1, int id2, void *handle)
{
    CLPI_CL *cl = (CLPI_CL*)handle;

    if (id1 == 1) {
        if (id2 == 2) {
            // LPCM down mix coefficient
            //_parse_lpcm_down_mix_coeff(bits, &cl->lpcm_down_mix_coeff);
            return 0;
        }
    }

    if (id1 == 2) {
        if (id2 == 4) {
            // Extent start point
            return _parse_extent_start_points(bits, &cl->extent_start);
        }
        if (id2 == 5) {
            // ProgramInfo SS
            return _parse_program(bits, &cl->program_ss);
        }
        if (id2 == 6) {
            // CPI SS
            return _parse_cpi(bits, &cl->cpi_ss);
        }
    }

    BD_DEBUG(DBG_NAV | DBG_CRIT, "_parse_clpi_extension(): unhandled extension %d.%d\n", id1, id2);
    return 0;
}

static void
_clean_program(CLPI_PROG_INFO *p)
{
    int ii;

    if (p && p->progs) {
        for (ii = 0; ii < p->num_prog; ii++) {
            X_FREE(p->progs[ii].streams);
        }
        X_FREE(p->progs);
    }
}

static void
_clean_cpi(CLPI_CPI *cpi)
{
    int ii;

    if (cpi && cpi->entry) {
        for (ii = 0; ii < cpi->num_stream_pid; ii++) {
            X_FREE(cpi->entry[ii].coarse);
            X_FREE(cpi->entry[ii].fine);
        }
        X_FREE(cpi->entry);
    }
}

static void
_clpi_clean(void *p)
{
    CLPI_CL *cl = p;
    int ii;

    X_FREE(cl->clip.atc_delta);
    X_FREE(cl->clip.font_info.font);

    if (cl->sequence.atc_seq) {
        for (ii = 0; ii < cl->sequence.num_atc_seq; ii++) {
            X_FREE(cl->sequence.atc_seq[ii].stc_seq);
        }

        X_FREE(cl->sequence.atc_seq);
    }

    _clean_program(&cl->program);
    _clean_cpi(&cl->cpi);

    X_FREE(cl->extent_start.point);

    _clean_program(&cl->program_ss);
    _clean_cpi(&cl->cpi_ss);
}

static void
_clpi_free(const CLPI_CL *cl)
{
    refcnt_dec(cl);
}

void
clpi_unref(const CLPI_CL **cl)
{
    if (*cl) {
        _clpi_free(*cl);
        *cl = NULL;
    }
}

void
clpi_free(CLPI_CL **cl)
{
    if (*cl) {
        _clpi_free(*cl);
        *cl = NULL;
    }
}

static CLPI_CL*
_clpi_parse(BD_FILE_H *fp)
{
    BITSTREAM  bits;
    CLPI_CL   *cl;

    if (bs_init(&bits, fp) < 0) {
        BD_DEBUG(DBG_NAV, "?????.clpi: read error\n");
        return NULL;
    }

    cl = refcnt_calloc(sizeof(CLPI_CL), _clpi_clean);
    if (cl == NULL) {
        BD_DEBUG(DBG_CRIT, "out of memory\n");
        return NULL;
    }

    if (!_parse_header(&bits, cl)) {
        _clpi_free(cl);
        return NULL;
    }

    if (cl->ext_data_start_addr > 0) {
        bdmv_parse_extension_data(&bits,
                                   cl->ext_data_start_addr,
                                   _parse_clpi_extension,
                                   cl);
    }

    if (!_parse_clipinfo(&bits, cl)) {
        _clpi_free(cl);
        return NULL;
    }
    if (!_parse_sequence(&bits, cl)) {
        _clpi_free(cl);
        return NULL;
    }
    if (!_parse_program_info(&bits, cl)) {
        _clpi_free(cl);
        return NULL;
    }
    if (!_parse_cpi_info(&bits, cl)) {
        _clpi_free(cl);
        return NULL;
    }

    return cl;
}

CLPI_CL*
clpi_parse(const char *path)
{
    BD_FILE_H *fp;
    CLPI_CL   *cl;

    fp = file_open(path, "rb");
    if (!fp) {
        BD_DEBUG(DBG_NAV | DBG_CRIT, "Failed to open %s\n", path);
        return NULL;
    }

    cl = _clpi_parse(fp);
    file_close(fp);
    return cl;
}

static CLPI_CL*
_clpi_get(BD_DISC *disc, const char *dir, const char *file)
{
    BD_FILE_H *fp;
    CLPI_CL   *cl;

    fp = disc_open_file(disc, dir, file);
    if (!fp) {
        return NULL;
    }

    cl = _clpi_parse(fp);
    file_close(fp);
    return cl;
}

const CLPI_CL*
clpi_get(BD_DISC *disc, const char *file)
{
    const CLPI_CL *cl;

    cl = disc_cache_get(disc, file);
    if (cl) {
        return cl;
    }

    cl = _clpi_get(disc, "BDMV" DIR_SEP "CLIPINF", file);
    if (!cl) {
        /* if failed, try backup file */
        cl = _clpi_get(disc, "BDMV" DIR_SEP "BACKUP" DIR_SEP "CLIPINF", file);
    }

    if (cl) {
        disc_cache_put(disc, file, cl);
    }

    return cl;
}

CLPI_CL*
clpi_copy(const CLPI_CL* src_cl)
{
    CLPI_CL* dest_cl = NULL;
    int ii, jj;

    if (src_cl) {
        dest_cl = refcnt_calloc(sizeof(CLPI_CL), _clpi_clean);
        if (!dest_cl) {
            goto fail;
        }
        dest_cl->clip.clip_stream_type = src_cl->clip.clip_stream_type;
        dest_cl->clip.application_type = src_cl->clip.application_type;
        dest_cl->clip.is_atc_delta = src_cl->clip.is_atc_delta;
        dest_cl->clip.atc_delta_count = src_cl->clip.atc_delta_count;
        dest_cl->clip.ts_recording_rate = src_cl->clip.ts_recording_rate;
        dest_cl->clip.num_source_packets = src_cl->clip.num_source_packets;
        dest_cl->clip.ts_type_info.validity = src_cl->clip.ts_type_info.validity;
        memcpy(dest_cl->clip.ts_type_info.format_id, src_cl->clip.ts_type_info.format_id, 5);
        dest_cl->clip.atc_delta = malloc(src_cl->clip.atc_delta_count * sizeof(CLPI_ATC_DELTA));
        if (src_cl->clip.atc_delta_count && !dest_cl->clip.atc_delta) {
            goto fail;
        }
        for (ii = 0; ii < src_cl->clip.atc_delta_count; ii++) {
            dest_cl->clip.atc_delta[ii].delta =  src_cl->clip.atc_delta[ii].delta;
            memcpy(dest_cl->clip.atc_delta[ii].file_id, src_cl->clip.atc_delta[ii].file_id, 6);
            memcpy(dest_cl->clip.atc_delta[ii].file_code, src_cl->clip.atc_delta[ii].file_code, 5);
        }

        dest_cl->sequence.num_atc_seq = src_cl->sequence.num_atc_seq;
        dest_cl->sequence.atc_seq = calloc(src_cl->sequence.num_atc_seq, sizeof(CLPI_ATC_SEQ));
        if (dest_cl->sequence.num_atc_seq && !dest_cl->sequence.atc_seq) {
            goto fail;
        }
        for (ii = 0; ii < src_cl->sequence.num_atc_seq; ii++) {
            dest_cl->sequence.atc_seq[ii].spn_atc_start = src_cl->sequence.atc_seq[ii].spn_atc_start;
            dest_cl->sequence.atc_seq[ii].offset_stc_id = src_cl->sequence.atc_seq[ii].offset_stc_id;
            dest_cl->sequence.atc_seq[ii].num_stc_seq = src_cl->sequence.atc_seq[ii].num_stc_seq;
            dest_cl->sequence.atc_seq[ii].stc_seq = malloc(src_cl->sequence.atc_seq[ii].num_stc_seq * sizeof(CLPI_STC_SEQ));
            if (dest_cl->sequence.atc_seq[ii].num_stc_seq && !dest_cl->sequence.atc_seq[ii].stc_seq) {
                goto fail;
            }
            for (jj = 0; jj < src_cl->sequence.atc_seq[ii].num_stc_seq; jj++) {
                dest_cl->sequence.atc_seq[ii].stc_seq[jj].spn_stc_start = src_cl->sequence.atc_seq[ii].stc_seq[jj].spn_stc_start;
                dest_cl->sequence.atc_seq[ii].stc_seq[jj].pcr_pid = src_cl->sequence.atc_seq[ii].stc_seq[jj].pcr_pid;
                dest_cl->sequence.atc_seq[ii].stc_seq[jj].presentation_start_time = src_cl->sequence.atc_seq[ii].stc_seq[jj].presentation_start_time;
                dest_cl->sequence.atc_seq[ii].stc_seq[jj].presentation_end_time = src_cl->sequence.atc_seq[ii].stc_seq[jj].presentation_end_time;
            }
        }

        dest_cl->program.num_prog = src_cl->program.num_prog;
        dest_cl->program.progs = calloc(src_cl->program.num_prog, sizeof(CLPI_PROG));
        if (dest_cl->program.num_prog && !dest_cl->program.progs) {
            goto fail;
        }
        for (ii = 0; ii < src_cl->program.num_prog; ii++) {
            dest_cl->program.progs[ii].spn_program_sequence_start = src_cl->program.progs[ii].spn_program_sequence_start;
            dest_cl->program.progs[ii].program_map_pid = src_cl->program.progs[ii].program_map_pid;
            dest_cl->program.progs[ii].num_streams = src_cl->program.progs[ii].num_streams;
            dest_cl->program.progs[ii].num_groups = src_cl->program.progs[ii].num_groups;
            dest_cl->program.progs[ii].streams = malloc(src_cl->program.progs[ii].num_streams * sizeof(CLPI_PROG_STREAM));
            if (src_cl->program.progs[ii].num_streams && !dest_cl->program.progs[ii].streams) {
                goto fail;
            }
            for (jj = 0; jj < src_cl->program.progs[ii].num_streams; jj++) {
                dest_cl->program.progs[ii].streams[jj].coding_type = src_cl->program.progs[ii].streams[jj].coding_type;
                dest_cl->program.progs[ii].streams[jj].pid = src_cl->program.progs[ii].streams[jj].pid;
                dest_cl->program.progs[ii].streams[jj].format = src_cl->program.progs[ii].streams[jj].format;
                dest_cl->program.progs[ii].streams[jj].rate = src_cl->program.progs[ii].streams[jj].rate;
                dest_cl->program.progs[ii].streams[jj].aspect = src_cl->program.progs[ii].streams[jj].aspect;
                dest_cl->program.progs[ii].streams[jj].oc_flag = src_cl->program.progs[ii].streams[jj].oc_flag;
                dest_cl->program.progs[ii].streams[jj].cr_flag = src_cl->program.progs[ii].streams[jj].cr_flag;
                dest_cl->program.progs[ii].streams[jj].dynamic_range_type = src_cl->program.progs[ii].streams[jj].dynamic_range_type;
                dest_cl->program.progs[ii].streams[jj].color_space = src_cl->program.progs[ii].streams[jj].color_space;
                dest_cl->program.progs[ii].streams[jj].hdr_plus_flag = src_cl->program.progs[ii].streams[jj].hdr_plus_flag;
                dest_cl->program.progs[ii].streams[jj].char_code = src_cl->program.progs[ii].streams[jj].char_code;
                memcpy(dest_cl->program.progs[ii].streams[jj].lang,src_cl->program.progs[ii].streams[jj].lang,4);
                memcpy(dest_cl->program.progs[ii].streams[jj].isrc,src_cl->program.progs[ii].streams[jj].isrc,12);
            }
        }

        dest_cl->cpi.num_stream_pid = src_cl->cpi.num_stream_pid;
        dest_cl->cpi.entry = calloc(src_cl->cpi.num_stream_pid, sizeof(CLPI_EP_MAP_ENTRY));
        if (dest_cl->cpi.num_stream_pid && !dest_cl->cpi.entry) {
            goto fail;
        }
        for (ii = 0; ii < dest_cl->cpi.num_stream_pid; ii++) {
            dest_cl->cpi.entry[ii].pid = src_cl->cpi.entry[ii].pid;
            dest_cl->cpi.entry[ii].ep_stream_type = src_cl->cpi.entry[ii].ep_stream_type;
            dest_cl->cpi.entry[ii].num_ep_coarse = src_cl->cpi.entry[ii].num_ep_coarse;
            dest_cl->cpi.entry[ii].num_ep_fine = src_cl->cpi.entry[ii].num_ep_fine;
            dest_cl->cpi.entry[ii].ep_map_stream_start_addr = src_cl->cpi.entry[ii].ep_map_stream_start_addr;
            dest_cl->cpi.entry[ii].coarse = malloc(src_cl->cpi.entry[ii].num_ep_coarse * sizeof(CLPI_EP_COARSE));
            if (dest_cl->cpi.entry[ii].num_ep_coarse && !dest_cl->cpi.entry[ii].coarse) {
                goto fail;
            }
            for (jj = 0; jj < src_cl->cpi.entry[ii].num_ep_coarse; jj++) {
                dest_cl->cpi.entry[ii].coarse[jj].ref_ep_fine_id = src_cl->cpi.entry[ii].coarse[jj].ref_ep_fine_id;
                dest_cl->cpi.entry[ii].coarse[jj].pts_ep = src_cl->cpi.entry[ii].coarse[jj].pts_ep;
                dest_cl->cpi.entry[ii].coarse[jj].spn_ep = src_cl->cpi.entry[ii].coarse[jj].spn_ep;
            }
            dest_cl->cpi.entry[ii].fine = malloc(src_cl->cpi.entry[ii].num_ep_fine * sizeof(CLPI_EP_FINE));
            if (dest_cl->cpi.entry[ii].num_ep_fine && !dest_cl->cpi.entry[ii].fine) {
                goto fail;
            }
            for (jj = 0; jj < src_cl->cpi.entry[ii].num_ep_fine; jj++) {
                dest_cl->cpi.entry[ii].fine[jj].is_angle_change_point = src_cl->cpi.entry[ii].fine[jj].is_angle_change_point;
                dest_cl->cpi.entry[ii].fine[jj].i_end_position_offset = src_cl->cpi.entry[ii].fine[jj].i_end_position_offset;
                dest_cl->cpi.entry[ii].fine[jj].pts_ep = src_cl->cpi.entry[ii].fine[jj].pts_ep;
                dest_cl->cpi.entry[ii].fine[jj].spn_ep = src_cl->cpi.entry[ii].fine[jj].spn_ep;
            }
        }

        dest_cl->clip.font_info.font_count = src_cl->clip.font_info.font_count;
        if (dest_cl->clip.font_info.font_count) {
            dest_cl->clip.font_info.font = malloc(dest_cl->clip.font_info.font_count * sizeof(CLPI_FONT));
            if (!dest_cl->clip.font_info.font) {
                goto fail;
            }
            memcpy(dest_cl->clip.font_info.font, src_cl->clip.font_info.font, dest_cl->clip.font_info.font_count * sizeof(CLPI_FONT));
        }
    }

    return dest_cl;

 fail:
    BD_DEBUG(DBG_CRIT, "out of memory\n");
    _clpi_free(dest_cl);
    return NULL;
}
