package turgen.decoder;

import java.io.*;
import turgen.Logger;
import turgen.Turgen;
import turgen.Utils;
import turgen.dtb.DOS2Binary;

public class KBlockDecoder implements FileDecoder,BlockDecoderListener{

    private BlockDecoder decoder;
    private static final String MSG_PFX="[T2K KB] ";
    private long firstFileSample=0L;
    private Logger log;

    public boolean decodeFile(String outdir, Logger log, PulseDecoder d,DecoderConfig config) throws Exception {
        
        if (config!=null && config.genPreferAdaptiveSpeedDetection==true) {
            decoder = new SuperTurboBlockDecoder(d,config);
        }
        else {
            decoder = new Turbo2000BlockDecoder(d,config);
        }
        
        this.log=log;
        decoder.setBlockDecoderListener(this);

        int[] header;
        int[] data;

        BlockDecodeResult bdr=null;
        int errCode;

        /*Try to decode header*/
        headerDecode:
        while (true) {

            /*Decode 19 bytes block*/
            firstFileSample=d.getCurrentSample();
            bdr = decoder.decodeBlock(19);
            errCode=bdr.getErrorCode();

             /*Immediate break ?*/
            if (BlockDecodeResult.isCodeImmediateBreak(errCode)) {
                log.addMessage(MSG_PFX+bdr.getFullErrorMessage(),true);
                return false;
            }

            /*Kilobyte blocks header found ?*/
            if (BlockDecodeResult.isCodeOK(errCode))  {
                if (bdr.getData()[0]==0) break headerDecode;
            }

            /*Header not decoded for any other reasons, retry*/
            continue headerDecode;

        }

        /*Header decoded*/
        header=bdr.getData();
        
        /*Write the header to the log*/
        log.addMessage(MSG_PFX+"HEADER: "+Utils.kblockHeaderToString(header)+"<"+bdr.getFullErrorMessage()+">",true);
        
        byte[] ba=null;
        ByteArrayOutputStream baos =null;
        DataOutputStream dos = null;
        
        try {
            
            baos = new ByteArrayOutputStream();
            dos = new DataOutputStream(baos);
            
            boolean quitme = false;
            boolean expectEOF = false;
            boolean error = false;

            int blockCount=0;

            outerLoop:
            while (quitme == false) {

                /*Read block*/
                bdr=decoder.decodeBlock(1026);
                errCode=bdr.getErrorCode();
                
                blockCount++;

                /*Block not obtained for any reason*/
                if (!BlockDecodeResult.isCodeOK(errCode)) {
                    log.addMessage(MSG_PFX+bdr.getFullErrorMessage(),true);
                    error = true;
                    break outerLoop;
                }

                /*If not a perfect block, scream*/
                if (!BlockDecodeResult.isCodePerfect(errCode)) {
                    log.addMessage(MSG_PFX+"BLOCK: "+Integer.toString(blockCount)+": <"+bdr.getFullErrorMessage()+">",true);
                }

                data=bdr.getData();

                

                /*According to the block type, do the resolution*/
                switch (data[0]) {
                    /*Full block*/
                    case 255: {
                        if (expectEOF == true) {
                            log.addMessage(MSG_PFX+"ERROR: Expected EOF block, but found FULL block {"+Long.toString(bdr.getSample())+"}",true);
                            error = true;
                            break outerLoop;
                        }
                        for (int i = 1; i < 1025; i++) {
                            dos.write(data[i]);
                        }
                        break;
                    }
                    /*EOF block*/
                    case 250: {
                        quitme = true;
                        break;
                    }
                    /*Others*/
                    default: {

                        if (expectEOF == true) {
                            log.addMessage(MSG_PFX+"ERROR: Expected EOF block, but found PARTIAL block {"+Long.toString(bdr.getSample())+"}",true);
                            error = true;
                            break outerLoop;
                        }
                        
                        
                        if (data[0] >= 251) {
                            int hi = data[0]-251;
                            int lo = data[1024];
                            int ln = hi * 256 + lo;
                            for (int i = 0; i < ln; i++) {
                                dos.write(data[1 + i]);
                            }
                            expectEOF = true;
                        } else {
                            log.addMessage(MSG_PFX+"ERROR: Unknown block type {"+Long.toString(bdr.getSample())+"}",true);
                            error = true;
                            break outerLoop;
                        }
                    }
                }


            }
            

            /*We are out of the outerLoop*/
            ba = baos.toByteArray();
            dos.close();
            
            if (error==true) {
                if (errCode==PulseDecoder.PD_EOF || errCode==PulseDecoder.PD_ERROR || errCode==WavePulseDecoder.PD_USER_BREAK) {
                    return false;
                }
                else {
                    return true;
                }
            }

        } catch (Exception e) {
            log.addMessage(MSG_PFX+Utils.getExceptionMessage(e),true);
            try {
                dos.close();
            }
            catch (Exception xe) {
                /*Intentionally blank*/
            }
            return true;
        }
        
        /*Construct output filespec*/
        String fspec = constructFilespec(outdir, header,ba,firstFileSample,config.genPrependSampleNumber);
        
        
        
        /*Flush data to disk*/
        RandomAccessFile raf = null;
        
        try {
            raf = new RandomAccessFile(fspec, "rw");
            raf.setLength(0L);
            raf.write(ba);
            raf.close();
        }
        catch (Exception e) {
            log.addMessage(MSG_PFX+Utils.getExceptionMessage(e),true);
            try {
                raf.close();
            }
            catch(Exception xe) {
                /*Intentionally blank*/
            }
            return true;
        }

        log.addMessage(MSG_PFX+"SAVE: " + fspec,true);
        return true;

    }

    

    String constructFilespec(String outdir, int[] headerData,byte[] fileData,long sample,boolean prependSample) throws Exception {

        /*First, we construct namebase, removing all
         *dangerous characters*/
        String nameBase="";
        if (prependSample==true) {
            nameBase+=Utils.padZeros(Long.toString(sample),10);
            nameBase+='_';
        }
        nameBase+=Utils.getPolishedFilespec(headerData, 2, 10);

        String xtension = ".kbdat";

        /*Check whether file happens to be binary file*/
        DOS2Binary dtb = new DOS2Binary("");
        try {
            dtb.analyzeFromData(Utils.getAsIntArray(fileData));
            xtension=".xex";
        }
        catch (Exception e) {
            /*Intentionally blank*/
        }
        
        File odf = new File(outdir);
        outdir = odf.getCanonicalPath();

        String retVal="";
        
        /*Filespec is constructed*/
        if (outdir.endsWith(Turgen.sp)) {
            retVal = outdir + nameBase + xtension;
        }
        else {
            retVal = outdir + Turgen.sp + nameBase + xtension;
        }

        return retVal;
    }
    
    public void blockDecodeEvent(Object eventInfo) {
       //log.addMessage(MSG_PFX+(String)eventInfo,true);
        log.impulse(true);
    }
}
