package turgen.decoder;
import turgen.Utils;

public class TurboBlizzardBlockDecoder implements BlockDecoder{

    
    private int PILOT_HI = 29;
    private int PILOT_LO = 19;
    private int MAX_3PILOT = 3*35;

    private int WIDE_HI = 18;
    private int WIDE_LO = 11;

    private int NARROW_HI = 10;
    private int NARROW_LO = 6;

    private int MAX_PULSE= 40;
    

    private PulseDecoder decoder;
    private DecoderConfig config;
    private int validBytes;
    private BlockDecoderListener blockDecoderListener=null;
    private boolean sync;
    private int pilotTonePulses;
    

    TurboBlizzardBlockDecoder(PulseDecoder decoder,DecoderConfig config,boolean sync, int pilotTonePulses) {

        this.decoder=decoder;
        this.config=config;
        recalculatePulses(decoder.getSampleRate());
        this.sync=sync;
        this.pilotTonePulses=pilotTonePulses;
        
        
    }
    public BlockDecodeResult decodeBlock(int dataLength) {
        return decodeBlock(dataLength,null);
    }

    public BlockDecodeResult decodeBlock(int dataLength,Object constraint) {

        int r;
        int data[] = new int[dataLength];
        validBytes=0;
        
        while(true) {

            /*Wait for pilot tone*/
            if (sync==true && config.genPreferAdaptiveSpeedDetection==true) {
                r=waitForPilotAdaptive();
            }
            else {
                r=waitForPilot();
            }
        
            if (r==PulseDecoder.PD_ERROR || r==PulseDecoder.PD_EOF || r==PulseDecoder.PD_USER_BREAK) {
                return new BlockDecodeResult(null,validBytes,r,decoder.getCurrentSample(),decoder);
            }
            if (r!=PulseDecoder.PD_OK) continue;

            /*Wait for sync, but for zero length block, don't check for the last edge*/
            r=waitForSync(dataLength==0?false:true);
            if (r==PulseDecoder.PD_ERROR || r==PulseDecoder.PD_EOF || r==PulseDecoder.PD_USER_BREAK) {
                return new BlockDecodeResult(null,validBytes,r,decoder.getCurrentSample(),decoder);
            }
            if (r!=PulseDecoder.PD_OK) continue;
            
            if (blockDecoderListener!=null) blockDecoderListener.blockDecodeEvent(null);
            
            
            /*If there is no data requested, we are finished*/
            if (data.length==0) {
                return new BlockDecodeResult(data,0,r,decoder.getCurrentSample(),decoder);
            }
            
            /*Obtain data*/
            r=decodeData(data);

            /*Data not obtained*/
            if (r!=PulseDecoder.PD_OK) {
                return new BlockDecodeResult(data,validBytes,r,decoder.getCurrentSample(),decoder);
            }
            /*Data obtained*/
            else {

                /*Not acceptable - too short*/
                if (data.length<3) {
                    return new BlockDecodeResult(data,validBytes,BlockDecodeResult.BLOCK_TOO_SHORT,decoder.getCurrentSample(),decoder);
                }
                /*Checksum verify*/
                boolean b = Utils.checkBlizzardBlock(data);

                if (b==false) {
                    if (config.genIgnoreBadSum==false) return new BlockDecodeResult(data,validBytes,BlockDecodeResult.BAD_CHSUM,decoder.getCurrentSample(),decoder);
                    else return new BlockDecodeResult(data,validBytes,BlockDecodeResult.OK_CHSUM_WARNING,decoder.getCurrentSample(),decoder);
                }

                return new BlockDecodeResult(data,validBytes,r,decoder.getCurrentSample(),decoder);
            }

        }

    }

    /**Wait for pilot tone*/
    private int waitForPilot() {

        int r;
        int h=0;

        /*Wait for starting edge*/
        decoder.setMaxSilence(MAX_PULSE);
        decoder.setCounter(0);
        
        r=decoder.waitForStartingEdge();
        if (r!=PulseDecoder.PD_OK) return r;

        while(true) {

            decoder.setCounter(0);
            r=decoder.measureLength();
            if (r!=PulseDecoder.PD_OK) return r;

            /*If pulse is shorter than lower bound of pilot tone*/
            if (decoder.getCounter() < PILOT_LO) {
                return PulseDecoder.PD_UNEXPECTED_PULSE_WIDTH;
            }

            /*Pilot tone pulse. If given number of  pilot tone pulses found, pilot tone
             *is considered valid*/
            h++;

            if (h<pilotTonePulses) continue; else break;

        }

        return PulseDecoder.PD_OK;


    }
    
    
    private int waitForPilotAdaptive() {
        
        int r=0;
        int h=0;
        int c=0;

        /*Detection of speed*/
        decoder.setMaxSilence(MAX_PULSE);
        decoder.setCounter(0);
        r=decoder.waitForStartingEdge();
        if (r!=PulseDecoder.PD_OK) return r;

        for(int q=0;q<4;q++) {

            /*Read three pulses to measure speed*/
            decoder.setMaxSilence(MAX_3PILOT);
            decoder.setCounter(0);

            for(int i=0;i<3;i++) {
                r=decoder.measureLength();
                if (r!=PulseDecoder.PD_OK) {
                    return r;
                }
            }

            /*Three pulses read, speed is measured*/
            c=decoder.getCounter();
            
            
           PILOT_LO=(c/4+1);
           PILOT_HI=(c/2);
           
           WIDE_HI=PILOT_LO-1;
           WIDE_LO=(c/8+1);
           
           NARROW_HI=WIDE_LO-1;
           NARROW_LO=c/16;
            
            /*Wait for edge*/
            decoder.setCounter(0);
            decoder.setMaxSilence(MAX_PULSE);
            r=decoder.waitForEdge();
            if (r!=PulseDecoder.PD_OK) return r;

            /*At least 384 pulses should be at the same speed*/
            h=0;
            decoder.setMaxSilence(PILOT_HI);

            while(h<pilotTonePulses/4) {
                decoder.setCounter(0);
                r=decoder.measureLength();
                if (r!=PulseDecoder.PD_OK) return r;
                c=decoder.getCounter();
                if (c<PILOT_LO) return PulseDecoder.PD_UNEXPECTED_PULSE_WIDTH;
                h++;
            }

            /*1/4 of required pulses found*/
        }
        
        //printDetectedSpeed();
        return PulseDecoder.PD_OK;
    }
    
