/*
 *  21.04.2004 Original verion. davagin@udm.ru.
 *-----------------------------------------------------------------------
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 */

package davaguine.jmac.prediction;

import davaguine.jmac.info.CompressionLevel;
import davaguine.jmac.tools.JMACException;
import davaguine.jmac.tools.RollBufferFastInt;

import java.util.Arrays;

/**
 * Author: Dmitry Vaguine
 * Date: 04.03.2004
 * Time: 14:51:31
 */
public class PredictorDecompress3950toCurrent extends IPredictorDecompress {
    public final static int M_COUNT = 8;
    private final static int WINDOW_BLOCKS = 512;

    public PredictorDecompress3950toCurrent(int nCompressionLevel, int nVersion) {
        super(nCompressionLevel, nVersion);
        m_nVersion = nVersion;

        if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_FAST) {
            m_pNNFilter = null;
            m_pNNFilter1 = null;
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_NORMAL) {
            m_pNNFilter = new NNFilter16(11, nVersion);
            m_pNNFilter1 = null;
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_HIGH) {
            m_pNNFilter = new NNFilter64(11, nVersion);
            m_pNNFilter1 = null;
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_EXTRA_HIGH) {
            m_pNNFilter = new NNFilter256(13, nVersion);
            m_pNNFilter1 = new NNFilter32(10, nVersion);
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_INSANE) {
            m_pNNFilter = new NNFilter1280(15, nVersion);
            m_pNNFilter1 = new NNFilter256(13, nVersion);
            m_pNNFilter2 = new NNFilter16(11, nVersion);
        } else {
            throw new JMACException("Unknown Compression Type");
        }
    }

    public int DecompressValue(int nA) {
        return DecompressValue(nA, 0);
    }

    public int DecompressValue(int nA, int nB) {
        if (m_nCurrentIndex == WINDOW_BLOCKS) {
            // copy forward and adjust pointers
            m_rbPredictionA.Roll();
            m_rbPredictionB.Roll();
            m_rbAdaptA.Roll();
            m_rbAdaptB.Roll();

            m_nCurrentIndex = 0;
        }

        // stage 2: NNFilter
        Object obj;
        if ((obj = m_pNNFilter2) != null)
            nA = ((NNFilter) (obj)).Decompress(nA);
        if ((obj = m_pNNFilter1) != null)
            nA = ((NNFilter) (obj)).Decompress(nA);
        if ((obj = m_pNNFilter) != null)
            nA = ((NNFilter) (obj)).Decompress(nA);

        // stage 1: multiple predictors (order 2 and offset 1)
        int indexA = ((RollBufferFastInt) (obj = m_rbPredictionA)).index;
        RollBufferFastInt predictB;
        int indexB = (predictB = m_rbPredictionB).index;
        int ai[];
        int l;
        (ai = ((RollBufferFastInt) (obj)).m_pData)[indexA] = l = m_nLastValueA;
        int l1 = indexA - 1;
        ai[l1] = l - ai[l1];

        int ai3[] = predictB.m_pData;

        ai3[indexB] = nB - ((scaledFilterBLV * 31) >> 5);
        scaledFilterBLV = nB;

//        ai3[indexB] = m_Stage1FilterB.Compress(nB);
        int k2 = indexB - 1;
        ai3[k2] = ai3[indexB] - ai3[k2];

        int ai2[];
        int ai4[];
        int nPredictionA = (l * (ai2 = m_aryMA)[0]) + (ai[l1] * ai2[1]) + (ai[indexA - 2] * ai2[2]) + (ai[indexA - 3] * ai2[3]);
        int nPredictionB = (ai3[indexB] * (ai4 = m_aryMB)[0]) + (ai3[k2] * ai4[1]) + (ai3[indexB - 2] * ai4[2]) + (ai3[indexB - 3] * ai4[3]) + (ai3[indexB - 4] * ai4[4]);

        int nCurrentA = nA + ((nPredictionA + (nPredictionB >> 1)) >> 10);

        RollBufferFastInt adaptA;
        RollBufferFastInt adaptB;
        int indexAA = (adaptA = m_rbAdaptA).index;
        int indexAB = (adaptB = m_rbAdaptB).index;
        int ai1[];
        (ai1 = m_rbAdaptA.m_pData)[indexAA] = (l != 0) ? ((l >> 30) & 2) - 1 : 0;
        ai1[indexAA - 1] = (ai[l1] != 0) ? ((ai[l1] >> 30) & 2) - 1 : 0;

        int ai5[];
        (ai5 = m_rbAdaptB.m_pData)[indexAB] = (ai3[indexB] != 0) ? ((ai3[indexB] >> 30) & 2) - 1 : 0;
        ai5[indexAB - 1] = (ai3[k2] != 0) ? ((ai3[k2] >> 30) & 2) - 1 : 0;

        if (nA > 0) {
            ai2[0] -= ai1[indexAA];
            ai2[1] -= ai1[indexAA - 1];
            ai2[2] -= ai1[indexAA - 2];
            ai2[3] -= ai1[indexAA - 3];

            ai4[0] -= ai5[indexAB];
            ai4[1] -= ai5[indexAB - 1];
            ai4[2] -= ai5[indexAB - 2];
            ai4[3] -= ai5[indexAB - 3];
            ai4[4] -= ai5[indexAB - 4];
        } else if (nA < 0) {
            ai2[0] += ai1[indexAA];
            ai2[1] += ai1[indexAA - 1];
            ai2[2] += ai1[indexAA - 2];
            ai2[3] += ai1[indexAA - 3];

            ai4[0] += ai5[indexAB];
            ai4[1] += ai5[indexAB - 1];
            ai4[2] += ai5[indexAB - 2];
            ai4[3] += ai5[indexAB - 3];
            ai4[4] += ai5[indexAB - 4];
        }

//        int nRetVal = m_Stage1FilterA.Decompress(nCurrentA);
        scaledFilterALV = nCurrentA + ((scaledFilterALV * 31) >> 5);
        m_nLastValueA = nCurrentA;

        ((RollBufferFastInt) (obj)).index++;
        predictB.index++;
        adaptA.index++;
        adaptB.index++;

        m_nCurrentIndex++;

        return scaledFilterALV;
    }

    public void Flush() {
        NNFilter nnfilter;
        if ((nnfilter = m_pNNFilter) != null)
            nnfilter.Flush();
        if ((nnfilter = m_pNNFilter1) != null)
            nnfilter.Flush();
        if ((nnfilter = m_pNNFilter2) != null)
            nnfilter.Flush();

        Arrays.fill(m_aryMA, 0);
        Arrays.fill(m_aryMB, 0);

        m_rbPredictionA.Flush();
        m_rbPredictionB.Flush();
        m_rbAdaptA.Flush();
        m_rbAdaptB.Flush();

        int ai[];
        (ai = m_aryMA)[0] = 360;
        ai[1] = 317;
        ai[2] = -109;
        ai[3] = 98;

//        m_Stage1FilterA.Flush();
        scaledFilterALV = 0;
//        m_Stage1FilterB.Flush();
        scaledFilterBLV = 0;

        m_nLastValueA = 0;

        m_nCurrentIndex = 0;
    }

    // adaption
    protected int m_aryMA[] = new int[M_COUNT];
    protected int m_aryMB[] = new int[M_COUNT];

    // buffer pointers
    protected RollBufferFastInt m_rbPredictionA = new RollBufferFastInt(WINDOW_BLOCKS, 8);
    protected RollBufferFastInt m_rbPredictionB = new RollBufferFastInt(WINDOW_BLOCKS, 8);

    protected RollBufferFastInt m_rbAdaptA = new RollBufferFastInt(WINDOW_BLOCKS, 8);
    protected RollBufferFastInt m_rbAdaptB = new RollBufferFastInt(WINDOW_BLOCKS, 8);

//    protected ScaledFirstOrderFilter m_Stage1FilterA = new ScaledFirstOrderFilter(31, 5);
    protected int scaledFilterALV;
//    protected ScaledFirstOrderFilter m_Stage1FilterB = new ScaledFirstOrderFilter(31, 5);
    protected int scaledFilterBLV;

    // other
    protected int m_nCurrentIndex;
    protected int m_nLastValueA;
    protected int m_nVersion;
    protected NNFilter m_pNNFilter;
    protected NNFilter m_pNNFilter1;
    protected NNFilter m_pNNFilter2;
}
