

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;

/**
 * Tape image chunk for Tape Image extractor
 * 
 */
class ExtractorChunk {

    
    private final String chunkName;
    private String irgString;
    private final String lengthString;
    private String headString;
    private String tailString;
    private String hintString;
    private final String idString;
    private final HintsCrate originalHints;
    
    private HintsCrate hints;
    
    private final TapeImageChunk tapeImageChunk;
    
    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;

    private String hexStringRepresentation;

    
    
    static class ExtractionCrate {
        int[] data;
        int completionCode;
    }
    
    ExtractorChunk(TapeImageChunk c,int id) {
        
        chunkName = c.getType();
        lengthString = Integer.toString(c.getLength());
        hints = new HintsCrate();
        createHead(c);
        createTail(c);
        analyzeChunk(c);
        originalHints=hints.getCopy();
        createIRGString(c);
        tapeImageChunk = c;
        idString=Integer.toString(id);
        hexStringRepresentation=null;
        
    }
            
    String getChunkName() {
        return chunkName;
    }

    String getIRGString() {
        return irgString;
    }

    String getLengthString() {
        return lengthString;
    }

    String getHeadString() {
        return headString;
    }

    String getTailString() {
        return tailString;
    }

    String getHintString() {
        if (hints.extractData) {
            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).toUpperCase());
            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]).toUpperCase(),2));
            sb.append(' ');
        }
        tailString = sb.toString();
    }

    private void analyzeChunk(TapeImageChunk c) {
        
        /*Default values for the chunk*/
        hints.extractData = false;
        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")) {
                hints.extractData=true;
            }
            return;
        }
        
        
        /*Now we have some extracteble chunks*/
        hints.extractData=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) {
            hints.extractData=false;
            return;
        }
        
        /*if this is full block, data portion will be extracted*/
        if (data[2]==0xFC) {
           hints.skipHead=3;
           hints.skipTail=1;
           hints.lengthType=HintsCrate.LENGTH_TYPE_NONE;
            updateHintString();
            return;
        }
        /*If partial block*/
        if (data[2]==0xFA) {
            hints.skipHead=3;
            hints.skipTail=1;
            hints.lengthType=HintsCrate.LENGTH_TYPE_8BIT;
            hints.lengthOffsetLow=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(hints.skipHead));
        sb.append(',');
        sb.append(Integer.toString(hints.skipTail));
        sb.append(',');
        
        switch(hints.lengthType) {
            case HintsCrate.LENGTH_TYPE_NONE: {
                sb.append('X');
                break;
            }
            case HintsCrate.LENGTH_TYPE_8BIT: {
                sb.append(Integer.toString(hints.lengthOffsetLow));
                break;
            }
            case HintsCrate.LENGTH_TYPE_16BIT: {
                sb.append('(');
                sb.append(Integer.toString(hints.lengthOffsetLow));
                sb.append(',');
                sb.append(Integer.toString(hints.lengthOffsetHigh));
                sb.append(')');
            }
        }
        
        
        hintString = sb.toString();

    }

    boolean isExtractible() {
        return hints.extractData;
    }
    
    void setHintsCrate(HintsCrate c) {
        hints=c;
        updateHintString();
    }
    
    HintsCrate getHintsCrate() {
        return hints;
    }

    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=0;
        
        /*If hints are totally wrong, we return no data*/
        if (data==null|| data.length==0 || (numBytes = data.length-hints.skipHead-hints.skipTail)<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 && hints.lengthType==HintsCrate.LENGTH_TYPE_NONE) {
            ec.data = new int[0];
            return ec;
        }
        
        /*If there is no length offset, we just return data*/
        if (hints.lengthType==HintsCrate.LENGTH_TYPE_NONE) {
            ec.data = new int[numBytes];
            System.arraycopy(data,hints.skipHead , ec.data, 0, numBytes);
            return ec;
        }
        
        int length;
        
        /*First, we check if the length ofset is valid*/
        if (hints.lengthType==HintsCrate.LENGTH_TYPE_16BIT) {
            if (hints.lengthOffsetLow>data.length-1 || hints.lengthOffsetHigh>data.length) {
                ec.data = new int[0];
                ec.completionCode=CC_BAD_LENGTH;
                return ec;
            }
            length=data[hints.lengthOffsetLow];
            length+=data[hints.lengthOffsetHigh]*256;
        }
        else {
            if (hints.lengthOffsetLow>data.length-1) {
                ec.data=new int[0];
                ec.completionCode=CC_BAD_LENGTH;
                return ec;
            }
            length=data[hints.lengthOffsetLow];
        }
        
        
        if (length>numBytes) {
            length=numBytes;
            ec.completionCode=CC_BAD_LENGTH;
        }
        ec.data = new int[length];
        System.arraycopy(data, hints.skipHead, ec.data, 0, length);
        return ec;
    }
    
    String getId() {
        return idString;
    }
    
    void updateHintsCrate(HintsCrate crate, HintsCrate.Map map) {
        
        /* Data extraction*/
        if (map.extractData==true) {
            hints.extractData=crate.extractData;
        }
        /*Head and tail*/
        if (map.skipHead==true) {
            hints.skipHead=crate.skipHead;
        }
        if (map.skipTail==true) {
            hints.skipTail=crate.skipTail;
        }
        /*Length*/
        if (map.lengthType==true) {
            hints.lengthType=crate.lengthType;
        }
        if (map.lengthOffsetLow==true) {
            hints.lengthOffsetLow=crate.lengthOffsetLow;
        }
        if (map.lengthOffsetHigh==true) {
            hints.lengthOffsetHigh=crate.lengthOffsetHigh;
        }
        
        updateHintString();
    }
    
    void resetHints() {
        setHintsCrate(originalHints.getCopy());
    }
    
    int getRawLength() {
        return tapeImageChunk.getLength();
    }
    
    String getHexStringRepresentation() {
       
        if (hexStringRepresentation!=null) {
            return hexStringRepresentation;
        }
        
        String lineSp = System.getProperty("line.separator");
        
        StringBuilder sb = new StringBuilder();
        final int[] data =tapeImageChunk.getData();
        
        int counter=0;
        
        for(int i=0;i<data.length;i++) {
            sb.append(Utils.padZeros(Integer.toHexString(data[i]),2).toUpperCase());
            sb.append(' ');
            if (counter==15) {
                sb.append(lineSp);
                counter=0;
            }
            else {
                counter++;
            }
            
        }
        
        hexStringRepresentation = sb.toString();
        return sb.toString();
    }

}
