/*
 * Copyright (C) 2000 ymnk<ymnk@jcraft.com>
 *               2015 Trilarion
 *
 * This program 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 3 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 Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.github.trilarion.sound.vorbis.jcraft.jorbis;

import java.util.logging.Logger;
import com.github.trilarion.sound.util.Util;
import com.github.trilarion.sound.vorbis.jcraft.jogg.Buffer;
import com.github.trilarion.sound.vorbis.jcraft.jogg.Packet;

/**
 *
 */
public class Info {

    private static final Logger LOG = Logger.getLogger(Info.class.getName());

    private static final int OV_EBADPACKET = -136;
    private static final int OV_ENOTAUDIO = -135;

    private static final byte[] _vorbis = "vorbis".getBytes();
    private static final int VI_TIMEB = 1;
    //  private static final int VI_FLOORB=1;
    private static final int VI_FLOORB = 2;
    //  private static final int VI_RESB=1;
    private static final int VI_RESB = 3;
    private static final int VI_MAPB = 1;
    private static final int VI_WINDOWB = 1;

    /**
     *
     */
    public int version;

    /**
     *
     */
    public int channels;

    /**
     *
     */
    public int rate;

    // The below bitrate declarations are *hints*.
    // Combinations of the three values carry the following implications:
    //     
    // all three set to the same value: 
    // implies a fixed rate bitstream
    // only nominal set: 
    // implies a VBR stream that averages the nominal bitrate.  No hard 
    // upper/lower limit
    // upper and or lower set: 
    // implies a VBR bitstream that obeys the bitrate limits. nominal 
    // may also be set to give a nominal rate.
    // none set:
    //  the coder does not care to speculate.
    int bitrate_upper;
    int bitrate_nominal;
    int bitrate_lower;

    // Vorbis supports only short and long blocks, but allows the
    // encoder to choose the sizes
    int[] blocksizes = new int[2];

    // modes are the primary means of supporting on-the-fly different
    // blocksizes, different channel mappings (LR or mid-side),
    // different residue backends, etc.  Each mode consists of a
    // blocksize flag and a mapping (along with the mapping setup
    int modes;
    int maps;
    int times;
    int floors;
    int residues;
    int books;
    int psys; // encode only

    InfoMode[] mode_param = null;

    int[] map_type = null;
    Object[] map_param = null;

    int[] time_type = null;
    Object[] time_param = null;

    int[] floor_type = null;
    Object[] floor_param = null;

    int[] residue_type = null;
    Object[] residue_param = null;

    StaticCodeBook[] book_param = null;

    PsyInfo[] psy_param = new PsyInfo[64]; // encode only

    // for block long/sort tuning; encode only
    int envelopesa;
    float preecho_thresh;
    float preecho_clamp;

    // used by synthesis, which has a full, alloced vi
    /**
     *
     */
    public void init() {
        rate = 0;
    }

