/*
 * $ProjectName$
 * $ProjectRevision$
 * -----------------------------------------------------------
 * $Id: VorbisStream.java,v 1.4 2003/04/10 19:49:04 jarnbjo Exp $
 * -----------------------------------------------------------
 *
 * $Author: jarnbjo $
 *
 * Description:
 *
 * Copyright 2002-2003 Tor-Einar Jarnbjo
 * -----------------------------------------------------------
 *
 * Change History
 * -----------------------------------------------------------
 * $Log: VorbisStream.java,v $
 * Revision 1.4  2003/04/10 19:49:04  jarnbjo
 * no message
 *
 * Revision 1.3  2003/03/31 00:20:16  jarnbjo
 * no message
 *
 * Revision 1.2  2003/03/16 01:11:12  jarnbjo
 * no message
 *
 *
 */

package de.jarnbjo.vorbis;

import java.io.*;
import java.util.*;

import de.jarnbjo.ogg.*;
import de.jarnbjo.util.io.*;

/**
 */

public class VorbisStream {

   private LogicalOggStream oggStream;
   private IdentificationHeader identificationHeader;
   private CommentHeader commentHeader;
   private SetupHeader setupHeader;

   private AudioPacket lastAudioPacket, nextAudioPacket;
   private LinkedList audioPackets=new LinkedList();
   private byte[] currentPcm;
   private int currentPcmIndex;
   private int currentPcmLimit;

   private static final int IDENTIFICATION_HEADER = 1;
   private static final int COMMENT_HEADER = 3;
   private static final int SETUP_HEADER = 5;

   private int bitIndex=0;
   private byte lastByte=(byte)0;
   private boolean initialized=false;

   private Object streamLock=new Object();
   private int pageCounter=0;

   private int currentBitRate=0;

   private long currentGranulePosition;

   public static final int BIG_ENDIAN = 0;
   public static final int LITTLE_ENDIAN = 1;

   public VorbisStream() {
   }

   public VorbisStream(LogicalOggStream oggStream) throws VorbisFormatException, IOException {
      this.oggStream=oggStream;

      for(int i=0; i<3; i++) {
         BitInputStream source=new ByteArrayBitInputStream(oggStream.getNextOggPacket());
         int headerType=source.getInt(8);
         switch(headerType) {
         case IDENTIFICATION_HEADER:
            identificationHeader=new IdentificationHeader(source);
            break;
         case COMMENT_HEADER:
            commentHeader=new CommentHeader(source);
            break;
         case SETUP_HEADER:
            setupHeader=new SetupHeader(this, source);
            break;
         }
      }

      if(identificationHeader==null) {
         throw new VorbisFormatException("The file has no identification header.");
      }

      if(commentHeader==null) {
         throw new VorbisFormatException("The file has no commentHeader.");
      }

      if(setupHeader==null) {
         throw new VorbisFormatException("The file has no setup header.");
      }

      //currentPcm=new int[identificationHeader.getChannels()][16384];
      currentPcm=new byte[identificationHeader.getChannels()*identificationHeader.getBlockSize1()*2];
      //new BufferThread().start();
   }

   public IdentificationHeader getIdentificationHeader() {
      return identificationHeader;
   }

   public CommentHeader getCommentHeader() {
      return commentHeader;
   }

   protected SetupHeader getSetupHeader() {
      return setupHeader;
   }

   public boolean isOpen() {
      return oggStream.isOpen();
   }

   public void close() throws IOException {
      oggStream.close();
   }


   public int readPcm(byte[] buffer, int offset, int length) throws IOException {
      synchronized (streamLock) {
         final int channels=identificationHeader.getChannels();

         if(lastAudioPacket==null) {
            lastAudioPacket=getNextAudioPacket();
         }
         if(currentPcm==null || currentPcmIndex>=currentPcmLimit) {
            AudioPacket ap=getNextAudioPacket();
            try {
               ap.getPcm(lastAudioPacket, currentPcm);
               currentPcmLimit=ap.getNumberOfSamples()*identificationHeader.getChannels()*2;
            }
            catch(ArrayIndexOutOfBoundsException e) {
               return 0;
            }
            currentPcmIndex=0;
            lastAudioPacket=ap;
         }
         int written=0;
         int i=0;
         int arrIx=0;
         for(i=currentPcmIndex; i<currentPcmLimit && arrIx<length; i++) {
            buffer[offset+arrIx++]=currentPcm[i];
            written++;
         }
         currentPcmIndex=i;
         return written;
      }
   }


   private AudioPacket getNextAudioPacket() throws VorbisFormatException, IOException {
      pageCounter++;
      byte[] data=oggStream.getNextOggPacket();
      AudioPacket res=null;
      while(res==null) {
         try {
            res=new AudioPacket(this, new ByteArrayBitInputStream(data));
         }
         catch(ArrayIndexOutOfBoundsException e) {
            // ignore and continue with next packet
         }
      }
      currentGranulePosition+=res.getNumberOfSamples();
      currentBitRate=data.length*8*identificationHeader.getSampleRate()/res.getNumberOfSamples();
      return res;
   }

   public long getCurrentGranulePosition() {
      return currentGranulePosition;
   }

   public int getCurrentBitRate() {
      return currentBitRate;
   }

   public byte[] processPacket(byte[] packet) throws VorbisFormatException, IOException {
      if(packet.length==0) {
         throw new VorbisFormatException("Cannot decode a vorbis packet with length = 0");
      }
      if(((int)packet[0]&1)==1) {
         // header packet
         BitInputStream source=new ByteArrayBitInputStream(packet);
         switch(source.getInt(8)) {
         case IDENTIFICATION_HEADER:
            identificationHeader=new IdentificationHeader(source);
            break;
         case COMMENT_HEADER:
            commentHeader=new CommentHeader(source);
            break;
         case SETUP_HEADER:
            setupHeader=new SetupHeader(this, source);
            break;
         }
         return null;
      }
      else {
         // audio packet
         if(identificationHeader==null ||
            commentHeader==null ||
            setupHeader==null) {

            throw new VorbisFormatException("Cannot decode audio packet before all three header packets have been decoded.");
         }

         AudioPacket ap=new AudioPacket(this, new ByteArrayBitInputStream(packet));
         currentGranulePosition+=ap.getNumberOfSamples();

         if(lastAudioPacket==null) {
            lastAudioPacket=ap;
            return null;
         }

         byte[] res=new byte[identificationHeader.getChannels()*ap.getNumberOfSamples()*2];

         try {
            ap.getPcm(lastAudioPacket, res);
         }
         catch(IndexOutOfBoundsException e) {
            java.util.Arrays.fill(res, (byte)0);
         }

         lastAudioPacket=ap;

         return res;
      }
   }

}