package turgen.decoder;
import turgen.Utils;

public class KSOTurbo2000BlockDecoder implements BlockDecoder{

   

    int PILOT_HI = 54;
    int PILOT_LO = 34;

    int WIDE_HI = 28;
    int WIDE_LO = 17;

    int NARROW_HI = 16;
    int NARROW_LO = 5;

    int MAX_PULSE= 56;


    private PulseDecoder decoder;
    private DecoderConfig config;
    private int validBytes;
    private BlockDecoderListener blockDecoderListener=null;
    private int firstBlockBit;
    

    KSOTurbo2000BlockDecoder(PulseDecoder decoder,DecoderConfig config) {

        this.decoder=decoder;
        this.config=config;
        recalculatePulses(decoder.getSampleRate());
        
    }
    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*/
            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 first bit*/
            r=waitForFirstBit();
            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);
            /*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.checkKSOBlock(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 256 pilot tone pulses found, pilot tone
             *is considered valid*/
            h++;

            if (h<256) continue; else break;

        }

        return PulseDecoder.PD_OK;


    }
    private int waitForFirstBit() {
        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) ) {
                firstBlockBit=0;
                return decoder.waitForEdge();
                
            }
            /*Is it wide pulse?*/
            if (c<=(WIDE_HI/2)) {
                firstBlockBit=128;
                return decoder.waitForEdge();
            }
            
            /*Not a valid pulse*/
            return PulseDecoder.PD_UNEXPECTED_PULSE_WIDTH;
            
            
            

        }

    }
    private int decodeData(int[] data) {

        /*Data*/
        int d = 0;
        int mask = 64;
        int cur = firstBlockBit;
        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));
    }

}
