package v4lk.lwbd.decoders.processing.jflac;

/**
 * libFLAC - Free Lossless Audio Codec library
 * Copyright (C) 2000,2001,2002,2003  Josh Coalson
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * <p>
 * This library 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
 * Library General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

import v4lk.lwbd.decoders.processing.jflac.frame.*;
import v4lk.lwbd.decoders.processing.jflac.io.BitInputStream;
import v4lk.lwbd.decoders.processing.jflac.io.RandomFileInputStream;
import v4lk.lwbd.decoders.processing.jflac.metadata.*;
import v4lk.lwbd.decoders.processing.jflac.util.ByteData;
import v4lk.lwbd.decoders.processing.jflac.util.CRC16;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;

/**
 * A Java FLAC decoder.
 * @author kc7bfi
 */
public class FLACDecoder {
    private static final int FRAME_FOOTER_CRC_LEN = 16; // bits
    private static final byte[] ID3V2_TAG = new byte[]{'I', 'D', '3'};

    private BitInputStream bitStream;
    private ChannelData[] channelData = new ChannelData[Constants.MAX_CHANNELS];
    private int outputCapacity = 0;
    private int outputChannels = 0;
    private int lastFrameNumber;
    private long samplesDecoded = 0;
    private StreamInfo streamInfo;
    private Frame frame = new Frame();
    private byte[] headerWarmup = new byte[2]; // contains the sync code and reserved bits
    //private int state;
    private int channels;
    private int channelAssignment;
    private int bitsPerSample;
    private int sampleRate; // in Hz
    private int blockSize; // in samples (per channel)
    private InputStream inputStream = null;

    private int badFrames;
    private boolean eof = false;

    private FrameListeners frameListeners = new FrameListeners();
    private PCMProcessors pcmProcessors = new PCMProcessors();

    // Decoder states
    //private static final int DECODER_SEARCH_FOR_METADATA = 0;
    //private static final int DECODER_READ_METADATA = 1;
    //private static final int DECODER_SEARCH_FOR_FRAME_SYNC = 2;
    //private static final int DECODER_READ_FRAME = 3;
    //private static final int DECODER_END_OF_STREAM = 4;
    //private static final int DECODER_ABORTED = 5;
    //private static final int DECODER_UNPARSEABLE_STREAM = 6;
    //private static final int STREAM_DECODER_MEMORY_ALLOCATION_ERROR = 7;
    //private static final int STREAM_DECODER_ALREADY_INITIALIZED = 8;
    //private static final int STREAM_DECODER_INVALID_CALLBACK = 9;
    //private static final int STREAM_DECODER_UNINITIALIZED = 10;

    /**
     * The constructor.
     * @param inputStream    The input stream to read data from
     */
    public FLACDecoder(InputStream inputStream) {
        this.inputStream = inputStream;
        this.bitStream = new BitInputStream(inputStream);
        //state = DECODER_SEARCH_FOR_METADATA;
        lastFrameNumber = 0;
        samplesDecoded = 0;
        //state = DECODER_SEARCH_FOR_METADATA;
    }

    /**
     * Return the parsed StreamInfo Metadata record.
     * @return The StreamInfo
     */
    public StreamInfo getStreamInfo() {
        return streamInfo;
    }

    /**
     * Return the ChannelData object.
     * @return The ChannelData object
     */
    public ChannelData[] getChannelData() {
        return channelData;
    }

    /**
     * Return the input but stream.
     * @return The bit stream
     */
    public BitInputStream getBitInputStream() {
        return bitStream;
    }

    /**
     * Add a frame listener.
     * @param listener  The frame listener to add
     */
    public void addFrameListener(FrameListener listener) {
        frameListeners.addFrameListener(listener);
    }

    /**
     * Remove a frame listener.
     * @param listener  The frame listener to remove
     */
    public void removeFrameListener(FrameListener listener) {
        frameListeners.removeFrameListener(listener);
    }

    /**
     * Add a PCM processor.
     * @param processor  The processor listener to add
     */
    public void addPCMProcessor(PCMProcessor processor) {
        pcmProcessors.addPCMProcessor(processor);
    }

    /**
     * Remove a PCM processor.
     * @param processor  The processor listener to remove
     */
    public void removePCMProcessor(PCMProcessor processor) {
        pcmProcessors.removePCMProcessor(processor);
    }

    private void callPCMProcessors(Frame frame) {
        ByteData bd = decodeFrame(frame, null);
        pcmProcessors.processPCM(bd);
    }

