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

import davaguine.jmac.tools.File;
import davaguine.jmac.tools.InputStreamFile;
import davaguine.jmac.tools.JMACException;
import davaguine.jmac.tools.RandomAccessFile;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

/**
 * Author: Dmitry Vaguine
 * Date: 04.03.2004
 * Time: 14:51:31
 */
public class APEInfo {

    // construction and destruction
    public APEInfo(URL url) throws IOException {
        this(url, null);
    }

    public APEInfo(URL url, APETag pTag) throws IOException {
        this(url.openStream(), pTag);
    }

    public APEInfo(java.io.File file) throws IOException {
        this(file, null);
    }

    public APEInfo(java.io.File file, APETag pTag) throws IOException {
        this(new RandomAccessFile(file, "r"), pTag);
    }

    public APEInfo(InputStream pIO) throws IOException {
        this(pIO, null);
    }

    public APEInfo(InputStream pIO, APETag pTag) throws IOException {
        this(new InputStreamFile(pIO), pTag);
    }

    public APEInfo(File pIO) throws IOException {
        this(pIO, null);
    }

    public APEInfo(File pIO, APETag pTag) throws IOException {
        // open the file
        m_spIO = pIO;

        // get the file information
        GetFileInformation();

        // get the tag (do this second so that we don't do it on failure)
        if (pTag == null) {
            // we don't want to analyze right away for non-local files
            // since a single I/O object is shared, we can't tag and read at the same time (i.e. in multiple threads)

            m_spAPETag = new APETag(m_spIO, pIO.isLocal());
        } else
            m_spAPETag = pTag;
    }

    public void close() throws IOException {
        m_APEFileInfo.spWaveHeaderData = null;
        m_APEFileInfo.spSeekBitTable = null;
        m_APEFileInfo.spSeekByteTable = null;
        m_APEFileInfo.spAPEDescriptor = null;
        m_spAPETag = null;

        // re-initialize variables
        m_APEFileInfo.nSeekTableElements = 0;
        m_bHasFileInformationLoaded = false;
    }

    public int getApeInfoFileVersion() {
        return m_APEFileInfo.nVersion;
    }

    public int getApeInfoCompressionLevel() {
        return m_APEFileInfo.nCompressionLevel;
    }

    public int getApeInfoFormatFlags() {
        return m_APEFileInfo.nFormatFlags;
    }

    public int getApeInfoSampleRate() {
        return m_APEFileInfo.nSampleRate;
    }

    public int getApeInfoBitsPerSample() {
        return m_APEFileInfo.nBitsPerSample;
    }

    public int getApeInfoBytesPerSample() {
        return m_APEFileInfo.nBytesPerSample;
    }

    public int getApeInfoChannels() {
        return m_APEFileInfo.nChannels;
    }

    public int getApeInfoBlockAlign() {
        return m_APEFileInfo.nBlockAlign;
    }

    public int getApeInfoBlocksPerFrame() {
        return m_APEFileInfo.nBlocksPerFrame;
    }

    public int getApeInfoFinalFrameBlocks() {
        return m_APEFileInfo.nFinalFrameBlocks;
    }

    public int getApeInfoTotalFrames() {
        return m_APEFileInfo.nTotalFrames;
    }

    public int getApeInfoWavHeaderBytes() {
        return m_APEFileInfo.nWAVHeaderBytes;
    }

    public int getApeInfoWavTerminatingBytes() {
        return m_APEFileInfo.nWAVTerminatingBytes;
    }

    public int getApeInfoWavDataBytes() {
        return m_APEFileInfo.nWAVDataBytes;
    }

    public int getApeInfoWavTotalBytes() {
        return m_APEFileInfo.nWAVTotalBytes;
    }

    public int getApeInfoApeTotalBytes() {
        return m_APEFileInfo.nAPETotalBytes;
    }

    public int getApeInfoTotalBlocks() {
        return m_APEFileInfo.nTotalBlocks;
    }

    public int getApeInfoLengthMs() {
        return m_APEFileInfo.nLengthMS;
    }

    public int getApeInfoAverageBitrate() {
        return m_APEFileInfo.nAverageBitrate;
    }

    public int getApeInfoSeekByte(int nFrame) {
        return (nFrame < 0 || nFrame >= m_APEFileInfo.nTotalFrames) ? 0 : m_APEFileInfo.spSeekByteTable[nFrame] + m_APEFileInfo.nJunkHeaderBytes;
    }

    public int getApeInfoFrameBytes(int nFrame) throws IOException {
        if ((nFrame < 0) || (nFrame >= m_APEFileInfo.nTotalFrames))
            return -1;
        else {
            if (nFrame != (m_APEFileInfo.nTotalFrames - 1))
                return getApeInfoSeekByte(nFrame + 1) - getApeInfoSeekByte(nFrame);
            else {
                if (m_spIO.isLocal())
                    return (int) m_spIO.length() - m_spAPETag.GetTagBytes() - m_APEFileInfo.nWAVTerminatingBytes - getApeInfoSeekByte(nFrame);
                else if (nFrame > 0)
                    return getApeInfoSeekByte(nFrame) - getApeInfoSeekByte(nFrame - 1);
                else
                    return -1;
            }
        }
    }

