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.playlist.AbstractPlayListItem;
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;

    /**
     * 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();
        Utils.addSetupInstruction(is, pl.pulses, casChunkName,/*1-*/ cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_HL);

        /*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);

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

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

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

    public void setInOutProvider(InOutProvider prov) {

        panel.ioProvider = 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, is);
        }

        /*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, InstructionStream is) throws Exception {

        String headerName;
        if (cfgLoaderNamedAsFile.getAsBoolean() == true) {
            headerName = fname;
        }
        else {
            headerName = "HTBL+";
        }

        generateMainHeader(headerName, headerPilotTone, is);
        addSilenceAfterHeader(is);

        Segment seg1 = new Segment(0x2000, 0x220D, Segment.DATA_SEGMENT);
        seg1.data = htblpData;
        Segment seg2 = new Segment(736, 737, Segment.RUN_SEGMENT, 0x21D9);

        generateSegment(seg1, blockPilotTone, is);
        generateSegment(seg2, blockPilotTone, is);

        generateTermHeader(blockPilotTone, is);

    }

    int htblpData[] = {
        0x4c, 0x68, 0x09, 0xa9, 0x00, 0x8d, 0x64, 0x08, 0x8d, 0x44, 0x02, 0xa9, 0x01, 0x85, 0x09, 0xa9, 0x65, 0x8d, 0xe2, 0x02,
        0x8d, 0xe0, 0x02, 0xa9, 0x08, 0x8d, 0xe1, 0x02, 0x8d, 0xe3, 0x02, 0xa9, 0x27, 0x85, 0x34, 0xa9, 0x04, 0x85, 0x33, 0x85,
        0x35, 0xa9, 0x00, 0x85, 0x32, 0x20, 0xa1, 0x08, 0x90, 0xed, 0xa2, 0x00, 0xa0, 0x04, 0x20, 0x42, 0xc6, 0xa9, 0x3c, 0x8d,
        0x02, 0xd3, 0xa9, 0x01, 0x8d, 0xfc, 0x02, 0x85, 0x09, 0x20, 0xfc, 0xfd, 0xa9, 0x00, 0x8d, 0x63, 0x08, 0x85, 0x32, 0xa9,
        0x04, 0x85, 0x34, 0x85, 0x33, 0x85, 0x35, 0xa9, 0xff, 0x20, 0xa1, 0x08, 0x90, 0x39, 0x20, 0x6f, 0x08, 0xad, 0x00, 0x04,
        0x85, 0x32, 0xad, 0x01, 0x04, 0x85, 0x33, 0xad, 0x02, 0x04, 0x85, 0x34, 0xad, 0x03, 0x04, 0x85, 0x35, 0xa9, 0x65, 0x8d,
        0xe2, 0x02, 0xa9, 0x08, 0x8d, 0xe3, 0x02, 0xa9, 0xff, 0x20, 0xa1, 0x08, 0x90, 0x11, 0xa9, 0x3c, 0x8d, 0x02, 0xd3, 0x20,
        0x60, 0x08, 0x4c, 0x17, 0x08, 0x6c, 0xe2, 0x02, 0x6f, 0x00, 0x60, 0x20, 0x8e, 0xef, 0x20, 0x3e, 0xc6, 0x4c, 0xd2, 0x07,
        0xad, 0x00, 0x04, 0xc9, 0xff, 0xd0, 0x07, 0xad, 0x01, 0x04, 0xc9, 0xff, 0xf0, 0x17, 0xad, 0x64, 0x08, 0xd0, 0xe3, 0xad,
        0x00, 0x04, 0x8d, 0xe0, 0x02, 0xad, 0x01, 0x04, 0x8d, 0xe1, 0x02, 0xee, 0x64, 0x08, 0x4c, 0x65, 0x08, 0xa9, 0x3c, 0x8d,
        0x02, 0xd3, 0xa9, 0x3c, 0x8d, 0x02, 0xd3, 0x6c, 0xe0, 0x02, 0x85, 0x36, 0xa9, 0x34, 0x8d, 0x02, 0xd3, 0x8d, 0x03, 0xd3,
        0xa9, 0x80, 0x85, 0x10, 0x8d, 0x0e, 0xd2, 0x18, 0xa0, 0x00, 0x84, 0x30, 0x84, 0x31, 0x8c, 0x0e, 0xd4, 0x8c, 0x00, 0xd4,
        0x08, 0xd0, 0x70, 0x20, 0x43, 0x09, 0x90, 0xf9, 0xa9, 0x00, 0x85, 0x2e, 0x85, 0x37, 0xa0, 0xb4, 0x20, 0x3e, 0x09, 0x90,
        0xec, 0xc0, 0xd8, 0x90, 0xea, 0xe6, 0x2e, 0xd0, 0xf1, 0xc6, 0x37, 0xa0, 0xdb, 0x20, 0x43, 0x09, 0x90, 0xdb, 0xc0, 0xe6,
        0xb0, 0xf5, 0x20, 0x43, 0x09, 0x90, 0x44, 0xa0, 0xc6, 0x4c, 0x0d, 0x09, 0x28, 0xd0, 0x08, 0xa5, 0x36, 0x45, 0x2f, 0xd0,
        0x37, 0xf0, 0x0c, 0xa0, 0x00, 0xa5, 0x2f, 0x91, 0x32, 0xe6, 0x32, 0xd0, 0x02, 0xe6, 0x33, 0xa0, 0xc6, 0x08, 0xa9, 0x01,
        0x85, 0x2f, 0x20, 0x3e, 0x09, 0x90, 0x1c, 0xc0, 0xde, 0x26, 0x2f, 0xa0, 0xc6, 0x90, 0xf3, 0xa5, 0x31, 0x45, 0x2f, 0x85,
        0x31, 0xa5, 0x32, 0xc5, 0x34, 0xa5, 0x33, 0xe5, 0x35, 0x90, 0xc5, 0xa9, 0x00, 0xc5, 0x31, 0x68, 0xa9, 0xc0, 0x8d, 0x0e,
        0xd4, 0x85, 0x10, 0x8d, 0x0e, 0xd2, 0x60, 0x20, 0x43, 0x09, 0x90, 0x24, 0xa2, 0x04, 0xca, 0xd0, 0xfd, 0xa5, 0x30, 0x4a,
        0x25, 0x37, 0x8d, 0x1a, 0xd0, 0xc8, 0xf0, 0x13, 0xa5, 0x11, 0xf0, 0x0d, 0xad, 0x0f, 0xd2, 0x29, 0x10, 0xc5, 0x30, 0xf0,
        0xf0, 0x85, 0x30, 0x38, 0x60, 0xc6, 0x11, 0x18, 0x60, 0xa9, 0x00, 0x8d, 0x44, 0x02, 0xa9, 0x01, 0x85, 0x09, 0xa9, 0xff,
        0x8d, 0x01, 0xd3, 0x8d, 0xf8, 0x03, 0xa9, 0x84, 0x85, 0x0c, 0xa9, 0x09, 0x85, 0x0d, 0x20, 0x74, 0xe4, 0xa9, 0x8e, 0x85,
        0x0a, 0xa9, 0x09, 0x85, 0x0b, 0x18, 0x60, 0xa2, 0xa0, 0xa0, 0x09, 0x20, 0x42, 0xc6, 0xa9, 0x01, 0x8d, 0xfc, 0x02, 0x20,
        0xfc, 0xfd, 0x4c, 0xd2, 0x07, 0x48, 0x54, 0x42, 0x4c, 0x2b, 0x9b, 0xd7, 0x01, 0xa9, 0x00, 0x85, 0x32, 0xa9, 0x20, 0x85,
        0x33, 0xa9, 0xcf, 0x85, 0x34, 0xa9, 0x07, 0x85, 0x35, 0xa0, 0x00, 0xae, 0xd8, 0x21, 0xf0, 0x0e, 0xb1, 0x32, 0x91, 0x34,
        0xc8, 0xd0, 0xf9, 0xe6, 0x33, 0xe6, 0x35, 0xca, 0xd0, 0xf2, 0xae, 0xd7, 0x21, 0xf0, 0x08, 0xb1, 0x32, 0x91, 0x34, 0xc8,
        0xca, 0xd0, 0xf8, 0x4c, 0xcf, 0x07

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

}
