/*
 *  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.core;

import davaguine.jmac.decoder.IAPEDecompress;
import davaguine.jmac.encoder.IAPECompress;
import davaguine.jmac.info.APEFileInfo;
import davaguine.jmac.info.InputSource;
import davaguine.jmac.info.WaveFormat;
import davaguine.jmac.tools.*;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * Author: Dmitry Vaguine
 * Date: 11.05.2004
 * Time: 16:26:19
 */
public class APESimple {
    public final static int UNMAC_DECODER_OUTPUT_APE = 2;
    public final static int UNMAC_DECODER_OUTPUT_WAV = 1;
    public final static int UNMAC_DECODER_OUTPUT_NONE = 0;
    public final static int BLOCKS_PER_DECODE = 9216;

    public static void VerifyFile(String pInputFilename, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
        // error check the function parameters
        if (pInputFilename == null)
            throw new JMACException("Bad Parameters");

        // see if we can quick verify
        File file = File.createFile(pInputFilename, "r");
        IAPEDecompress spAPEDecompress = null;
        try {
            spAPEDecompress = IAPEDecompress.CreateIAPEDecompress(file);

            APEFileInfo pInfo = spAPEDecompress.getApeInfoInternalInfo();

            if ((pInfo.nVersion < 3980) || (pInfo.spAPEDescriptor == null))
                DecompressCore(spAPEDecompress, null, UNMAC_DECODER_OUTPUT_NONE, -1, progressor);
            else {
                MD5 MD5Helper = new MD5();

                File pIO = spAPEDecompress.getApeInfoIoSource();

                int nHead = (int) (pInfo.nJunkHeaderBytes + pInfo.spAPEDescriptor.nDescriptorBytes);
                int nStart = (int) (nHead + pInfo.spAPEDescriptor.nHeaderBytes + pInfo.spAPEDescriptor.nSeekTableBytes);

                pIO.seek(nHead);
                int nHeadBytes = nStart - nHead;
                byte[] spHeadBuffer = new byte[nHeadBytes];
                pIO.readFully(spHeadBuffer);

                int nBytesLeft = (int) (pInfo.spAPEDescriptor.nHeaderDataBytes + pInfo.spAPEDescriptor.nAPEFrameDataBytes + pInfo.spAPEDescriptor.nTerminatingDataBytes);
                // create the progress helper
                ProgressHelper spMACProgressHelper = new ProgressHelper(nBytesLeft / 16384, progressor);
                byte[] spBuffer = new byte[16384];
                int nBytesRead = 1;
                while ((nBytesLeft > 0) && (nBytesRead > 0)) {
                    int nBytesToRead = Math.min(16384, nBytesLeft);
                    nBytesRead = pIO.read(spBuffer, 0, nBytesToRead);

                    MD5Helper.Update(spBuffer, nBytesRead);
                    nBytesLeft -= nBytesRead;

                    spMACProgressHelper.UpdateProgress();
                    if (spMACProgressHelper.isKillFlag())
                        throw new JMACStoppedByUserException();
                }

                if (nBytesLeft != 0)
                    throw new JMACException("The File Is Broken");

                MD5Helper.Update(spHeadBuffer, nHeadBytes);

                // fire the "complete" progress notification
                spMACProgressHelper.UpdateProgressComplete();

                byte[] cResult = MD5Helper.Final();
                for (int i = 0; i < 16; i++)
                    if (cResult[i] != pInfo.spAPEDescriptor.cFileMD5[i])
                        throw new JMACException("Invalid Checksum");
            }
        } finally {
            file.close();
        }
    }

