package turgen.plugin.plturbo;

import turgen.plugin.*;
import turgen.generator.SignalGenerator;
import turgen.config.Configuration;
import turgen.*;
import java.io.*;
import java.util.Iterator;
import javax.swing.*;
import turgen.config.BooleanConfigEntry;
import turgen.config.ConfigClass;
import turgen.config.IntegerConfigEntry;
import turgen.config.PulseCorrectionConfigEntry;
import turgen.config.StringConfigEntry;
import turgen.dtb.DOS2Binary;
import turgen.dtb.DOS2BinaryProcessingException;
import turgen.dtb.Segment;
import turgen.generator.PulseCrate;
import turgen.playlist.AbstractPlayListItem;
import turgen.plugin.standard.StandardPlugin;
import turgen.tapeimage.DataChunk;
import turgen.tapeimage.TapeImageChunk;
import turgen.tapeimage.TurboChunk;

public class PlTurboROMPlugin implements Plugin {

    private String lastError;
    private PlTurboROMPluginPanel panel;
    private final int[] casChunkName = {116, 114, 112, 52};

    private ConfigClass cfcPlTurboROM;
    private BooleanConfigEntry cfgInvertPolarity;
    private IntegerConfigEntry cfgPilotToneHeader;
    private IntegerConfigEntry cfgPilotToneData;
    private IntegerConfigEntry cfgPilotToneBinaryLoad;
    private IntegerConfigEntry cfgSilenceAfterBinaryLoader;
    private IntegerConfigEntry cfgSilenceAfterHeader;
    private BooleanConfigEntry cfgPauseAfterHeader;
    private IntegerConfigEntry cfgBasicRunAddress;
    private IntegerConfigEntry cfgBasicLoadAddress;
    private BooleanConfigEntry cfgBinaryLoaderAsStandardBootFile;
    private StringConfigEntry  cfgPrependTurboROMNaturalLoader;
    private PulseCorrectionConfigEntry cfgPulseCorrections;
    private BooleanConfigEntry cfgGenerateLoaderOnly;

    /**
     * New instance
     */
    public PlTurboROMPlugin() {
    }

    /**
     * Get human readable name
     */
    public String getName() {
        return "Turbo ROM";
    }

    /**
     * Generate instructions for generator
     *
     * @param _pl Playlist item
     * @return Instructions or null
     */
    public int[] convertItem(AbstractPlayListItem _pl) throws Exception {

        PlTurboROMPlaylistItem pl;
        pl = (PlTurboROMPlaylistItem) _pl;

        pl.pulses.applyCorrections(cfgPulseCorrections.getAsPulseCrate(), false);
        
        /*Initialization of streams and initial setup instructions*/
        InstructionStream is = new InstructionStream();


        /*Code dependent on the conversion type*/
        switch (pl.conversionType) {
            case PlTurboROMPlaylistItem.CV_COMPATIBLE_BINARY: {
                converCompatibleBinaryFile(is, pl);
                break;
            }
            case PlTurboROMPlaylistItem.CV_BASIC: {
                convertBASICFile(is, pl);
                break;
            }
            case PlTurboROMPlaylistItem.CV_BINARY: {
                convertSegmentedBinaryFile(is, pl);
                break;
            }
        }

        /*Finalization*/
        is.add(SignalGenerator.INSTR_END);

        return is.getInstructions();

    }

    public String getLastError() {
        return lastError;
    }

    public AbstractPlayListItem getItemFromPanel() {
        PlTurboROMPlaylistItem dpr = panel.getRecord();
        dpr.pluginClass = Turgen.pluginManager.getPluginClassName(this);
        return dpr;
    }

    public void resetPanel() {
        panel.clear();
    }

    public String validateItemOnPanel() {
        return panel.verify();
    }

    public JPanel getItemPanel() {
        return panel;
    }

