/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package turgen.tapeimageextractor;

import turgen.Utils;
import turgen.tapeimage.BaudChunk;
import turgen.tapeimage.DataChunk;
import turgen.tapeimage.FSKChunk;
import turgen.tapeimage.FujiChunk;
import turgen.tapeimage.PWMChunk;
import turgen.tapeimage.TapeImageChunk;
import turgen.tapeimage.TurboChunk;

/**
 *
 * 
 */
class ExtractorChunk {

    
    private final String chunkName;
    private String irgString;
    private final String lengthString;
    private String headString;
    private String tailString;
    private String hintString;
    private String idString;
    
    private int hintSkipFirst;
    private int hintSkipLast;
    private int hintLengthOffset;
    private boolean isExtractible;
    private final TapeImageChunk tapeImageChunk;
    
    
    private boolean hintIsLength16Bit;
    
    static final int CC_OK=0;
    static final int CC_BAD_HINT=1;
    static final int CC_BAD_LENGTH=2;
    static final int NO_LENGTH_OFFSET=-1;

    
    
    static class ExtractionCrate {
        int[] data;
        int completionCode;
    }
    
    ExtractorChunk(TapeImageChunk c,int id) {
        
        chunkName = c.getType();
        lengthString = Integer.toString(c.getLength());
        createHead(c);
        createTail(c);
        analyzeChunk(c);
        createIRGString(c);
        hintIsLength16Bit=false;
        tapeImageChunk = c;
        idString=Integer.toString(id);
        
    }
            
    String getChunkName() {
        return chunkName;
    }

    String getIRGString() {
        return irgString;
    }

    String getLengthString() {
        return lengthString;
    }

    String getHeadString() {
        return headString;
    }

    String getTailString() {
        return tailString;
    }

    String getHintString() {
        if (isExtractible) {
            return hintString;
        }
        else {
            return "";
        }
    }

    private void createHead(TapeImageChunk c) {
        int maxHeadLen = Math.min(5,c.getLength());
        StringBuilder sb = new StringBuilder();
        int[] data = c.getData();
        
        for(int i=0;i<maxHeadLen;i++) {
            sb.append(Utils.padZeros(Integer.toHexString(data[i]),2));
            sb.append(' ');
        }
        headString = sb.toString();
        
    }

    private void createTail(TapeImageChunk c) {
        int maxTailLen = Math.min(c.getLength(),2 );
        StringBuilder sb = new StringBuilder();
        int[] data = c.getData();
        
        for(int i=maxTailLen-1;i>=0;i--) {
            sb.append(Utils.padZeros(Integer.toHexString(data[data.length-1-i]),2));
            sb.append(' ');
        }
        tailString = sb.toString();
    }

    private void analyzeChunk(TapeImageChunk c) {
        
        /*Default values for the chunk*/
        isExtractible = false;
        hintSkipFirst=0;
        hintSkipLast=0;
        hintLengthOffset=NO_LENGTH_OFFSET;
        updateHintString();
        
        /*baud, fuji,fsk, and trXX chunks are not extractible*/
        if (c instanceof FujiChunk || c instanceof BaudChunk || c instanceof FSKChunk ||
            c instanceof TurboChunk) {
            return;
        }
        
        /*For pwmX chunks, only pwmd chunk is extractible*/
        if (c instanceof PWMChunk) {
            if (c.getType().equals("pwmd")) {
                isExtractible=true;
            }
            return;
        }
        
        
        /*Now we have some extracteble chunks*/
        isExtractible=true;
        
        /*Check if we are data chunk of a baud parent. If not,
          we just hint to extract all data
        */
        
        if (c instanceof DataChunk) {
            TapeImageChunk parent = c.getParent();
            if (parent==null|| !(parent instanceof BaudChunk)) {
                return;
            }
        }
        
        /*Check if there are proper leader and trailer bytes*/
        int[] data = c.getData();
        if (data.length>256 || data.length<4) return;
        
        /*Check leader bytes*/
        if (data[0]!=0x55 || data[1]!=0x55) return;
        
        /*Check if the block type is ok*/
        if (data[2]!=0xFA && data[2]!=0xFC && data[2]!=0xFE) return;
        
        /*If this is EOF block, we are done*/
        if (data[2]==0xFE) {
            isExtractible=false;
            return;
        }
        
        /*if this is full block, data portion will be extracted*/
        if (data[2]==0xFC) {
            hintSkipFirst=3;
            hintSkipLast=1;
            hintLengthOffset=NO_LENGTH_OFFSET;
            updateHintString();
            return;
        }
        /*If partial block*/
        if (data[2]==0xFA) {
            hintSkipFirst=3;
            hintSkipLast=1;
            hintLengthOffset=data.length-2;
            updateHintString();
        }
        
        
    }