    /**
     * **************************************************************************************
     * Convert file
     * ***************************************************************************************
     */
    public static void ConvertFile(String pInputFilename, String pOutputFilename, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
        DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_APE, nCompressionLevel, progressor);
    }

    /**
     * **************************************************************************************
     * Decompress file
     * ***************************************************************************************
     */
    public static void DecompressFile(String pInputFilename, String pOutputFilename, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
        if (pOutputFilename == null)
            VerifyFile(pInputFilename, progressor);
        else
            DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_WAV, -1, progressor);
    }

    public static void DecompressCore(String pInputFilename, String pOutputFilename, int nOutputMode, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
        // error check the function parameters
        if (pInputFilename == null)
            throw new JMACException("Bad Parameters");

        // variable declares
        IAPEDecompress spAPEDecompress = null;
        File file = File.createFile(pInputFilename, "r");
        try {
            spAPEDecompress = IAPEDecompress.CreateIAPEDecompress(file);
            DecompressCore(spAPEDecompress, pOutputFilename, nOutputMode, nCompressionLevel, progressor);
        } finally {
            file.close();
        }
    }

    public static void DecompressCore(IAPEDecompress spAPEDecompress, String pOutputFilename, int nOutputMode, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
        // variable declares
        java.io.RandomAccessFile spioOutput = null;
        IAPECompress spAPECompress = null;

        try {
            // create the core
            WaveFormat wfeInput = spAPEDecompress.getApeInfoWaveFormatEx();

            // allocate space for the header
            byte[] waveHeaderBuffer = spAPEDecompress.getApeInfoWavHeaderData(spAPEDecompress.getApeInfoWavHeaderBytes());

            // initialize the output
            if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV) {
                // create the file
                spioOutput = new RandomAccessFile(pOutputFilename, "rw");

                // output the header
                spioOutput.write(waveHeaderBuffer);
            } else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE) {
                // quit if there is nothing to do
                if (spAPEDecompress.getApeInfoFileVersion() == Globals.MAC_VERSION_NUMBER && spAPEDecompress.getApeInfoCompressionLevel() == nCompressionLevel)
                    throw new JMACSkippedException();

                // create and start the compressor
                spAPECompress = IAPECompress.CreateIAPECompress();
                spAPECompress.Start(pOutputFilename, wfeInput, spAPEDecompress.getApeInfoDecompressTotalBlocks() * spAPEDecompress.getApeInfoBlockAlign(),
                        nCompressionLevel, waveHeaderBuffer, spAPEDecompress.getApeInfoWavHeaderBytes());
            }

            int blockAlign = spAPEDecompress.getApeInfoBlockAlign();
            // allocate space for decompression
            byte[] spTempBuffer = new byte[blockAlign * BLOCKS_PER_DECODE];

            int nBlocksLeft = spAPEDecompress.getApeInfoDecompressTotalBlocks();

            // create the progress helper
            ProgressHelper spMACProgressHelper = new ProgressHelper(nBlocksLeft / BLOCKS_PER_DECODE, progressor);

            // main decoding loop
            while (nBlocksLeft > 0) {
                // decode data
                int nBlocksDecoded = spAPEDecompress.GetData(spTempBuffer, BLOCKS_PER_DECODE);

                // handle the output
                if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
                    spioOutput.write(spTempBuffer, 0, nBlocksDecoded * blockAlign);
                else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
                    spAPECompress.AddData(spTempBuffer, nBlocksDecoded * spAPEDecompress.getApeInfoBlockAlign());

                // update amount remaining
                nBlocksLeft -= nBlocksDecoded;

                // update progress and kill flag
                spMACProgressHelper.UpdateProgress();
                if (spMACProgressHelper.isKillFlag())
                    throw new JMACStoppedByUserException();
            }

            // terminate the output
            if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV) {
                // write any terminating WAV data
                if (spAPEDecompress.getApeInfoWavTerminatingBytes() > 0) {
                    byte[] termData = spAPEDecompress.getApeInfoWavTerminatingData(spAPEDecompress.getApeInfoWavTerminatingBytes());

                    int nBytesToWrite = spAPEDecompress.getApeInfoWavTerminatingBytes();
                    spioOutput.write(termData, 0, nBytesToWrite);
                }
            } else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE) {
                // write the WAV data and any tag
                int nTagBytes = spAPEDecompress.getApeInfoTag().GetTagBytes();
                boolean bHasTag = (nTagBytes > 0);
                int nTerminatingBytes = nTagBytes;
                nTerminatingBytes += spAPEDecompress.getApeInfoWavTerminatingBytes();

                if (nTerminatingBytes > 0) {
                    spTempBuffer = spAPEDecompress.getApeInfoWavTerminatingData(nTerminatingBytes);

                    if (bHasTag) {
                        spAPEDecompress.getApeInfoIoSource().seek(spAPEDecompress.getApeInfoIoSource().length() - nTagBytes);
                        spAPEDecompress.getApeInfoIoSource().read(spTempBuffer, spAPEDecompress.getApeInfoWavTerminatingBytes(), nTagBytes);
                    }

                    spAPECompress.Finish(spTempBuffer, nTerminatingBytes, spAPEDecompress.getApeInfoWavTerminatingBytes());
                } else
                    spAPECompress.Finish(null, 0, 0);
            }

            // fire the "complete" progress notification
            spMACProgressHelper.UpdateProgressComplete();
        } finally {
            if (spioOutput != null)
                spioOutput.close();
            if (spAPECompress != null)
                spAPECompress.Kill();
        }
    }

    public static void CompressFile(String pInputFilename, String pOutputFilename, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACStoppedByUserException {
        // declare the variables
        IAPECompress spAPECompress = null;
        InputSource spInputSource = null;

        try {
            byte[] spBuffer = null;

            WaveFormat WaveFormatEx = new WaveFormat();

            // create the input source
            IntegerPointer nAudioBlocks = new IntegerPointer();
            nAudioBlocks.value = 0;
            IntegerPointer nHeaderBytes = new IntegerPointer();
            nHeaderBytes.value = 0;
            IntegerPointer nTerminatingBytes = new IntegerPointer();
            nTerminatingBytes.value = 0;
            spInputSource = InputSource.CreateInputSource(pInputFilename, WaveFormatEx, nAudioBlocks,
                    nHeaderBytes, nTerminatingBytes);

            // create the compressor
            spAPECompress = IAPECompress.CreateIAPECompress();

            // figure the audio bytes
            int nAudioBytes = nAudioBlocks.value * WaveFormatEx.nBlockAlign;

            // start the encoder
            if (nHeaderBytes.value > 0) spBuffer = new byte[nHeaderBytes.value];
            spInputSource.GetHeaderData(spBuffer);
            spAPECompress.Start(pOutputFilename, WaveFormatEx, nAudioBytes,
                    nCompressionLevel, spBuffer, nHeaderBytes.value);

            // set-up the progress
            ProgressHelper spMACProgressHelper = new ProgressHelper(nAudioBytes, progressor);

            // master loop
            int nBytesLeft = nAudioBytes;

            spMACProgressHelper.UpdateStatus("Process data by compressor");

            while (nBytesLeft > 0) {
                int nBytesAdded = spAPECompress.AddDataFromInputSource(spInputSource, nBytesLeft);

                nBytesLeft -= nBytesAdded;

                // update the progress
                spMACProgressHelper.UpdateProgress(nAudioBytes - nBytesLeft);

                // process the kill flag
                if (spMACProgressHelper.isKillFlag())
                    throw new JMACStoppedByUserException();
            }

            spMACProgressHelper.UpdateStatus("Finishing compression");

            // finalize the file
            if (nTerminatingBytes.value > 0) spBuffer = new byte[nTerminatingBytes.value];
            spInputSource.GetTerminatingData(spBuffer);
            spAPECompress.Finish(spBuffer, nTerminatingBytes.value, nTerminatingBytes.value);

            // update the progress to 100%
            spMACProgressHelper.UpdateStatus("Compression finished");
        } finally {
            // kill the compressor if we failed
            if (spAPECompress != null)
                spAPECompress.Kill();
            if (spInputSource != null)
                spInputSource.Close();
        }
    }
}
