package turgen.plugin.plturbo;

import turgen.plugin.*;
import turgen.generator.SignalGenerator;
import turgen.config.Configuration;
import turgen.*;
import javax.swing.*;
import turgen.config.BooleanConfigEntry;
import turgen.config.ConfigClass;
import turgen.config.IntegerConfigEntry;
import turgen.dtb.DOS2Binary;
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 PlHardTurboPlugin implements Plugin {

    public static final int BL_NONE = 0;
    public static final int BL_HTBLP = 1;

    private PlHardTurboPluginPanel panel;
    private final int[] casChunkName = {116, 114, 112, 56};

    private ConfigClass cfcPlHardTurbo;
    private BooleanConfigEntry cfgInvertPolarity;
    private IntegerConfigEntry cfgPilotToneHeader;
    private IntegerConfigEntry cfgPilotToneBlocks;
    private IntegerConfigEntry cfgSilenceAfterHeader;
    private BooleanConfigEntry cfgPauseAfterHeader;
    private BooleanConfigEntry cfgLoaderNamedAsFile;
    private BooleanConfigEntry cfgHTBLPToCassetteBootFile;

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

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

    /**
     * Generate instructions for generator
     */
    public int[] convertItem(AbstractPlayListItem _pl) throws Exception {

        PlHardTurboPlaylistItem pl;
        pl = (PlHardTurboPlaylistItem) _pl;

        /* Initialization of streams and initial setup instructions*/
        InstructionStream is = new InstructionStream();
        

        /*Binary file*/
        convertBinaryHT(is, pl);

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

    }

    private void addDataBlock(InstructionStream is, int[] data, int pilotPulses) throws Exception {

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

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

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

        is.add(data);


        /*1 safety narrow pulse*/
        is.add(SignalGenerator.INSTR_NARROW);

        /*Block separator*/
        is.add(SignalGenerator.INSTR_BLOCKSEP);

    }

    public AbstractPlayListItem getItemFromPanel() {
        PlHardTurboPlaylistItem 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 PlHardTurboPluginPanel();

        /*Initialize configuration class*/
        cfcPlHardTurbo = new ConfigClass("PlHT", "Hard Turbo");
        cfgInvertPolarity = new BooleanConfigEntry("invertPolarity", "Invert polarity of pulses", false);

        cfgPilotToneHeader = new IntegerConfigEntry("pilotToneHeader", "Header block pilot tone length [pulse]", 4096, 256, 16384);
        cfgPilotToneBlocks = new IntegerConfigEntry("pilotToneBlock", "Data block pilot tone length [pulse]", 2048, 256, 16384);
        cfgSilenceAfterHeader = new IntegerConfigEntry("silenceAfterHeader","Silence after header [s]",0,0,30);
        cfgPauseAfterHeader = new BooleanConfigEntry("pauseAfterHeader","Pause after header",false);
        cfgLoaderNamedAsFile = new BooleanConfigEntry("loaderNamedAsFile", "Name loader same as file", false);
        cfgHTBLPToCassetteBootFile = new BooleanConfigEntry("HTBLPToCassetteBootFile","Convert HTBL+ loader to a standard cassette boot file",false);

        /*Add entries to the class*/
        cfcPlHardTurbo.addEntry(cfgInvertPolarity);
        cfcPlHardTurbo.addEntry(cfgPilotToneHeader);
        cfcPlHardTurbo.addEntry(cfgPilotToneBlocks);
        cfcPlHardTurbo.addEntry(cfgSilenceAfterHeader);
        cfcPlHardTurbo.addEntry(cfgPauseAfterHeader);
        cfcPlHardTurbo.addEntry(cfgLoaderNamedAsFile);
        cfcPlHardTurbo.addEntry(cfgHTBLPToCassetteBootFile);

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

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

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

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

    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_HL);
            return;
        }

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

        /*Put instructions*/
        addDataBlock(is, dataChunk.getData(), dataChunk.getAux());

    }

    public BinaryConvertor[] getBinaryConvertors() {

        BinaryConvertor[] convertors = new BinaryConvertor[2];
        convertors[0] = new PlHardTurboBinaryConvertor(this, "Hard Turbo", BL_NONE);
        convertors[1] = new PlHardTurboBinaryConvertor(this, "Hard Turbo + HTBL+", BL_HTBLP);
        return convertors;

    }

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

    private void convertBinaryHT(InstructionStream is, PlHardTurboPlaylistItem pl) throws Exception {

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

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

        int blockPilotPulses = cfgPilotToneBlocks.getAsInt();
        int headerPilotPulses = cfgPilotToneHeader.getAsInt();

        if (pl.binaryLoader == BL_HTBLP) {
            addHTBLP(headerPilotPulses, blockPilotPulses, pl.fname, pl.pulses,is);
        }
        else {
            Utils.addSetupInstruction(is, pl.pulses, casChunkName, cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_HL);
        }

        /*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 main header*/
        generateMainHeader(pl.fname, headerPilotPulses, is);
        addSilenceAfterHeader(is);

        /*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 calcCheckSum(int[] data) {

        int chsum = data[0];
        for (int i = 1; i < data.length - 1; i++) {
            chsum = chsum ^ data[i];
        }
        data[data.length - 1] = chsum;
    }

    private void generateMainHeader(String displayedName, int pilotTonePulses, InstructionStream is) throws Exception {
        /*Generate header block 41 bytes*/
        int[] fHeader = new int[41];
        fHeader[0] = 0;

        /*Blanks*/
        for (int i = 1; i < fHeader.length - 2; i++) {
            fHeader[i] = 32;
        }
        /*Place EOL*/
        fHeader[fHeader.length - 2] = 155;
        String fName = displayedName;
        if (fName.length() > 38) {
            fName = fName.substring(0, 38);
        }
        char[] nameChars = fName.toCharArray();
        for (int i = 0; i < nameChars.length; i++) {
            fHeader[i + 1] = (nameChars[i]);
        }

        calcCheckSum(fHeader);
        addDataBlock(is, fHeader, pilotTonePulses);

    }

    private void generateSegment(Segment seg, int pilotTonePulses, InstructionStream is) throws Exception {
        int[] bHeader = new int[6];
        int sLo = seg.start % 256;
        int sHi = seg.start / 256;
        int eLo = (seg.stop + 1) % 256;
        int eHi = (seg.stop + 1) / 256;

        bHeader[0] = 255;
        bHeader[1] = sLo;
        bHeader[2] = sHi;
        bHeader[3] = eLo;
        bHeader[4] = eHi;
        calcCheckSum(bHeader);

        /*Address*/
        addDataBlock(is, bHeader, pilotTonePulses);

        /*Segment data*/
        int[] segData = new int[seg.data.length + 2];
        segData[0] = 255;
        System.arraycopy(seg.data, 0, segData, 1, seg.data.length);
        calcCheckSum(segData);
        addDataBlock(is, segData, pilotTonePulses);
    }

    private void generateTermHeader(int pilotTonePulses, InstructionStream is) throws Exception {
        int[] termHeader = new int[6];
        /*Termination header*/
        for (int i = 0; i < 5; i++) {
            termHeader[i] = 255;
        }
        calcCheckSum(termHeader);
        addDataBlock(is, termHeader, pilotTonePulses);
    }

    private void addHTBLP(int headerPilotTone, int blockPilotTone, String fname,PulseCrate pulses, InstructionStream is) throws Exception {

        String headerName;
        if (cfgLoaderNamedAsFile.getAsBoolean() == true) {
            headerName = fname;
        }
        else {
            headerName = "HTBL+";
        }
        
        /*Get loader data*/
        if (HTBLPLoaderData==null) {
            HTBLPLoaderData = Utils.getLoaderFromResource("/turgen/plugin/plturbo/loaders/htblp.xex");
        }
        DOS2Binary loaderDTB = new DOS2Binary("");
        loaderDTB.analyzeFromData(HTBLPLoaderData);
        
        /*Convert loader to Hard Turbo*/
        if (cfgHTBLPToCassetteBootFile.getAsBoolean()==false) {
            Utils.addSetupInstruction(is, pulses, casChunkName, cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_HL);

            generateMainHeader(headerName, headerPilotTone, is);
            addSilenceAfterHeader(is);
            
            generateSegment(loaderDTB.getFirstDataSegment(),blockPilotTone,is);
            generateSegment(loaderDTB.getFirstJumpSegment(),blockPilotTone,is);
            generateTermHeader(blockPilotTone, is);
            
        }
        /*Convert loader to a standard cassette boot file*/
        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 HTBL+ loader is not present");
            }
            ((StandardPlugin) stdPlugin).convertDirtyMonolithicBinaryUsingData(
                HTBLPLoaderData,
                is);
            Utils.addSetupInstruction(is, pulses, casChunkName, cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_HL);
        }
        
        

    }

    
    
    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);
            }
        }
    }

    public boolean isUsingAlternateFileChooser() {
        return false;
    }
    
    private int[] HTBLPLoaderData = null;

}