    public void init() {
        panel = new PlTurboROMPluginPanel();

        /*Initialize configuration class*/
        cfcPlTurboROM = new ConfigClass("PlTurboROM", "Turbo ROM");
        cfgInvertPolarity = new BooleanConfigEntry("invertPolarity", "Invert polarity of pulses", false);
        cfgPilotToneHeader = new IntegerConfigEntry("pilotToneHeader2", "Header pilot tone length [pulse]", 4884, 1024, 8192);
        cfgPilotToneData = new IntegerConfigEntry("pilotToneData2", "Data block pilot tone length [pulse]", 1792, 128, 4096);
        cfgSilenceAfterHeader = new IntegerConfigEntry("silenceAfterHeader", "Silence after header [s]", 2, 0, 30);
        cfgPauseAfterHeader = new BooleanConfigEntry("pauseAfterHeader","Pause after header",false);
        cfgSilenceAfterBinaryLoader = new IntegerConfigEntry("silenceAfterBinaryLoader", "Silence after binary loader [s]", 2, 0, 30);
        cfgBasicLoadAddress = new IntegerConfigEntry("basicLoadAddress", "LOAD address of a BASIC program", 1792, 512, 65535);
        cfgBasicRunAddress = new IntegerConfigEntry("basicRunAddress", "RUN address of a BASIC program", 0xA07E, 0, 65535);
        cfgPilotToneBinaryLoad = new IntegerConfigEntry("pilotToneBinaryLoad2", "Binary load pilot tone length [pulse]", 2048, 512, 4096);
        cfgBinaryLoaderAsStandardBootFile = new BooleanConfigEntry("loaderAsStandardBootFile", "Convert binary loader to a standard cassette boot file", false);
        
        String[] naturalLoaderOptions = {"None","Standard","Diagnostic"};
        cfgPrependTurboROMNaturalLoader = new StringConfigEntry("prependTurboROMNaturalLoader", "Prepend Turbo ROM loader","None",naturalLoaderOptions);
        
        cfgGenerateLoaderOnly = new BooleanConfigEntry("generateLoaderOnly","Generate loader only",false);
        cfgPulseCorrections = new PulseCorrectionConfigEntry("pulseCorrections", "Pulse corrections");

        /*Add entries to the class*/
        cfcPlTurboROM.addEntry(cfgInvertPolarity);
        cfcPlTurboROM.addEntry(cfgPilotToneHeader);
        cfcPlTurboROM.addEntry(cfgPilotToneData);
        cfcPlTurboROM.addEntry(cfgPilotToneBinaryLoad);
        cfcPlTurboROM.addEntry(cfgSilenceAfterHeader);
        cfcPlTurboROM.addEntry(cfgPauseAfterHeader);
        cfcPlTurboROM.addEntry(cfgSilenceAfterBinaryLoader);
        cfcPlTurboROM.addEntry(cfgBinaryLoaderAsStandardBootFile);
        cfcPlTurboROM.addEntry(cfgPrependTurboROMNaturalLoader);
        cfcPlTurboROM.addEntry(cfgBasicLoadAddress);
        cfcPlTurboROM.addEntry(cfgBasicRunAddress);
        cfcPlTurboROM.addEntry(cfgGenerateLoaderOnly);
        cfcPlTurboROM.addEntry(cfgPulseCorrections);

        /*Register configuration class*/
        Configuration.registerClass(cfcPlTurboROM);
    }

    public void fillPanel(AbstractPlayListItem plr) {
        PlTurboROMPlaylistItem dpr = (PlTurboROMPlaylistItem) plr;
        panel.fill(dpr);
    }

    public void setInOutProvider(InOutProvider prov) {
        panel.setIOProvider(prov);
    }

    public String[] getSupportedTapeImageChunks() {
        return new String[]{"trp4"};
    }