    /**
     * Fill the given ByteData object with PCM data from the frame.
     *
     * @param frame the frame to send to the PCM processors
     * @param pcmData the byte data to be filled, or null if it should be allocated
     * @return the ByteData that was filled (may be a new instance from <code>space</code>) 
     */
    public ByteData decodeFrame(Frame frame, ByteData pcmData) {
        // required size of the byte buffer
        int byteSize = frame.header.blockSize * channels * ((streamInfo.getBitsPerSample() + 7) / 2);
        if (pcmData == null || pcmData.getData().length < byteSize) {
            pcmData = new ByteData(byteSize);
        } else {
            pcmData.setLen(0);
        }
        if (streamInfo.getBitsPerSample() == 8) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    pcmData.append((byte) (channelData[channel].getOutput()[i] + 0x80));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 16) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    short val = (short) (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 24) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    int val = (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                    pcmData.append((byte) ((val >> 16) & 0xff));
                }
            }
        }
        return pcmData;
    }

    /**
     * Read the FLAC stream info.
     * @return The FLAC Stream Info record
     * @throws java.io.IOException On read error
     */
    public StreamInfo readStreamInfo() throws IOException {
        readStreamSync();
        Metadata metadata = readNextMetadata();
        if (!(metadata instanceof StreamInfo)) throw new IOException("StreamInfo metadata block missing");
        return (StreamInfo) metadata;
    }

    /**
     * Read an array of metadata blocks.
     * @return The array of metadata blocks
     * @throws java.io.IOException  On read error
     */
    public Metadata[] readMetadata() throws IOException {
        readStreamSync();
        Vector metadataList = new Vector();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return (Metadata[]) metadataList.toArray(new Metadata[0]);
    }

    /**
     * Read an array of metadata blocks.
     * @param streamInfo    The StreamInfo metadata block previously read
     * @return The array of metadata blocks
     * @throws java.io.IOException  On read error
     */
    public Metadata[] readMetadata(StreamInfo streamInfo) throws IOException {
        if (streamInfo.isLast()) return new Metadata[0];
        Vector metadataList = new Vector();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return (Metadata[]) metadataList.toArray(new Metadata[0]);
    }

    /**
     * process a single metadata/frame.
     * @return True of one processed
     * @throws java.io.IOException  on read error
     */
    /*
     public boolean processSingle() throws IOException {
     
     while (true) {
     switch (state) {
     case DECODER_SEARCH_FOR_METADATA :
     readStreamSync();
     break;
     case DECODER_READ_METADATA :
     readNextMetadata(); // above function sets the status for us
     return true;
     case DECODER_SEARCH_FOR_FRAME_SYNC :
     frameSync(); // above function sets the status for us
     break;
     case DECODER_READ_FRAME :
     readFrame();
     return true; // above function sets the status for us
     //break;
      case DECODER_END_OF_STREAM :
      case DECODER_ABORTED :
      return true;
      default :
      return false;
      }
      }
      }
      */

    /**
     * Process all the metadata records.
     * @throws java.io.IOException On read error
     */
    /*
     public void processMetadata() throws IOException {
     
     while (true) {
     switch (state) {
     case DECODER_SEARCH_FOR_METADATA :
     readStreamSync();
     break;
     case DECODER_READ_METADATA :
     readNextMetadata(); // above function sets the status for us
     break;
     case DECODER_SEARCH_FOR_FRAME_SYNC :
     case DECODER_READ_FRAME :
     case DECODER_END_OF_STREAM :
     case DECODER_ABORTED :
     default :
     return;
     }
     }
     }
     */

    /**
     * Decode the FLAC file.
     * @throws java.io.IOException  On read error
     */
    public void decode() throws IOException {
        readMetadata();
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }

    /**
     * Decode the data frames.
     * @throws java.io.IOException  On read error
     */
    public void decodeFrames() throws IOException {
        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }

    /**
     * Decode the data frames between two seek points.
     * @param from  The starting seek point
     * @param to    The ending seek point (non-inclusive)
     * @throws java.io.IOException  On read error
     */
    public void decode(SeekPoint from, SeekPoint to) throws IOException {
        // position random access file
        if (!(inputStream instanceof RandomFileInputStream))
            throw new IOException("Not a RandomFileInputStream: " + inputStream.getClass().getName());
        ((RandomFileInputStream) inputStream).seek(from.getStreamOffset());
        bitStream.reset();
        samplesDecoded = from.getSampleNumber();

        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //frameListeners.processFrame(frame);
                //callPCMProcessors(frame);
                //System.out.println(samplesDecoded +" "+ to.getSampleNumber());
                if (to != null && samplesDecoded >= to.getSampleNumber()) return;
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }
    
    /*
     private boolean processUntilEndOfStream() throws IOException {
     //boolean got_a_frame;
      
      while (true) {
      switch (state) {
      case DECODER_SEARCH_FOR_METADATA :
      readStreamSync();
      break;
      case DECODER_READ_METADATA :
      readNextMetadata(); // above function sets the status for us
      break;
      case DECODER_SEARCH_FOR_FRAME_SYNC :
      frameSync(); // above function sets the status for us
      //System.exit(0);
       break;
       case DECODER_READ_FRAME :
       readFrame();
       break;
       case DECODER_END_OF_STREAM :
       case DECODER_ABORTED :
       return true;
       default :
       return false;
       }
       }
       }
       */

    /**
     * Read the next data frame.
     * @return The next frame
     * @throws java.io.IOException  on read error
     */
    public Frame readNextFrame() throws IOException {
        //boolean got_a_frame;

        try {
            while (true) {
                //switch (state) {
                //case STREAM_DECODER_SEARCH_FOR_METADATA :
                //    findMetadata();
                //    break;
                //case STREAM_DECODER_READ_METADATA :
                //    readMetadata(); /* above function sets the status for us */
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync(); /* above function sets the status for us */
                //System.exit(0);
                //break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    return frame;
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return null;
                //default :
                //    return null;
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
        return null;
    }

    /**
     * Bytes consumed.
     * @return The number of bytes read
     */
    //public long getBytesConsumed() {
    //    return is.getConsumedBlurbs();
    //}

    /**
     * Bytes read.
     * @return The number of bytes read
     */
    public long getTotalBytesRead() {
        return bitStream.getTotalBytesRead();
    }
    
    /*
     public int getInputBytesUnconsumed() {
     return is.getInputBytesUnconsumed();
     }
     */

    private void allocateOutput(int size, int channels) {
        if (size <= outputCapacity && channels <= outputChannels) return;

        for (int i = 0; i < Constants.MAX_CHANNELS; i++) {
            channelData[i] = null;
        }

        for (int i = 0; i < channels; i++) {
            channelData[i] = new ChannelData(size);
        }

        outputCapacity = size;
        outputChannels = channels;
    }

    /**
     * Read the stream sync string.
     * @throws java.io.IOException  On read error
     */
    private void readStreamSync() throws IOException {
        int id = 0;
        for (int i = 0; i < 4; ) {
            int x = bitStream.readRawUInt(8);
            if (x == Constants.STREAM_SYNC_STRING[i]) {
                i++;
                id = 0;
            } else if (x == ID3V2_TAG[id]) {
                id++;
                i = 0;
                if (id == 3) {
                    skipID3v2Tag();
                    id = 0;
                }
            } else {
                throw new IOException("Could not find Stream Sync");
                //i = 0;
                //id = 0;
            }
        }
    }

    /**
     * Read a single metadata record.
     * @return The next metadata record
     * @throws java.io.IOException  on read error
     */
    public Metadata readNextMetadata() throws IOException {
        Metadata metadata = null;

        boolean isLast = (bitStream.readRawUInt(Metadata.STREAM_METADATA_IS_LAST_LEN) != 0);
        int type = bitStream.readRawUInt(Metadata.STREAM_METADATA_TYPE_LEN);
        int length = bitStream.readRawUInt(Metadata.STREAM_METADATA_LENGTH_LEN);

        if (type == Metadata.METADATA_TYPE_STREAMINFO) {
            streamInfo = new StreamInfo(bitStream, length, isLast);
            metadata = streamInfo;
            pcmProcessors.processStreamInfo((StreamInfo) metadata);
        } else if (type == Metadata.METADATA_TYPE_SEEKTABLE) {
            metadata = new SeekTable(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_APPLICATION) {
            metadata = new Application(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_PADDING) {
            metadata = new Padding(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_VORBIS_COMMENT) {
            metadata = new VorbisComment(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_CUESHEET) {
            metadata = new CueSheet(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_PICTURE) {
            metadata = new Picture(bitStream, length, isLast);
        } else {
            metadata = new Unknown(bitStream, length, isLast);
        }
        frameListeners.processMetadata(metadata);
        //if (isLast) state = DECODER_SEARCH_FOR_FRAME_SYNC;
        return metadata;
    }


    private void skipID3v2Tag() throws IOException {

        // skip the version and flags bytes 
        int verMajor = bitStream.readRawInt(8);
        int verMinor = bitStream.readRawInt(8);
        int flags = bitStream.readRawInt(8);

        // get the size (in bytes) to skip
        int skip = 0;
        for (int i = 0; i < 4; i++) {
            int x = bitStream.readRawUInt(8);
            skip <<= 7;
            skip |= (x & 0x7f);
        }

        // skip the rest of the tag
        bitStream.readByteBlockAlignedNoCRC(null, skip);
    }

    private void findFrameSync() throws IOException {
        boolean first = true;
        //int cnt=0;

        // If we know the total number of samples in the stream, stop if we've read that many.
        // This will stop us, for example, from wasting time trying to sync on an ID3V1 tag.
        if (streamInfo != null && (streamInfo.getTotalSamples() != 0)) {
            if (samplesDecoded >= streamInfo.getTotalSamples()) {
                //state = DECODER_END_OF_STREAM;
                return;
            }
        }

        // make sure we're byte aligned
        if (!bitStream.isConsumedByteAligned()) {
            bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
        }

        int x;
        try {
            while (true) {
                x = bitStream.readRawUInt(8);
                if (x == 0xff) { // MAGIC NUMBER for the first 8 frame sync bits
                    headerWarmup[0] = (byte) x;
                    x = bitStream.peekRawUInt(8);

                    /* we have to check if we just read two 0xff's in a row; the second may actually be the beginning of the sync code */
                    /* else we have to check if the second byte is the end of a sync code */
                    if (x >> 2 == 0x3e) { /* MAGIC NUMBER for the last 6 sync bits */
                        headerWarmup[1] = (byte) bitStream.readRawUInt(8);
                        //state = DECODER_READ_FRAME;
                        return;
                    }
                }
                if (first) {
                    frameListeners.processError("FindSync LOST_SYNC: " + Integer.toHexString((x & 0xff)));
                    first = false;
                }
            }
        } catch (EOFException e) {
            if (!first) frameListeners.processError("FindSync LOST_SYNC: Left over data in file");
            //state = DECODER_END_OF_STREAM;
        }
    }

    /**
     * Read the next data frame.
     * @throws java.io.IOException  On read error
     * @throws FrameDecodeException On frame decoding error
     */
    public void readFrame() throws IOException, FrameDecodeException {
        boolean gotAFrame = false;
        int channel;
        int i;
        int mid, side, left, right;
        short frameCRC; /* the one we calculate from the input stream */
        //int x;

        /* init the CRC */
        frameCRC = 0;
        frameCRC = CRC16.update(headerWarmup[0], frameCRC);
        frameCRC = CRC16.update(headerWarmup[1], frameCRC);
        bitStream.resetReadCRC16(frameCRC);

        try {
            frame.header = new Header(bitStream, headerWarmup, streamInfo);
        } catch (BadHeaderException e) {
            frameListeners.processError("Found bad header: " + e);
            throw new FrameDecodeException("Bad Frame Header: " + e);
        }
        //if (state == DECODER_SEARCH_FOR_FRAME_SYNC) return false;
        allocateOutput(frame.header.blockSize, frame.header.channels);
        for (channel = 0; channel < frame.header.channels; channel++) {
            // first figure the correct bits-per-sample of the subframe
            int bps = frame.header.bitsPerSample;
            switch (frame.header.channelAssignment) {
                case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT:
                    /* no adjustment needed */
                    break;
                case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE:
                    if (channel == 1)
                        bps++;
                    break;
                case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE:
                    if (channel == 0)
                        bps++;
                    break;
                case Constants.CHANNEL_ASSIGNMENT_MID_SIDE:
                    if (channel == 1)
                        bps++;
                    break;
                default:
            }
            // now read it
            try {
                readSubframe(channel, bps);
            } catch (IOException e) {
                frameListeners.processError("ReadSubframe: " + e);
                throw e;
            }
        }
        readZeroPadding();

        // Read the frame CRC-16 from the footer and check
        frameCRC = bitStream.getReadCRC16();
        frame.setCRC((short) bitStream.readRawUInt(FRAME_FOOTER_CRC_LEN));
        if (frameCRC == frame.getCRC()) {
            /* Undo any special channel coding */
            switch (frame.header.channelAssignment) {
                case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT:
                    /* do nothing */
                    break;
                case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++)
                        channelData[1].getOutput()[i] = channelData[0].getOutput()[i] - channelData[1].getOutput()[i];
                    break;
                case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++)
                        channelData[0].getOutput()[i] += channelData[1].getOutput()[i];
                    break;
                case Constants.CHANNEL_ASSIGNMENT_MID_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++) {
                        mid = channelData[0].getOutput()[i];
                        side = channelData[1].getOutput()[i];
                        mid <<= 1;
                        if ((side & 1) != 0) // i.e. if 'side' is odd...
                            mid++;
                        left = mid + side;
                        right = mid - side;
                        channelData[0].getOutput()[i] = left >> 1;
                        channelData[1].getOutput()[i] = right >> 1;
                    }
                    //System.exit(1);
                    break;
                default:
                    break;
            }

            gotAFrame = true;
        } else {
            // Bad frame, emit error and zero the output signal
            frameListeners.processError("CRC Error: " + Integer.toHexString((frameCRC & 0xffff)) + " vs " + Integer.toHexString((frame.getCRC() & 0xffff)));
            for (channel = 0; channel < frame.header.channels; channel++) {
                for (int j = 0; j < frame.header.blockSize; j++)
                    channelData[channel].getOutput()[j] = 0;
            }
        }

        // put the latest values into the public section of the decoder instance
        channels = frame.header.channels;
        channelAssignment = frame.header.channelAssignment;
        bitsPerSample = frame.header.bitsPerSample;
        sampleRate = frame.header.sampleRate;
        blockSize = frame.header.blockSize;

        //samplesDecoded = frame.header.sampleNumber + frame.header.blockSize;
        samplesDecoded += frame.header.blockSize;
        //System.out.println(samplesDecoded+" "+frame.header.sampleNumber + " "+frame.header.blockSize);

        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        //return;
    }

    private void readSubframe(int channel, int bps) throws IOException, FrameDecodeException {
        int x;

        x = bitStream.readRawUInt(8); /* MAGIC NUMBER */

        boolean haveWastedBits = ((x & 1) != 0);
        x &= 0xfe;

        int wastedBits = 0;
        if (haveWastedBits) {
            wastedBits = bitStream.readUnaryUnsigned() + 1;
            bps -= wastedBits;
        }

        // Lots of magic numbers here
        if ((x & 0x80) != 0) {
            frameListeners.processError("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //state = DECODER_SEARCH_FOR_FRAME_SYNC;
            throw new FrameDecodeException("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //return true;
        } else if (x == 0) {
            frame.subframes[channel] = new ChannelConstant(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x == 2) {
            frame.subframes[channel] = new ChannelVerbatim(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x < 16) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else if (x <= 24) {
            //FLACSubframe_Fixed subframe = read_subframe_fixed_(channel, bps, (x >> 1) & 7);
            frame.subframes[channel] = new ChannelFixed(bitStream, frame.header, channelData[channel], bps, wastedBits, (x >> 1) & 7);
        } else if (x < 64) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else {
            frame.subframes[channel] = new ChannelLPC(bitStream, frame.header, channelData[channel], bps, wastedBits, ((x >> 1) & 31) + 1);
        }
        if (haveWastedBits) {
            int i;
            x = frame.subframes[channel].getWastedBits();
            for (i = 0; i < frame.header.blockSize; i++)
                channelData[channel].getOutput()[i] <<= x;
        }
    }

    private void readZeroPadding() throws IOException, FrameDecodeException {
        if (!bitStream.isConsumedByteAligned()) {
            int zero = bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
            if (zero != 0) {
                frameListeners.processError("ZeroPaddingError: " + Integer.toHexString(zero));
                //state = DECODER_SEARCH_FOR_FRAME_SYNC;
                throw new FrameDecodeException("ZeroPaddingError: " + Integer.toHexString(zero));
            }
        }
    }

    /**
     * Get the number of samples decoded.
     * @return Returns the samples Decoded.
     */
    public long getSamplesDecoded() {
        return samplesDecoded;
    }

    /**
     * @return Returns the number of bad frames decoded.
     */
    public int getBadFrames() {
        return badFrames;
    }

    /**
     * @return Returns true if end-of-file.
     */
    public boolean isEOF() {
        return eof;
    }
}