    public int getApeInfoFrameBlocks(int nFrame) {
        if ((nFrame < 0) || (nFrame >= m_APEFileInfo.nTotalFrames))
            return -1;
        else {
            if (nFrame != (m_APEFileInfo.nTotalFrames - 1))
                return m_APEFileInfo.nBlocksPerFrame;
            else
                return m_APEFileInfo.nFinalFrameBlocks;
        }
    }

    public int getApeInfoFrameBitrate(int nFrame) throws IOException {
        int nFrameBytes = getApeInfoFrameBytes(nFrame);
        int nFrameBlocks = getApeInfoFrameBlocks(nFrame);
        if ((nFrameBytes > 0) && (nFrameBlocks > 0) && m_APEFileInfo.nSampleRate > 0) {
            int nFrameMS = (nFrameBlocks * 1000) / m_APEFileInfo.nSampleRate;
            if (nFrameMS != 0) {
                return (nFrameBytes * 8) / nFrameMS;
            }
        }
        return m_APEFileInfo.nAverageBitrate;
    }

    public int getApeInfoDecompressedBitrate() {
        return m_APEFileInfo.nDecompressedBitrate;
    }

    public int getApeInfoPeakLevel() {
        return m_APEFileInfo.nPeakLevel;
    }

    public int getApeInfoSeekBit(int nFrame) {
        if (getApeInfoFileVersion() > 3800)
            return 0;
        else {
            if (nFrame < 0 || nFrame >= m_APEFileInfo.nTotalFrames)
                return 0;
            else
                return m_APEFileInfo.spSeekBitTable[nFrame];
        }
    }

    public WaveFormat getApeInfoWaveFormatEx() {
        final WaveFormat pWaveFormatEx = new WaveFormat();
        WaveFormat.FillWaveFormatEx(pWaveFormatEx, m_APEFileInfo.nSampleRate, m_APEFileInfo.nBitsPerSample, m_APEFileInfo.nChannels);
        return pWaveFormatEx;
    }

    public byte[] getApeInfoWavHeaderData(int nMaxBytes) {
        if ((m_APEFileInfo.nFormatFlags & APEHeader.MAC_FORMAT_FLAG_CREATE_WAV_HEADER) > 0) {
            if (WaveHeader.WAVE_HEADER_BYTES > nMaxBytes)
                return null;
            else {
                WaveFormat wfeFormat = getApeInfoWaveFormatEx();
                WaveHeader WAVHeader = new WaveHeader();
                WaveHeader.FillWaveHeader(WAVHeader, m_APEFileInfo.nWAVDataBytes, wfeFormat, m_APEFileInfo.nWAVTerminatingBytes);
                return WAVHeader.write();
            }
        } else {
            if (m_APEFileInfo.nWAVHeaderBytes > nMaxBytes)
                return null;
            else {
                byte[] pBuffer = new byte[m_APEFileInfo.nWAVHeaderBytes];
                System.arraycopy(m_APEFileInfo.spWaveHeaderData, 0, pBuffer, 0, m_APEFileInfo.nWAVHeaderBytes);
                return pBuffer;
            }
        }
    }

    public File getApeInfoIoSource() {
        return m_spIO;
    }

    public APETag getApeInfoTag() {
        return m_spAPETag;
    }

    public byte[] getApeInfoWavTerminatingData(int nMaxBytes) throws IOException {
        if (m_APEFileInfo.nWAVTerminatingBytes > nMaxBytes)
            return null;
        else {
            if (m_APEFileInfo.nWAVTerminatingBytes > 0) {
                // variables
                long nOriginalFileLocation = m_spIO.getFilePointer();

                // check for a tag
                m_spIO.seek(m_spIO.length() - (m_spAPETag.GetTagBytes() + m_APEFileInfo.nWAVTerminatingBytes));
                byte[] pBuffer = new byte[m_APEFileInfo.nWAVTerminatingBytes];
                try {
                    m_spIO.readFully(pBuffer);
                } catch (EOFException e) {
                    throw new JMACException("Can't Read WAV Terminating Bytes");
                }

                // restore the file pointer
                m_spIO.seek(nOriginalFileLocation);
                return pBuffer;
            }
            return null;
        }
    }

    public APEFileInfo getApeInfoInternalInfo() {
        return m_APEFileInfo;
    }

    private void GetFileInformation() throws IOException {
        // quit if the file information has already been loaded
        if (m_bHasFileInformationLoaded)
            return;

        // use a CAPEHeader class to help us analyze the file
        APEHeader APEHeader = new APEHeader(m_spIO);
        APEHeader.Analyze(m_APEFileInfo);

        m_bHasFileInformationLoaded = true;
    }

    // internal variables
    private boolean m_bHasFileInformationLoaded;
    private File m_spIO;
    private APETag m_spAPETag;
    private APEFileInfo m_APEFileInfo = new APEFileInfo();
}
