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

import java.util.Arrays;

/**
 * Author: Dmitry Vaguine
 * Date: 04.03.2004
 * Time: 14:51:31
 */
public abstract class NNFilter {
    public final static int NN_WINDOW_ELEMENTS = 512;

    public NNFilter(int nOrder, int nShift, int nVersion) {
        if ((nOrder <= 0) || ((nOrder % 16) != 0))
            throw new JMACException("Wrong Order");
        m_nOrder = nOrder;
        m_nShift = nShift;
        m_nVersion = nVersion;
        m_rbInput.Create(512 /* NN_WINDOW_ELEMENTS */, nOrder);
        m_rbDeltaM.Create(512 /*NN_WINDOW_ELEMENTS */, nOrder);
        m_paryM = new short[nOrder];
    }

    public int Compress(int nInput) {
        RollBufferShort input = m_rbInput;
        RollBufferShort delta = m_rbDeltaM;
        int order = m_nOrder;
        int shift = m_nShift;
        short[] pary = m_paryM;
        short[] inputData = input.m_pData;
        int inputIndex = input.index;
        short[] deltaData = delta.m_pData;
        int deltaIndex = delta.index;

        // convert the input to a short and store it
        inputData[inputIndex] = (short) ((nInput >= Short.MIN_VALUE && nInput <= Short.MAX_VALUE) ? nInput : (nInput >> 31) ^ 0x7FFF);

        // figure a dot product
        int nDotProduct = CalculateDotProductNoMMX(inputData, inputIndex - order, pary, 0);

        // calculate the output
        int nOutput = nInput - ((nDotProduct + (1 << (m_nShift - 1))) >> m_nShift);

        // adapt
        AdaptNoMMX(pary, 0, deltaData, deltaIndex - order, nOutput);

        int nTempABS = Math.abs(nInput);

        if (nTempABS > (m_nRunningAverage * 3))
            deltaData[deltaIndex] = (short) (((nInput >> 25) & 64) - 32);
        else if (nTempABS > (m_nRunningAverage * 4) / 3)
            deltaData[deltaIndex] = (short) (((nInput >> 26) & 32) - 16);
        else if (nTempABS > 0)
            deltaData[deltaIndex] = (short) (((nInput >> 27) & 16) - 8);
        else
            deltaData[deltaIndex] = (short) 0;

        m_nRunningAverage += (nTempABS - m_nRunningAverage) / 16;

        deltaData[deltaIndex - 1] >>= 1;
        deltaData[deltaIndex - 2] >>= 1;
        deltaData[deltaIndex - 8] >>= 1;

        // increment and roll if necessary
//        input.IncrementSafe();
        if ((++input.index) == orderPlusWindow) {
            System.arraycopy(inputData, input.index - order, inputData, 0, order);
            input.index = order;
        }
//        delta.IncrementSafe();
        if ((++delta.index) == orderPlusWindow) {
            System.arraycopy(deltaData, delta.index - order, deltaData, 0, order);
            delta.index = order;
        }

        return nOutput;
    }

    public int Decompress(int nInput) {
        // figure a dot product
        RollBufferShort input = m_rbInput;
        RollBufferShort delta = m_rbDeltaM;
        int order = m_nOrder;
        int shift = m_nShift;
        short[] pary = m_paryM;
        short[] inputData = input.m_pData;
        int inputIndex = input.index;
        short[] deltaData = delta.m_pData;
        int deltaIndex = delta.index;
        int nDotProduct = CalculateDotProductNoMMX(inputData, inputIndex - order, pary, 0);

        // adapt
        AdaptNoMMX(pary, 0, deltaData, deltaIndex - order, nInput);

        // store the output value
        int nOutput = nInput + ((nDotProduct + (1 << (shift - 1))) >> shift);

        // update the input buffer
        inputData[inputIndex] = (short) ((nOutput >= Short.MIN_VALUE && nOutput <= Short.MAX_VALUE) ? nOutput : (nOutput >> 31) ^ 0x7FFF);

        if (m_nVersion >= 3980) {
            int nTempABS = Math.abs(nOutput);

            if (nTempABS > (m_nRunningAverage * 3))
                deltaData[deltaIndex] = (short) (((nOutput >> 25) & 64) - 32);
            else if (nTempABS > (m_nRunningAverage * 4) / 3)
                deltaData[deltaIndex] = (short) (((nOutput >> 26) & 32) - 16);
            else if (nTempABS > 0)
                deltaData[deltaIndex] = (short) (((nOutput >> 27) & 16) - 8);
            else
                deltaData[deltaIndex] = 0;

            m_nRunningAverage += (nTempABS - m_nRunningAverage) / 16;

            deltaData[deltaIndex - 1] >>= 1;
            deltaData[deltaIndex - 2] >>= 1;
            deltaData[deltaIndex - 8] >>= 1;
        } else {
            deltaData[deltaIndex] = (short) ((nOutput == 0) ? 0 : ((nOutput >> 28) & 8) - 4);
            deltaData[deltaIndex - 4] >>= 1;
            deltaData[deltaIndex - 8] >>= 1;
        }

        // increment and roll if necessary
//        input.IncrementSafe();
        if ((++input.index) == orderPlusWindow) {
            System.arraycopy(inputData, input.index - order, inputData, 0, order);
            input.index = order;
        }
//        delta.IncrementSafe();
        if ((++delta.index) == orderPlusWindow) {
            System.arraycopy(deltaData, delta.index - order, deltaData, 0, order);
            delta.index = order;
        }

        return nOutput;
    }

    public void Flush() {
        Arrays.fill(m_paryM, (short) 0);
        m_rbInput.Flush();
        m_rbDeltaM.Flush();
        m_nRunningAverage = 0;
    }

    protected int m_nOrder;
    protected int m_nShift;
    protected int m_nVersion;
    protected int orderPlusWindow;
    private int m_nRunningAverage;

    private RollBufferShort m_rbInput = new RollBufferShort();
    private RollBufferShort m_rbDeltaM = new RollBufferShort();

    private short[] m_paryM;

    protected abstract int CalculateDotProductNoMMX(short[] pA, int indexA, short[] pB, int indexB);

    protected abstract void AdaptNoMMX(short[] pM, int indexM, short[] pAdapt, int indexA, int nDirection);
}