    private void createIRGString(TapeImageChunk c) {
        
        /* If data chunk - we have it in auxiliary byte*/
        if (c instanceof DataChunk) {
          irgString = Integer.toString(((DataChunk)c).getAux());
          return;
        }
        /* Other chunks do not have IRGs*/
        irgString="";
        
    }
    
    /*Update hint string*/
    private void updateHintString() {
        StringBuilder sb = new StringBuilder();
        sb.append(Integer.toString(hintSkipFirst));
        sb.append(',');
        sb.append(Integer.toString(hintSkipLast));
        sb.append(',');
        if (hintLengthOffset == NO_LENGTH_OFFSET) {
            sb.append('X');
        }
        else {
            sb.append(Integer.toString(hintLengthOffset));
            if (hintIsLength16Bit) sb.append('D');
        }
        hintString = sb.toString();

    }

    int getLengthOffset() {
        return hintLengthOffset;
    }

    boolean isLength16Bit() {
        return hintIsLength16Bit;
    }

   int getSkipFirst() {
        return hintSkipFirst;
    }

    int getSkipLast() {
        return hintSkipLast;
    }

    boolean isExtractible() {
        return isExtractible;
    }
    
    void setHintsCrate(HintsCrate c) {
        hintSkipFirst = c.skipFirst;
        hintSkipLast = c.skipLast;
        hintLengthOffset = c.lengthOffset;
        hintIsLength16Bit = c.isLength16Bit;
        updateHintString();
        
    }

    ExtractionCrate extractData() {
        ExtractionCrate ec = new ExtractionCrate();
        ec.completionCode=CC_OK;
        
        int[] data = tapeImageChunk.getData();
        
        /*What remains left if we skip head and tail*/
        int numBytes = data.length-hintSkipFirst-hintSkipLast;
        
        /*If hints are totally wrong, we return no data*/
        if (numBytes<0) {
            ec.completionCode=CC_BAD_HINT;
            ec.data = new int[0];
            return ec;
        }
        /*If hints set to extract no data, and there is no length offset, ok*/
        if (numBytes==0 && hintLengthOffset==NO_LENGTH_OFFSET) {
            ec.data = new int[0];
            return ec;
        }
        
        /*If there is no length offset, we just return data*/
        if (hintLengthOffset==NO_LENGTH_OFFSET) {
            ec.data = new int[numBytes];
            System.arraycopy(data,hintSkipFirst , ec.data, 0, numBytes);
            return ec;
        }
        
        int length;
        
        /*First, we check if the length ofset is valid*/
        if (hintIsLength16Bit==true) {
            if (hintLengthOffset>=data.length-1) {
                ec.data = new int[0];
                ec.completionCode=CC_BAD_LENGTH;
                return ec;
            }
            length=data[hintLengthOffset];
            length+=data[hintLengthOffset+1]*256;
        }
        else {
            if (hintLengthOffset>data.length-1) {
                ec.data=new int[0];
                ec.completionCode=CC_BAD_LENGTH;
                return ec;
            }
            length=data[hintLengthOffset];
        }
        
        
        if (length>numBytes) {
            length=numBytes;
            ec.completionCode=CC_BAD_LENGTH;
        }
        
        ec.data = new int[length];
        System.arraycopy(data, hintSkipFirst, ec.data, 0, length);
        return ec;
    }
    
    String getId() {
        return idString;
    }

}
