/*
 *  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.Globals;
import davaguine.jmac.tools.JMACException;
import davaguine.jmac.tools.RollBufferFastInt;
import davaguine.jmac.tools.ScaledFirstOrderFilter;

import java.util.Arrays;

/**
 * Author: Dmitry Vaguine
 * Date: 02.05.2004
 * Time: 13:08:34
 */
public class PredictorCompressNormal extends IPredictorCompress {
    private final static int WINDOW_BLOCKS = 512;

    public PredictorCompressNormal(int nCompressionLevel) {
        super(nCompressionLevel);
        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, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter1 = null;
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_HIGH) {
            m_pNNFilter = new NNFilter64(11, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter1 = null;
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_EXTRA_HIGH) {
            m_pNNFilter = new NNFilter256(13, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter1 = new NNFilter32(10, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter2 = null;
        } else if (nCompressionLevel == CompressionLevel.COMPRESSION_LEVEL_INSANE) {
            m_pNNFilter = new NNFilter1280(15, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter1 = new NNFilter256(13, Globals.MAC_VERSION_NUMBER);
            m_pNNFilter2 = new NNFilter16(11, Globals.MAC_VERSION_NUMBER);
        } else {
            throw new JMACException("Unknown Compression Type");
        }
    }

    public int CompressValue(int nA, int nB) {
        // roll the buffers if necessary
        if (m_nCurrentIndex == WINDOW_BLOCKS) {
            m_rbPrediction.Roll();
            m_rbAdapt.Roll();
            m_nCurrentIndex = 0;
        }

        // stage 1: simple, non-adaptive order 1 prediction
        nA = m_Stage1FilterA.Compress(nA);
        nB = m_Stage1FilterB.Compress(nB);

        // stage 2: adaptive offset filter(s)
        int predictIndex = m_rbPrediction.index;
        m_rbPrediction.m_pData[predictIndex] = nA;
        m_rbPrediction.m_pData[predictIndex - 2] = m_rbPrediction.m_pData[predictIndex - 1] - m_rbPrediction.m_pData[predictIndex - 2];

        m_rbPrediction.m_pData[predictIndex - 5] = nB;
        m_rbPrediction.m_pData[predictIndex - 6] = m_rbPrediction.m_pData[predictIndex - 5] - m_rbPrediction.m_pData[predictIndex - 6];

        int nPredictionA = (m_rbPrediction.m_pData[predictIndex - 1] * m_aryM[8]) + (m_rbPrediction.m_pData[predictIndex - 2] * m_aryM[7]) + (m_rbPrediction.m_pData[predictIndex - 3] * m_aryM[6]) + (m_rbPrediction.m_pData[predictIndex - 4] * m_aryM[5]);
        int nPredictionB = (m_rbPrediction.m_pData[predictIndex - 5] * m_aryM[4]) + (m_rbPrediction.m_pData[predictIndex - 6] * m_aryM[3]) + (m_rbPrediction.m_pData[predictIndex - 7] * m_aryM[2]) + (m_rbPrediction.m_pData[predictIndex - 8] * m_aryM[1]) + (m_rbPrediction.m_pData[predictIndex - 9] * m_aryM[0]);

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

        // adapt
        int adaptIndex = m_rbAdapt.index;
        m_rbAdapt.m_pData[adaptIndex] = (m_rbPrediction.m_pData[predictIndex - 1] != 0) ? ((m_rbPrediction.m_pData[predictIndex - 1] >> 30) & 2) - 1 : 0;
        m_rbAdapt.m_pData[adaptIndex - 1] = (m_rbPrediction.m_pData[predictIndex - 2] != 0) ? ((m_rbPrediction.m_pData[predictIndex - 2] >> 30) & 2) - 1 : 0;
        m_rbAdapt.m_pData[adaptIndex - 4] = (m_rbPrediction.m_pData[predictIndex - 5] != 0) ? ((m_rbPrediction.m_pData[predictIndex - 5] >> 30) & 2) - 1 : 0;
        m_rbAdapt.m_pData[adaptIndex - 5] = (m_rbPrediction.m_pData[predictIndex - 6] != 0) ? ((m_rbPrediction.m_pData[predictIndex - 6] >> 30) & 2) - 1 : 0;

        if (nOutput > 0) {
            int indexM = 0;
            int indexA = adaptIndex - 8;
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] -= m_rbAdapt.m_pData[indexA++];
        } else if (nOutput < 0) {
            int indexM = 0;
            int indexA = adaptIndex - 8;
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
            m_aryM[indexM++] += m_rbAdapt.m_pData[indexA++];
        }

        // stage 3: NNFilters
        if (m_pNNFilter != null) {
            nOutput = m_pNNFilter.Compress(nOutput);

            if (m_pNNFilter1 != null) {
                nOutput = m_pNNFilter1.Compress(nOutput);

                if (m_pNNFilter2 != null)
                    nOutput = m_pNNFilter2.Compress(nOutput);
            }
        }

        m_rbPrediction.index++;
        m_rbAdapt.index++;
        m_nCurrentIndex++;

        return nOutput;
    }

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

        m_rbPrediction.Flush();
        m_rbAdapt.Flush();
        m_Stage1FilterA.Flush();
        m_Stage1FilterB.Flush();

        Arrays.fill(m_aryM, 0);

        m_aryM[8] = 360;
        m_aryM[7] = 317;
        m_aryM[6] = -109;
        m_aryM[5] = 98;

        m_nCurrentIndex = 0;
    }

    // buffer information
    protected RollBufferFastInt m_rbPrediction = new RollBufferFastInt(WINDOW_BLOCKS, 10);
    protected RollBufferFastInt m_rbAdapt = new RollBufferFastInt(WINDOW_BLOCKS, 9);

    protected ScaledFirstOrderFilter m_Stage1FilterA = new ScaledFirstOrderFilter(31, 5);
    protected ScaledFirstOrderFilter m_Stage1FilterB = new ScaledFirstOrderFilter(31, 5);

    // adaption
    protected int[] m_aryM = new int[9];

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