package turgen.decoder;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;


public class AudioPulseDecoder implements PulseDecoder {

    private TargetDataLine waveLine;
    private long sample;
    private int lastValue;
    private int counter;
    
    transient private boolean stopRequest;
    private int maxSilence;
    private final byte[] currentByte;
    
    private int numBytes;
    private int byteIndex;
    private int numBits;
    private int numChannels;
    
    private byte[] buffer;
    private int bufferPosition;
    private int bufferedCount;

    public static final int CHANNEL_MONO=0;
    public static final int CHANNEL_LEFT=1;
    public static final int CHANNEL_RIGHT=2;
    
    private int sampleRate;

    public AudioPulseDecoder() {
      waveLine=null;
      sample=0L;
      lastValue=0;
      counter=0;
      stopRequest=false;
      currentByte=new byte[4];

      numBytes=1;
      byteIndex=0;
      bufferedCount=0;
      bufferPosition=0;
      numBits=8;
      numChannels=1;
    }


    public void init(String config,turgen.Logger log,Object config2,Object config3) throws Exception {

        /*Handle mono/stereo etc*/
        setupSampleGetterReader(config,(String)config2,(String)config3);
        
        AudioFormat af = new AudioFormat((float)sampleRate, numBits, numChannels, numBits==16, false);
        DataLine.Info info = new DataLine.Info(TargetDataLine.class,af);
        waveLine = (TargetDataLine) AudioSystem.getLine(info);
        waveLine.open(af, waveLine.getBufferSize());
        waveLine.start();
        
        sample=0L;
        buffer = new byte[waveLine.getBufferSize()];
        bufferPosition=0;
        bufferedCount=0;
        log.addMessage("[AudioPulseDecoder] "+Integer.toString(numBytes)+"/"+Integer.toString(byteIndex)+":"+Integer.toString(sampleRate),false);
        log.addMessage("[AudioPulseDecoder] "+waveLine.getLineInfo().toString(),false);
    }

    public void close(String s) {
        try {
            if (waveLine!=null) waveLine.close();
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }

    private synchronized boolean getStopRequest() {
        return stopRequest;
    }


    public boolean isPositionable() {
        return false;
    }

    public void setMaxSilence(int s) {
        maxSilence=s;
    }

    public synchronized void requestStop( boolean emergency) {
        stopRequest=true;
        if (emergency==true && waveLine!=null) {
            waveLine.stop();
        }
        sample=0L;
    }

    public long getCurrentSample() {
        return sample;
    }

    public int waitForEdge()  {

        int k;

        while (true) {
            k = getNextSample();
            if (k == PD_EOF || k == PD_USER_BREAK || k == PD_ERROR) {
                return k;
            }

            counter++;
            if (counter > maxSilence) {
                return PD_TOO_LONG;
            }

            if (k != lastValue) {
                lastValue = k;
                return PD_OK;
            }

        }

    }


    public int waitForStartingEdge() {
        lastValue=-1;
        int k;

        while (true) {
            k = getNextSample();

            if (k == PD_EOF || k == PD_USER_BREAK || k==PD_ERROR) {
                return k;
            }

            if (lastValue == 0 && k == 1) {
                lastValue = k;
                return PD_OK;
            }

            lastValue = k;

        }

    }

    public int measureLength() {

        int r = waitForEdge();
        if (r==PD_EOF || r==PD_ERROR || r==PD_USER_BREAK) return r;


        while (true) {
            int k = getNextSample();
            if (k == PD_EOF || k == PD_USER_BREAK || k==PD_ERROR) {
                return k;
            }
            counter++;
            if (counter > maxSilence) {
                return PD_TOO_LONG;
            }
            if (k != lastValue) {
                lastValue = k;
                return PD_OK;
            }

        }


    }

    public void finalize() throws Throwable {
        super.finalize();
        if  (waveLine!=null) {
            try {
                waveLine.close();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int c) {
        counter=c;
    }

    public void setCurrentSample(int s) {
        
    }

    public long getTotalSamples() {
        return 0L;
    }


    /** Get next sample (0 or 1)
     *
     * @return 0,1 - valid values or USER_BREAK,ERROR,EOF
     *
     */
    private int getNextSample() {

        if (getStopRequest()==true) {
            stopRequest=false;
            return PD_USER_BREAK;
        }

        int k;

        /*If there is no data in the buffer, read it*/
        if (bufferedCount==0) {
            try {
                bufferedCount=waveLine.read(buffer, 0, buffer.length);
                bufferPosition=0;
            }
            catch (Exception e) {
                e.printStackTrace();
                return PD_ERROR;
            }
        }
        
        if (bufferedCount>0) {
            for(int i=0;i<numBytes;i++) {
                currentByte[i]=buffer[bufferPosition];
                bufferPosition++;
                bufferedCount--;
            }
        }
        
        sample++;
        if (numBits==8) {
            k=(currentByte[byteIndex] < 0 ? currentByte[byteIndex] + 256 : currentByte[byteIndex]);
            if (k>=128) return 1;
            if (k>=0) return 0;
        }
        else {
            int frame = currentByte[byteIndex] + (currentByte[byteIndex+1]<<8);
            return (frame<0)?0:1;
        }
        

        

        return PD_EOF;
    }


    public String getMessage(int result) {
        switch (result) {
            case PD_EOF: return "EOF: End of file reached";
            case PD_TOO_LONG: return "ERROR: No pulse found";
            case PD_USER_BREAK: return "BREAK: User break";
            case PD_ERROR: return "ERROR: I/O Error";
            case PD_OK: return "OK: Success";
            case PD_NOT_ONE_NOT_ZERO: return "ERROR: Not one, not zero: ["+Integer.toString(counter)+"]";
            case PD_FILE_FORMAT_ERROR: return "ERROR: Bad file format";
            default: return "CODE: "+Integer.toString(result);
        }
    }

    private void setupSampleGetterReader(String config, String config2,String config3) {

        /*Mono/Stereo*/
        if (config.equals("Mono")) {
            numBytes=1;
            byteIndex=0;
            numChannels=1;
        }
        /*Left/Right*/
         else {
            numBytes=2;
            numChannels=2;
            if (config.equals("Left")) byteIndex=0; else byteIndex=1;
        }
        
        if (config3.equals("16")) {
            numBytes*=2;
            byteIndex*=2;
            numBits=16;
        }
        else {
            numBits=8;
        }
        
        try {
            sampleRate=Integer.parseInt(config2);
        }
        catch (NumberFormatException e) {
            e.printStackTrace();
            sampleRate=44100;
        }
        

    }

    public int getSampleRate() {
        return sampleRate;
    }

}