    /**
     *
     */
    public void clear() {
        for (int i = 0; i < modes; i++) {
            mode_param[i] = null;
        }
        mode_param = null;

        for (int i = 0; i < maps; i++) { // unpack does the range checking
            FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]);
        }
        map_param = null;

        for (int i = 0; i < times; i++) { // unpack does the range checking
            FuncTime.time_P[time_type[i]].free_info(time_param[i]);
        }
        time_param = null;

        for (int i = 0; i < floors; i++) { // unpack does the range checking
            FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]);
        }
        floor_param = null;

        for (int i = 0; i < residues; i++) { // unpack does the range checking
            FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]);
        }
        residue_param = null;

        // the static codebooks *are* freed if you call info_clear, because
        // decode side does alloc a 'static' codebook. Calling clear on the
        // full codebook does not clear the static codebook (that's our
        // responsibility)
        for (int i = 0; i < books; i++) {
            // just in case the decoder pre-cleared to save space
            if (book_param[i] != null) {
                book_param[i].clear();
                book_param[i] = null;
            }
        }
        //if(vi->book_param)free(vi->book_param);
        book_param = null;

        for (int i = 0; i < psys; i++) {
            psy_param[i].free();
        }

    }

    // Header packing/unpacking
    int unpack_info(Buffer opb) {
        version = opb.read(32);
        if (version != 0) {
            return (-1);
        }

        channels = opb.read(8);
        rate = opb.read(32);

        bitrate_upper = opb.read(32);
        bitrate_nominal = opb.read(32);
        bitrate_lower = opb.read(32);

        blocksizes[0] = 1 << opb.read(4);
        blocksizes[1] = 1 << opb.read(4);

        if ((rate < 1) || (channels < 1) || (blocksizes[0] < 8) || (blocksizes[1] < blocksizes[0])
                || (opb.read(1) != 1)) {
            clear();
            return (-1);
        }
        return (0);
    }

    // all of the real encoding details are here.  The modes, books,
    // everything
    int unpack_books(Buffer opb) {

        books = opb.read(8) + 1;

        if (book_param == null || book_param.length != books) {
            book_param = new StaticCodeBook[books];
        }
        for (int i = 0; i < books; i++) {
            book_param[i] = new StaticCodeBook();
            if (book_param[i].unpack(opb) != 0) {
                clear();
                return (-1);
            }
        }

        // time backend settings
        times = opb.read(6) + 1;
        if (time_type == null || time_type.length != times) {
            time_type = new int[times];
        }
        if (time_param == null || time_param.length != times) {
            time_param = new Object[times];
        }
        for (int i = 0; i < times; i++) {
            time_type[i] = opb.read(16);
            if (time_type[i] < 0 || time_type[i] >= VI_TIMEB) {
                clear();
                return (-1);
            }
            time_param[i] = FuncTime.time_P[time_type[i]].unpack(this, opb);
            if (time_param[i] == null) {
                clear();
                return (-1);
            }
        }

        // floor backend settings
        floors = opb.read(6) + 1;
        if (floor_type == null || floor_type.length != floors) {
            floor_type = new int[floors];
        }
        if (floor_param == null || floor_param.length != floors) {
            floor_param = new Object[floors];
        }

        for (int i = 0; i < floors; i++) {
            floor_type[i] = opb.read(16);
            if (floor_type[i] < 0 || floor_type[i] >= VI_FLOORB) {
                clear();
                return (-1);
            }

            floor_param[i] = FuncFloor.floor_P[floor_type[i]].unpack(this, opb);
            if (floor_param[i] == null) {
                clear();
                return (-1);
            }
        }

        // residue backend settings
        residues = opb.read(6) + 1;

        if (residue_type == null || residue_type.length != residues) {
            residue_type = new int[residues];
        }

        if (residue_param == null || residue_param.length != residues) {
            residue_param = new Object[residues];
        }

        for (int i = 0; i < residues; i++) {
            residue_type[i] = opb.read(16);
            if (residue_type[i] < 0 || residue_type[i] >= VI_RESB) {
                clear();
                return (-1);
            }
            residue_param[i] = FuncResidue.residue_P[residue_type[i]].unpack(this, opb);
            if (residue_param[i] == null) {
                clear();
                return (-1);
            }
        }

        // map backend settings
        maps = opb.read(6) + 1;
        if (map_type == null || map_type.length != maps) {
            map_type = new int[maps];
        }
        if (map_param == null || map_param.length != maps) {
            map_param = new Object[maps];
        }
        for (int i = 0; i < maps; i++) {
            map_type[i] = opb.read(16);
            if (map_type[i] < 0 || map_type[i] >= VI_MAPB) {
                clear();
                return (-1);
            }
            map_param[i] = FuncMapping.mapping_P[map_type[i]].unpack(this, opb);
            if (map_param[i] == null) {
                clear();
                return (-1);
            }
        }

        // mode settings
        modes = opb.read(6) + 1;
        if (mode_param == null || mode_param.length != modes) {
            mode_param = new InfoMode[modes];
        }
        for (int i = 0; i < modes; i++) {
            mode_param[i] = new InfoMode();
            mode_param[i].blockflag = opb.read(1);
            mode_param[i].windowtype = opb.read(16);
            mode_param[i].transformtype = opb.read(16);
            mode_param[i].mapping = opb.read(8);

            if ((mode_param[i].windowtype >= VI_WINDOWB)
                    || (mode_param[i].transformtype >= VI_WINDOWB)
                    || (mode_param[i].mapping >= maps)) {
                clear();
                return (-1);
            }
        }

        if (opb.read(1) != 1) {
            clear();
            return (-1);
        }

        return (0);
    }

    // The Vorbis header is in three packets; the initial small packet in
    // the first page that identifies basic parameters, a second packet
    // with bitstream comments and a third packet that holds the
    // codebook.
    /**
     *
     * @param vc
     * @param op
     * @return
     */
    public int synthesis_headerin(Comment vc, Packet op) {
        Buffer opb = new Buffer();

        if (op != null) {
            opb.readinit(op.packet_base, op.packet, op.bytes);

            // Which of the three types of header is this?
            // Also verify header-ness, vorbis
            {
                byte[] buffer = new byte[6];
                int packtype = opb.read(8);
                opb.read(buffer, 6);
                if (buffer[0] != 'v' || buffer[1] != 'o' || buffer[2] != 'r' || buffer[3] != 'b'
                        || buffer[4] != 'i' || buffer[5] != 's') {
                    // not a vorbis header
                    return (-1);
                }
                switch (packtype) {
                    case 0x01: // least significant *bit* is read first
                        if (op.b_o_s == 0) {
                            // Not the initial packet
                            return (-1);
                        }
                        if (rate != 0) {
                            // previously initialized info header
                            return (-1);
                        }
                        return (unpack_info(opb));
                    case 0x03: // least significant *bit* is read first
                        if (rate == 0) {
                            // um... we didn't get the initial header
                            return (-1);
                        }
                        return (vc.unpack(opb));
                    case 0x05: // least significant *bit* is read first
                        if (rate == 0 || vc.vendor == null) {
                            // um... we didn;t get the initial header or comments yet
                            return (-1);
                        }
                        return (unpack_books(opb));
                    default:
                        // Not a valid vorbis header type
                        //return(-1);
                        break;
                }
            }
        }
        return (-1);
    }

    // pack side
    int pack_info(Buffer opb) {
        // preamble
        opb.write(0x01, 8);
        opb.write(_vorbis);

        // basic information about the stream
        opb.write(0x00, 32);
        opb.write(channels, 8);
        opb.write(rate, 32);

        opb.write(bitrate_upper, 32);
        opb.write(bitrate_nominal, 32);
        opb.write(bitrate_lower, 32);

        opb.write(Util.ilog2(blocksizes[0]), 4);
        opb.write(Util.ilog2(blocksizes[1]), 4);
        opb.write(1, 1);
        return (0);
    }

    int pack_books(Buffer opb) {
        opb.write(0x05, 8);
        opb.write(_vorbis);

        // books
        opb.write(books - 1, 8);
        for (int i = 0; i < books; i++) {
            if (book_param[i].pack(opb) != 0) {
                //goto err_out;
                return (-1);
            }
        }

        // times
        opb.write(times - 1, 6);
        for (int i = 0; i < times; i++) {
            opb.write(time_type[i], 16);
            FuncTime.time_P[time_type[i]].pack(this.time_param[i], opb);
        }

        // floors
        opb.write(floors - 1, 6);
        for (int i = 0; i < floors; i++) {
            opb.write(floor_type[i], 16);
            FuncFloor.floor_P[floor_type[i]].pack(floor_param[i], opb);
        }

        // residues
        opb.write(residues - 1, 6);
        for (int i = 0; i < residues; i++) {
            opb.write(residue_type[i], 16);
            FuncResidue.residue_P[residue_type[i]].pack(residue_param[i], opb);
        }

        // maps
        opb.write(maps - 1, 6);
        for (int i = 0; i < maps; i++) {
            opb.write(map_type[i], 16);
            FuncMapping.mapping_P[map_type[i]].pack(this, map_param[i], opb);
        }

        // modes
        opb.write(modes - 1, 6);
        for (int i = 0; i < modes; i++) {
            opb.write(mode_param[i].blockflag, 1);
            opb.write(mode_param[i].windowtype, 16);
            opb.write(mode_param[i].transformtype, 16);
            opb.write(mode_param[i].mapping, 8);
        }
        opb.write(1, 1);
        return (0);
    }

    /**
     *
     * @param op
     * @return
     */
    public int blocksize(Packet op) {
        //codec_setup_info
        Buffer opb = new Buffer();

        int mode;

        opb.readinit(op.packet_base, op.packet, op.bytes);

        /* Check the packet type */
        if (opb.read(1) != 0) {
            /* Oops.  This is not an audio data packet */
            return (OV_ENOTAUDIO);
        }
        {
            int modebits = 0;
            int v = modes;
            while (v > 1) {
                modebits++;
                v >>>= 1;
            }

            /* read our mode and pre/post windowsize */
            mode = opb.read(modebits);
        }
        if (mode == -1) {
            return (OV_EBADPACKET);
        }
        return (blocksizes[mode_param[mode].blockflag]);
    }

    @Override
    public String toString() {
        return "version:" + version + ", channels:" + channels
                + ", rate:" + rate + ", bitrate:" + bitrate_upper
                + "," + bitrate_nominal + "," + bitrate_lower;
    }
}
