/*
 *  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 java.util.Arrays;

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

    private final static int BUFFER_COUNT = 1;
    private final static int M_COUNT = 8;

    public PredictorDecompressNormal3930to3950(int nCompressionLevel, int nVersion) {
        super(nCompressionLevel, nVersion);
        m_pBuffer[0] = new int[HISTORY_ELEMENTS + WINDOW_BLOCKS];

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

    public int DecompressValue(int nInput, int notneeded) {
        if (m_nCurrentIndex == WINDOW_BLOCKS) {
            // copy forward and adjust pointers
            System.arraycopy(m_pBuffer[0], WINDOW_BLOCKS, m_pBuffer[0], 0, HISTORY_ELEMENTS);
            m_pInputBuffer_i = 0;
            m_pInputBuffer_j = HISTORY_ELEMENTS;

            m_nCurrentIndex = 0;
        }

        // stage 2: NNFilter
        if (m_pNNFilter1 != null)
            nInput = m_pNNFilter1.Decompress(nInput);
        if (m_pNNFilter != null)
            nInput = m_pNNFilter.Decompress(nInput);

        // stage 1: multiple predictors (order 2 and offset 1)

        int p1 = m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 1];
        int p2 = m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 1] - m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 2];
        int p3 = m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 2] - m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 3];
        int p4 = m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 3] - m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j - 4];

        m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j] = nInput + (((p1 * m_aryM[0]) + (p2 * m_aryM[1]) + (p3 * m_aryM[2]) + (p4 * m_aryM[3])) >> 9);

        if (nInput > 0) {
            m_aryM[0] -= ((p1 >> 30) & 2) - 1;
            m_aryM[1] -= ((p2 >> 30) & 2) - 1;
            m_aryM[2] -= ((p3 >> 30) & 2) - 1;
            m_aryM[3] -= ((p4 >> 30) & 2) - 1;
        } else if (nInput < 0) {
            m_aryM[0] += ((p1 >> 30) & 2) - 1;
            m_aryM[1] += ((p2 >> 30) & 2) - 1;
            m_aryM[2] += ((p3 >> 30) & 2) - 1;
            m_aryM[3] += ((p4 >> 30) & 2) - 1;
        }

        int nRetVal = m_pBuffer[m_pInputBuffer_i][m_pInputBuffer_j] + ((m_nLastValue * 31) >> 5);
        m_nLastValue = nRetVal;

        m_nCurrentIndex++;
        m_pInputBuffer_j++;

        return nRetVal;
    }

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

        Arrays.fill(m_pBuffer[0], 0, HISTORY_ELEMENTS, 0);
        Arrays.fill(m_aryM, 0);

        m_aryM[0] = 360;
        m_aryM[1] = 317;
        m_aryM[2] = -109;
        m_aryM[3] = 98;

        m_pInputBuffer_i = 0;
        m_pInputBuffer_j = HISTORY_ELEMENTS;

        m_nLastValue = 0;
        m_nCurrentIndex = 0;
    }

    // buffer information
    protected int[][] m_pBuffer = new int[BUFFER_COUNT][];

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

    // buffer pointers
    protected int m_pInputBuffer_i;
    protected int m_pInputBuffer_j;

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