package turgen.decoder;

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

public class KSOTurbo2000FileDecoder implements FileDecoder,BlockDecoderListener{

    private BlockDecoder decoder;
    private static final String MSG_PFX="[KSO T2000] ";
    private long firstFileSample=0L;
    private Logger log;
    private static final int BLOCK_FULL=0;
    private static final int BLOCK_PART=1;
    private static final int BLOCK_EOF=2;
    private static final int BLOCK_BAD=3;

    public boolean decodeFile(String outdir, Logger log, PulseDecoder d,DecoderConfig config) throws Exception {
        
        decoder = new KSOTurbo2000BlockDecoder(d,config);
        
        this.log=log;
        decoder.setBlockDecoderListener(this);

        int[] header;
        int[] data;
        int foundBlockType;

        BlockDecodeResult bdr=null;
        int errCode;

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

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

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

            /*KSO Turbo 2000 header found?*/
            if (BlockDecodeResult.isCodeOK(errCode))  {
                header=bdr.getData();
                if (header[0]==0 && header[1]==255) {
                    break headerDecode;
                }
            }

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

        }
        
        /*Header is decoded ok*/
        /*Write the header to the log*/
        log.addMessage(MSG_PFX+"HEADER: "+headerToString(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 error = false;
            int blockCount=0;

            outerLoop:
            while (quitme == false) {

                /*Read block*/
                bdr=decoder.decodeBlock(3075);
                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;
                }

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

                data=bdr.getData();
                foundBlockType=getBlockType(data);
                

                /*According to the block type, do the resolution*/
                switch (foundBlockType) {
                    /*Full block*/
                    case BLOCK_FULL: {
                        for (int i = 2; i < 3074; i++) {
                            dos.write(data[i]);
                        }
                        break;
                    }
                    /*EOF block*/
                    case BLOCK_EOF: {
                        quitme = true;
                        break;
                    }
                    /*Partial block*/
                    case BLOCK_PART: {
                        int dataLen = data[0]+(data[1]<<8);
                        for(int i=0;i<dataLen;i++) {
                            dos.write(data[2+i]);
                        }
                        quitme = true;
                        break;
                    }
                    
                    /*Bad block*/
                    default: {
                        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 (BlockDecodeResult.isCodeImmediateBreak(errCode)) {
                    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 = ".ksodat";

        /*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*/
        }
        
        if (nameBase.toUpperCase().endsWith(xtension.toUpperCase())) {
            xtension="";
        }
        
        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);
    }
    
    private int getBlockType(int[] data) {
        
        int dataLen=data[0]+(data[1]<<8);
        
        if (dataLen==3072) return BLOCK_FULL;
        if (dataLen==0) return BLOCK_EOF;
        if (dataLen<3072) return BLOCK_PART;
        return BLOCK_BAD;
        
    }
    
    private String headerToString(int[] header) {
        byte nameBytes[] = new byte[10];
        for(int i=2;i<12;i++) {
            nameBytes[i-2]=(byte)header[i];
        }
        return new String(nameBytes);
    }
}