    public void addInstructionsForChunk(InstructionStream is, TapeImageChunk chunk) throws Exception {

        /*trXX chunk*/
        if (chunk instanceof TurboChunk) {
            Utils.addSetupInstruction(is, Turgen.pulseCache.getPulsesForPlugin(this)[0], this.casChunkName, (1 - cfgInvertPolarity.getAsInt()), SignalGenerator.FLAG_ORDER_LH);
            return;
        }

        /*Only data chunk accepted*/
        if (!(chunk instanceof DataChunk)) {
            return;
        }
        DataChunk dataChunk = (DataChunk) chunk;

        /*Pilot tone*/
        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(dataChunk.getAux());
        /*Short pulse*/
        is.add(SignalGenerator.INSTR_NARROW);

        /*Data of the block*/
        is.add(SignalGenerator.INSTR_DATA);
        is.add(dataChunk.getLength());
        int[] dta = dataChunk.getData();
        is.add(dta);

        /*Termination*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);

    }

    public BinaryConvertor[] getBinaryConvertors() {

        BinaryConvertor[] convertors = new BinaryConvertor[2];
        convertors[0] = new PlTurboROMBinaryConvertor(this, "Turbo ROM - Compatible binary file");
        convertors[1] = new PlTurboROMSegmentedBinaryConvertor(this, "Turbo ROM - Segmented binary file");

        return convertors;
    }

    public String getCountry() {
        return "PL";
    }

    private void converCompatibleBinaryFile(InstructionStream is, PlTurboROMPlaylistItem pli) throws Exception {
        if (!(cfgPrependTurboROMNaturalLoader.getAsString().equals("None"))) {
            prependTurboROMLoader(is);
        }
        if (cfgGenerateLoaderOnly.getAsBoolean()==true) return;
        convertCompatibleBinaryFileImpl(is, pli.inFile, pli.fname, null, pli.pulses);
    }

    /**
     * Convert binary file to Turbo ROM
     *
     * @param is Stream with instructions
     * @param inputFilespec Input file specifier
     * @param inputFileData Input file data. Can be null
     * @throws Exception
     */
    private void convertCompatibleBinaryFileImpl(InstructionStream is, String inputFilespec, String fname, int[] inputFileData, PulseCrate pulses) throws Exception {

        /*Load and analyze binary file*/
        DOS2Binary dtb;
        if (inputFileData != null) {
            dtb = new DOS2Binary("");
            dtb.analyzeFromData(inputFileData);
        }
        else {
            dtb = new DOS2Binary(inputFilespec);
            dtb.analyzeFromFile();
        }

        /*Reject binary file that is not compatible*/
        if (dtb.isPlTurboROMCompatible() == false) {
            throw new DOS2BinaryProcessingException("Binary file is not Turbo ROM compatible binary file");
        }

        /*Prepare header*/
        int[] dataBlock = dtb.getFirstDataSegment().data;
        int[] headerData = createBinaryHeader(getHeaderRunAddress(dtb), getHeaderInitAddress(dtb), dtb.getFirstDataSegment().start, dataBlock, fname);

        /*Setup*/
        Utils.addSetupInstruction(is, pulses, casChunkName, 1 - cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_LH);

        /*Write header block*/
        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(1);

        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(cfgPilotToneHeader.getAsInt());
        is.add(SignalGenerator.INSTR_NARROW);

        is.add(SignalGenerator.INSTR_DATA);
        is.add(headerData.length);
        is.add(headerData);


        /*Safety pulses and block separation*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);
        
        /*Silence after header*/
        addSilenceAfterHeader(is);

        /*Data block*/
        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(cfgPilotToneData.getAsInt());
        is.add(SignalGenerator.INSTR_NARROW);

        is.add(SignalGenerator.INSTR_DATA);
        is.add(dataBlock.length);

        is.add(dataBlock);

        /*Safety pulses and block separation*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);

    }

    /**
     * Get INIT address that will be used in header
     *
     * @param dtb DOS2 binary file
     * @return Init address or -1 when there is no INIT address
     */
    private int getHeaderInitAddress(DOS2Binary dtb) {

        /*No INIT segment, no INIT address*/
        if (dtb.hasInitSegment() == false) {
            return -1;
        }

        /*Browse throught JUMP segments*/
        Iterator it = dtb.allSegments.iterator();
        Segment s;

        while (it.hasNext()) {
            s = (Segment) it.next();
            if (s.type == Segment.INIT_SEGMENT) {
                return s.jump;
            }
            if (s.type == Segment.RUNINIT_SEGMENT) {
                return s.jump2;
            }
        }

        return -1;
    }

    /**
     * Get RUN address that will be used in header
     */
    private int getHeaderRunAddress(DOS2Binary dtb) {

        /*No RUN or RUNINIT segment*/
        if (dtb.hasRunSegment() == false) {
            return dtb.getFirstDataSegment().start;
        }
        /*RUN or RUNINIT segment*/
        else {
            /*Browse through all jump segments and get the address*/
            Iterator it = dtb.allSegments.iterator();
            Segment s;

            while (it.hasNext()) {
                s = (Segment) it.next();
                if (s.type == Segment.RUN_SEGMENT || s.type == Segment.RUNINIT_SEGMENT) {
                    return s.jump;
                }
            }
        }
        return 0;
    }

    private int[] createBinaryHeader(int headerRunAddress, int headerInitAddress, int loadAddress, int[] data, String fname) {

        int[] header = new int[41];

        /*Header load address*/
        header[1] = 0x01;
        header[2] = 0x06;

        /*Header length*/
        header[3] = 0x28;
        header[4] = 0x00;

        /*Run address*/
        header[6] = headerRunAddress % 256;
        header[7] = headerRunAddress / 256;

        /*Init address*/
        if (headerInitAddress == -1) {
            header[8] = 0;
            header[9] = 1;
        }
        else {
            header[8] = headerInitAddress % 256;
            header[9] = headerInitAddress / 256;
        }

        /*Load address*/
        header[10] = loadAddress % 256;
        header[11] = loadAddress / 256;

        /*Length of the block*/
        header[12] = data.length % 256;
        header[13] = data.length / 256;

        /*Padding*/
        header[14] = 0;

        /*Spaces in the file name*/
        for (int i = 15; i < 35; i++) {
            header[i] = 0;
        }

        /*Internal codes int the file name*/
        int len = Math.min(20, fname.length());
        char[] c = fname.toCharArray();

        for (int i = 0; i < len; i++) {
            header[15 + i] = Utils.ascii2Internal(c[i]);
        }

        /*Program type flag*/
        header[35] = 1;

        /*INIT execution*/
        if (headerInitAddress == -1) {
            header[36] = 1;
        }
        else {
            header[36] = 0;
        }

        /*Padding bytes*/
        header[37] = 0;
        header[38] = 0;
        header[39] = 0;

        /*RTS*/
        header[40] = 0x60;

        /*Data block checksum*/
        int dataBlockChecksum = data[0];
        for (int i = 1; i < data.length; i++) {
            dataBlockChecksum ^= data[i];
        }
        header[5] = dataBlockChecksum;

        /*Header block checksum*/
        int headerBlockChecksum = header[1];
        for (int i = 2; i < header.length; i++) {
            headerBlockChecksum ^= header[i];
        }
        header[0] = headerBlockChecksum;

        return header;
    }

    /**
     * Convert tokenized BASIC file
     */
    private void convertBASICFile(InstructionStream is, PlTurboROMPlaylistItem pl) throws Exception {

        RandomAccessFile raf = new RandomAccessFile(pl.inFile, "r");
        if (raf.length() > 1024 * 1024) {
            throw new FileTooLargeException(pl.inFile);
        }

        byte[] rawBytes = new byte[(int) raf.length()];
        int[] fileData = new int[rawBytes.length];

        raf.readFully(rawBytes);
        raf.close();

        /*Consolidate raw bytes so the can be processed as ints*/
        for (int i = 0; i < rawBytes.length; i++) {
            fileData[i] = rawBytes[i] < 0 ? rawBytes[i] + 256 : rawBytes[i];
        }

        int[] headerData = createBASICHeader(pl.fname, fileData);

        /*Setup*/
        Utils.addSetupInstruction(is, pl.pulses, casChunkName, 1 - cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_LH);

        /*Write header block*/
        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(1);

        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(cfgPilotToneHeader.getAsInt());
        is.add(SignalGenerator.INSTR_NARROW);

        is.add(SignalGenerator.INSTR_DATA);
        is.add(headerData.length);

        is.add(headerData);


        /*Safety pulses and block separation*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);
        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(2);

        /*Data block*/
        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(cfgPilotToneData.getAsInt());
        is.add(SignalGenerator.INSTR_NARROW);

        is.add(SignalGenerator.INSTR_DATA);
        is.add(fileData.length - 14);

        for (int i = 14; i < fileData.length; i++) {
            is.add(fileData[i]);
        }
        /*Safety pulses and block separation*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);

    }

    private int[] createBASICHeader(String fname, int[] fileData) {
        int[] header = new int[78];

        int specialAddr = cfgBasicRunAddress.getAsInt();
        int loadAddr = cfgBasicLoadAddress.getAsInt();

        /*Clear header*/
        for (int i = 0; i < header.length; i++) {
            header[i] = 0;
        }

        /*Header load address*/
        header[1] = 0x0601 % 0x100;
        header[2] = 0x0601 / 0x100;

        /*Header length*/
        header[3] = 0x004D % 0x100;
        header[4] = 0x004D / 0x100;

        /*Data block Check sum*/
        int chsum = fileData[14];
        for (int i = 15; i < fileData.length; i++) {
            chsum ^= fileData[i];
        }
        header[5] = chsum;

        /*Run address*/
        header[6] = specialAddr % 256;
        header[7] = specialAddr / 256;

        /*Load address*/
        int eLoadAdr = loadAddr + fileData[2] + fileData[3] * 256;
        header[10] = eLoadAdr % 256;
        header[11] = eLoadAdr / 256;

        /*Length of the main block. All data minus 14 bytes header*/
        header[12] = (fileData.length - 14) % 256;
        header[13] = (fileData.length - 14) / 256;

        /*File name*/
        int len = Math.min(20, fname.length());
        char[] c = fname.toCharArray();
        for (int i = 0; i < len; i++) {
            header[15 + i] = Utils.ascii2Internal(c[i]);
        }

        /*Program type and no init, basic initialization routine*/
        header[35] = 0;
        header[36] = 1;
        System.arraycopy(basInitRoutine, 0, header, 40, basInitRoutine.length);

        /*7 addresses to change*/
        int[] relo = new int[7];
        for (int i = 0; i < 7; i++) {
            relo[i] = fileData[2 * i] + 256 * fileData[2 * i + 1];
            relo[i] += loadAddr;
            header[60 + 2 * i] = relo[i] % 256;
            header[60 + 2 * i + 1] = relo[i] / 256;
        }

        header[60 + 14] = (fileData.length + loadAddr - 14) % 256;
        header[60 + 15] = (fileData.length + loadAddr - 14) / 256;
        header[60 + 16] = header[60 + 14];
        header[60 + 17] = header[60 + 15];

        /*Header checksum*/
        chsum = header[1];
        for (int i = 2; i < header.length; i++) {
            chsum ^= header[i];
        }
        header[0] = chsum;

        return header;
    }
    private static final int[] basInitRoutine
            = {
                0xA2, 0x11, 0xBD, 0x3C, 0x06, 0x95, 0x80, 0xCA, 0x10, 0xF8, 0x60
            };

    public boolean isUsingAlternateFileChooser() {
        return false;
    }

    private void convertSegmentedBinaryFile(InstructionStream is, PlTurboROMPlaylistItem pl) throws Exception {

        /*Prepend binary loader as a natural Turbo ROM file or as a standard cassette boot file*/
        if (cfgBinaryLoaderAsStandardBootFile.getAsBoolean() == false) {
            if (!(cfgPrependTurboROMNaturalLoader.getAsString().equals("None"))) {
                prependTurboROMLoader(is);
            }
            if (binaryLoaderData==null) {
                binaryLoaderData=Utils.getLoaderFromResource("/turgen/plugin/plturbo/loaders/turbo_rom_binload.xex");
            }
            
            convertCompatibleBinaryFileImpl(is, null, pl.fname, binaryLoaderData, pl.pulses);
            if (cfgGenerateLoaderOnly.getAsBoolean()==true) {
                return;
            }
        }
        else {
            /*Find standard plugin*/
            Plugin stdPlugin = Turgen.pluginManager.getPlugin("turgen.plugin.standard.StandardPlugin");
            if (stdPlugin == null) {
                throw new PluginException("The Standard plugin required to convert the binary loader is not present");
            }

            /*Prepare copy of the loader data*/
            if (simpleBinaryLoaderData==null) {
                simpleBinaryLoaderData=Utils.getLoaderFromResource("/turgen/plugin/plturbo/loaders/turbo_rom_binload_simple.xex");
            }
            
            int[] loaderData = new int[simpleBinaryLoaderData.length];
            System.arraycopy(simpleBinaryLoaderData, 0, loaderData, 0, simpleBinaryLoaderData.length);

            String fName = pl.fname;
            if (fName.length() > 20) {
                fName = fName.substring(0, 20);
            }
            char[] chars = fName.toCharArray();

            /*Place name*/
            for (int i = 0; i < chars.length; i++) {
                loaderData[0x09E9 - 0x0800 + 6 + 6 + i] = chars[i];
            }
            /*Allow loader to display the name - place NOPs*/
            for (int i = 0; i < 3; i++) {
                loaderData[0x099D - 0x0800 + 6 + 6 + i] = 0xEA;
            }

            ((StandardPlugin) stdPlugin).convertDirtyMonolithicBinaryUsingData(loaderData, is);
            
            if (cfgGenerateLoaderOnly.getAsBoolean()==true) {
                return;
            }

            /*Setup*/
            Utils.addSetupInstruction(is, pl.pulses, casChunkName, 1 - cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_LH);
        }
        
        

        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(cfgSilenceAfterBinaryLoader.getAsInt());

        /*Load and analyze binary file*/
        DOS2Binary dtb = new DOS2Binary((pl.inFile));
        dtb.analyzeFromFile();
        dtb.purify();

        int[] silenceList = pl.silenceList;
        if (silenceList == null) {
            silenceList = new int[0];
        }
        int silenceIndex = 0;

        int blockPilotPulses = cfgPilotToneBinaryLoad.getAsInt();

        /*Prepare array of segments*/
        Segment[] segments = new Segment[dtb.getTotalSegmentCount()];
        for (int i = 0; i < segments.length; i++) {
            segments[i] = (Segment) dtb.allSegments.get(i);
        }

        /*Generate segment header + segment data blocks*/
        int segCount = segments.length;
        for (int i = 0; i < segCount; i++) {

            Segment seg = segments[i];
            generateSegment(seg, blockPilotPulses, is);

            /*Silence after INIT segments*/
            if (seg.type == Segment.INIT_SEGMENT || seg.type == Segment.RUNINIT_SEGMENT) {
                if (silenceIndex < silenceList.length) {
                    is.add(SignalGenerator.INSTR_LONGSILENCE);
                    is.add(Math.min(silenceList[silenceIndex], 240));
                    silenceIndex++;
                }
            }
        }

        /*Generate termination header*/
        generateTermHeader(blockPilotPulses, is);
    }

    private void generateSegment(Segment s, int pilotTonePulses, InstructionStream is) {

        /*Generate header*/
        /* 00    - Header check sum
         01    - Block check sum
         02,03 - Block start address
         04,05 - Block length
         */
        int[] header = new int[6];
        int[] data = s.data;

        /*Data block check sum*/
        int dataChsum = data[0];
        for (int i = 1; i < data.length; i++) {
            dataChsum = dataChsum ^ data[i];
        }

        /*Create header*/
        header[1] = dataChsum;
        header[2] = s.start % 256;
        header[3] = s.start / 256;
        header[4] = s.data.length % 256;
        header[5] = s.data.length / 256;

        int headerChsum = header[1];
        for (int i = 2; i < header.length; i++) {
            headerChsum = headerChsum ^ header[i];
        }
        header[0] = headerChsum;

        addBlock(is, header, pilotTonePulses);
        addBlock(is, data, pilotTonePulses);
    }

    private void generateTermHeader(int pilotTonePulses, InstructionStream is) {
        int[] header = new int[6];
        for (int i = 0; i < header.length; i++) {
            header[i] = 0;
        }
        addBlock(is, header, pilotTonePulses);
    }

    private void addBlock(InstructionStream is, int[] data, int pilotTonePulses) {

        /*Pilot tone*/
        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(pilotTonePulses);

        /*Sync*/
        is.add(SignalGenerator.INSTR_NARROW);

        /*Data part*/
        is.add(SignalGenerator.INSTR_DATA);
        is.add(data.length);
        is.add(data);

        /*Tail*/
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_WIDE);
        is.add(SignalGenerator.INSTR_STOP);

        /*Block separator*/
        is.add(SignalGenerator.INSTR_BLOCKSEP);
    }
    
    private void addSilenceAfterHeader(InstructionStream is) {
        int silenceAfterHeader = cfgSilenceAfterHeader.getAsInt();
        if (silenceAfterHeader!=0) {
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            if (cfgPauseAfterHeader.getAsBoolean()==true) silenceAfterHeader=-silenceAfterHeader;
            is.add(silenceAfterHeader);
        }
        else {
            if (cfgPauseAfterHeader.getAsBoolean()==true) {
                is.add(SignalGenerator.INSTR_PAUSE);
            }
        }
    }

    private void prependTurboROMLoader(InstructionStream is) throws Exception {
        /*Find standard plugin*/
        Plugin stdPlugin = Turgen.pluginManager.getPlugin("turgen.plugin.standard.StandardPlugin");
        if (stdPlugin == null) {
            throw new PluginException("The Standard plugin required to convert the Turbo ROM loader is not present");
        }
        
        int[] loaderData;
        /*Diagnostic loader or not?*/
        if (cfgPrependTurboROMNaturalLoader.getAsString().equals("Diagnostic")==true) {
            if (turboROMDiagnosticLoaderData==null) {
                turboROMDiagnosticLoaderData=Utils.getLoaderFromResource("/turgen/plugin/plturbo/loaders/turbo_rom_diagnostic_loader.xex");
            }
            loaderData = turboROMDiagnosticLoaderData;
        }
        else {
            if (turboROMNaturalLoaderData==null) {
                turboROMNaturalLoaderData=Utils.getLoaderFromResource("/turgen/plugin/plturbo/loaders/turbo_rom_loader.xex");
            }
            loaderData = turboROMNaturalLoaderData;
        }
        
        ((StandardPlugin) stdPlugin).convertDirtyMonolithicBinaryUsingData(
                loaderData,
                is);
    }

    /*Turbo ROM binary loader*/
    private int[] binaryLoaderData = null;

    /*Simpler binary loader data*/
    private int[] simpleBinaryLoaderData = null;

    /*Turbo ROM natural format loader data*/
    private int[] turboROMNaturalLoaderData = null;

    /*Turbo ROM natural format diagnostic loader data*/
    private int[] turboROMDiagnosticLoaderData = null;

}