    private int waitForSync(boolean checkForLastEdge) {
        int r;
        int c;

        while(true) {

            /*Wait for the first edge*/
            decoder.setCounter(0);
            r=decoder.waitForEdge();

            /*Edge not found - bailing out*/
            if (r!=PulseDecoder.PD_OK) return r;

            /*Pick half pulse width*/
            c=decoder.getCounter();
            
            /*Is it pilot tone pulse?*/
            if (c>(WIDE_HI/2)) continue;
            
            /*Is it narrow pulse?*/
            if (c<=(NARROW_HI/2)) {
                r=decoder.waitForEdge();
                if (r!=PulseDecoder.PD_OK || checkForLastEdge==false) return r;
                
                /*Another narrow pulse?*/
                decoder.setCounter(0);
                r=decoder.waitForEdge();
                if (r!=PulseDecoder.PD_OK) return r;
                c=decoder.getCounter();
                if (c<=(NARROW_HI/2)) {
                    return decoder.waitForEdge();
                }
            }
            
            return PulseDecoder.PD_UNEXPECTED_PULSE_WIDTH;
        }

    }
    private int decodeData(int[] data) {

        /*Data*/
        int d = 0;
        int mask = 128;
        int cur = 0;
        int dataLength=data.length;

        /*Result*/
        int r;

        while (true) {

            /*Measure length of pulse*/
            decoder.setCounter(0);
            r = decoder.measureLength();

            /*Any failure results into corrupt data block*/
            if (r!=PulseDecoder.PD_OK) return r;
            int c =decoder.getCounter();

               /*Determine 0 or 1*/
            if (c >= WIDE_LO && c <= WIDE_HI) {
                cur += mask;
            } else if (c >= NARROW_LO && c <= NARROW_HI) {
                cur += 0;
            } else {
                return PulseDecoder.PD_NOT_ONE_NOT_ZERO;
            }

            if (mask == 1) {
                data[d] = cur;
                validBytes++;
                mask = 128;
                cur = 0;
                d++;
            } else {
                mask >>= 1;
            }

            if (d < dataLength) {
                continue;
            } else {
                break;
            }
        }

        return PulseDecoder.PD_OK;

    }


    public long getCurrentSample() {
        return decoder.getCurrentSample();
    }
    
    public void setBlockDecoderListener(BlockDecoderListener listener) {
        blockDecoderListener = listener;
    }
    
    private void recalculatePulses(int sampleRate) {
        
        double rate = (double) sampleRate;
        PILOT_HI = (int) Math.round(((double) PILOT_HI) * (rate / 44100.0));
        PILOT_LO = (int) Math.round(((double) PILOT_LO) * (rate / 44100.0));
        WIDE_HI = (int) Math.round(((double) WIDE_HI) * (rate / 44100.0));
        WIDE_LO = (int) Math.round(((double) WIDE_LO) * (rate / 44100.0));
        NARROW_HI = (int) Math.round(((double) NARROW_HI) * (rate / 44100.0));
        NARROW_LO = (int) Math.round(((double) NARROW_LO) * (rate / 44100.0));
        MAX_PULSE = (int) Math.round(((double) MAX_PULSE) * (rate / 44100.0));
        MAX_3PILOT = (int) Math.round(((double) MAX_PULSE) * (rate / 44100.0));
    }

    private void printDetectedSpeed() {
        System.out.println(PILOT_HI+" "+PILOT_LO+" "+WIDE_HI+" "+WIDE_LO+" "+NARROW_HI+" "+NARROW_LO);
    }

    void pickTransferSpeed(TurboBlizzardBlockDecoder sd) {
        this.PILOT_HI=sd.PILOT_HI;
        this.PILOT_LO=sd.PILOT_LO;
        this.WIDE_HI=sd.WIDE_HI;
        this.WIDE_LO=sd.WIDE_LO;
        this.NARROW_HI=sd.NARROW_HI;
        this.NARROW_LO=sd.NARROW_LO;
        this.MAX_3PILOT=sd.MAX_3PILOT;
        this.MAX_PULSE=sd.MAX_PULSE;
                
    }

}
