/*
 * This file is part of libbluray
 * Copyright (C) 2010  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 "ig_decode.h"

#include "pg_decode.h"           // pg_decode_*()
#include "../hdmv/mobj_parse.h"  // mobj_parse_cmd()
#include "../hdmv/mobj_data.h"   // MOBJ_CMD
#include "../bdnav/uo_mask.h"    // uo_mask_parse()

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

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


static int _decode_button(BITBUFFER *bb, BD_IG_BUTTON *p)
{
    unsigned ii;

    p->id                   = bb_read(bb, 16);

    p->numeric_select_value = bb_read(bb, 16);
    p->auto_action_flag     = bb_read(bb, 1);
    bb_skip(bb, 7);

    p->x_pos                = bb_read(bb, 16);
    p->y_pos                = bb_read(bb, 16);

    p->upper_button_id_ref  = bb_read(bb, 16);
    p->lower_button_id_ref  = bb_read(bb, 16);
    p->left_button_id_ref   = bb_read(bb, 16);
    p->right_button_id_ref  = bb_read(bb, 16);

    p->normal_start_object_id_ref    = bb_read(bb, 16);
    p->normal_end_object_id_ref      = bb_read(bb, 16);
    p->normal_repeat_flag            = bb_read(bb, 1);
    bb_skip(bb, 7);

    p->selected_sound_id_ref         = bb_read(bb, 8);
    p->selected_start_object_id_ref  = bb_read(bb, 16);
    p->selected_end_object_id_ref    = bb_read(bb, 16);
    p->selected_repeat_flag          = bb_read(bb, 1);
    bb_skip(bb, 7);

    p->activated_sound_id_ref        = bb_read(bb, 8);
    p->activated_start_object_id_ref = bb_read(bb, 16);
    p->activated_end_object_id_ref   = bb_read(bb, 16);

    p->num_nav_cmds = bb_read(bb, 16);
    p->nav_cmds     = calloc(p->num_nav_cmds, sizeof(MOBJ_CMD));
    if (!p->nav_cmds) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_nav_cmds; ii++) {
        uint8_t buf[12];
        bb_read_bytes(bb, buf, 12);

        mobj_parse_cmd(buf, &p->nav_cmds[ii]);
    }

    return 1;
}

static void _clean_button(BD_IG_BUTTON *p)
{
    X_FREE(p->nav_cmds);
}

static int _decode_bog(BITBUFFER *bb, BD_IG_BOG *p)
{
    unsigned ii;

    p->default_valid_button_id_ref = bb_read(bb, 16);

    p->num_buttons = bb_read(bb, 8);
    p->button      = calloc(p->num_buttons, sizeof(BD_IG_BUTTON));
    if (!p->button) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_buttons; ii++) {
        if (!_decode_button(bb, &p->button[ii])) {
            return 0;
        }
    }

    return 1;
}

static void _clean_bog(BD_IG_BOG *p)
{
    unsigned ii;

    if (p->button) {
        for (ii = 0; ii < p->num_buttons; ii++) {
            _clean_button(&p->button[ii]);
        }
    }

    X_FREE(p->button);
}

static int _decode_effect(BITBUFFER *bb, BD_IG_EFFECT *p)
{
    unsigned ii;

    p->duration       = bb_read(bb, 24);
    p->palette_id_ref = bb_read(bb, 8);

    p->num_composition_objects = bb_read(bb, 8);
    p->composition_object      = calloc(p->num_composition_objects, sizeof(BD_PG_COMPOSITION_OBJECT));
    if (!p->composition_object) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_composition_objects; ii++) {
        pg_decode_composition_object(bb, &p->composition_object[ii]);
    }

    return 1;
}

static void _clean_effect(BD_IG_EFFECT *p)
{
    X_FREE(p->composition_object);
}

static int _decode_effect_sequence(BITBUFFER *bb, BD_IG_EFFECT_SEQUENCE *p)
{
    unsigned ii;

    p->num_windows = bb_read(bb, 8);
    p->window      = calloc(p->num_windows, sizeof(BD_PG_WINDOW));
    if (!p->window) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_windows; ii++) {
        pg_decode_window(bb, &p->window[ii]);
    }

    p->num_effects = bb_read(bb, 8);
    p->effect      = calloc(p->num_effects, sizeof(BD_IG_EFFECT));
    if (!p->effect) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_effects; ii++) {
        if (!_decode_effect(bb, &p->effect[ii])) {
            return 0;
        }
    }

    return 1;
}

static void _clean_effect_sequence(BD_IG_EFFECT_SEQUENCE *p)
{
    unsigned ii;

    if (p->effect) {
        for (ii = 0; ii < p->num_effects; ii++) {
            _clean_effect(&p->effect[ii]);
        }
    }

    X_FREE(p->effect);

    X_FREE(p->window);
}


static int _decode_uo_mask_table(BITBUFFER *bb, BD_UO_MASK *p)
{
    uint8_t buf[8];
    bb_read_bytes(bb, buf, 8);

    return uo_mask_parse(buf, p);
}

static int _decode_page(BITBUFFER *bb, BD_IG_PAGE *p)
{
    unsigned ii;

    p->id      = bb_read(bb, 8);
    p->version = bb_read(bb, 8);

    _decode_uo_mask_table(bb, &p->uo_mask_table);

    if (!_decode_effect_sequence(bb, &p->in_effects)) {
        return 0;
    }
    if (!_decode_effect_sequence(bb, &p->out_effects)) {
        return 0;
    }

    p->animation_frame_rate_code       = bb_read(bb, 8);
    p->default_selected_button_id_ref  = bb_read(bb, 16);
    p->default_activated_button_id_ref = bb_read(bb, 16);
    p->palette_id_ref                  = bb_read(bb, 8);

    p->num_bogs = bb_read(bb, 8);
    p->bog      = calloc(p->num_bogs, sizeof(BD_IG_BOG));
    if (!p->bog) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_bogs; ii++) {
        if (!_decode_bog(bb, &p->bog[ii])) {
            return 0;
        }
    }

    return 1;
}

static void _clean_page(BD_IG_PAGE *p)
{
    unsigned ii;

    _clean_effect_sequence(&p->in_effects);
    _clean_effect_sequence(&p->out_effects);

    if (p->bog) {
        for (ii = 0; ii < p->num_bogs; ii++) {
          _clean_bog(&p->bog[ii]);
        }
    }

    X_FREE(p->bog);
}

static uint64_t bb_read_u64(BITBUFFER *bb, int i_count)
{
    uint64_t result = 0;
    if (i_count > 32) {
        i_count -= 32;
        result = (uint64_t)bb_read(bb, 32) << i_count;
    }
    result |= bb_read(bb, i_count);
    return result;
}

static int _decode_interactive_composition(BITBUFFER *bb, BD_IG_INTERACTIVE_COMPOSITION *p)
{
    unsigned ii;

    uint32_t data_len = bb_read(bb, 24);
    uint32_t buf_len  = bb->p_end - bb->p;
    if (data_len != buf_len) {
        BD_DEBUG(DBG_DECODE, "ig_decode_interactive(): buffer size mismatch (expected %d, have %d)\n", data_len, buf_len);
        return 0;
    }

    p->stream_model = bb_read(bb, 1);
    p->ui_model     = bb_read(bb, 1);
    bb_skip(bb, 6);

    if (p->stream_model == 0) {
        bb_skip(bb, 7);
        p->composition_timeout_pts = bb_read_u64(bb, 33);
        bb_skip(bb, 7);
        p->selection_timeout_pts = bb_read_u64(bb, 33);
    }

    p->user_timeout_duration = bb_read(bb, 24);

    p->num_pages = bb_read(bb, 8);
    p->page      = calloc(p->num_pages, sizeof(BD_IG_PAGE));
    if (!p->page) {
        BD_DEBUG(DBG_DECODE | DBG_CRIT, "out of memory\n");
        return 0;
    }

    for (ii = 0; ii < p->num_pages; ii++) {
        if (!_decode_page(bb, &p->page[ii])) {
            return 0;
        }
    }

  return 1;
}

static void _clean_interactive_composition(BD_IG_INTERACTIVE_COMPOSITION *p)
{
    unsigned ii;

    if (p->page) {
        for (ii = 0; ii < p->num_pages; ii++) {
            _clean_page(&p->page[ii]);
        }
    }

    X_FREE(p->page);
}

/*
 * segment
 */

int ig_decode_interactive(BITBUFFER *bb, BD_IG_INTERACTIVE *p)
{
    BD_PG_SEQUENCE_DESCRIPTOR sd;

    pg_decode_video_descriptor(bb, &p->video_descriptor);
    pg_decode_composition_descriptor(bb, &p->composition_descriptor);
    pg_decode_sequence_descriptor(bb, &sd);

    if (!sd.first_in_seq) {
        BD_DEBUG(DBG_DECODE, "ig_decode_interactive(): not first in seq\n");
        return 0;
    }
    if (!sd.last_in_seq) {
        BD_DEBUG(DBG_DECODE, "ig_decode_interactive(): not last in seq\n");
        return 0;
    }
    if (!bb_is_align(bb, 0x07)) {
        BD_DEBUG(DBG_DECODE, "ig_decode_interactive(): alignment error\n");
        return 0;
    }

    return _decode_interactive_composition(bb, &p->interactive_composition);
}

void ig_free_interactive(BD_IG_INTERACTIVE **p)
{
    if (p && *p) {
        _clean_interactive_composition(&(*p)->interactive_composition);
        X_FREE(*p);
    }
}
