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.config.PulseCorrectionConfigEntry;
import turgen.dtb.DOS2Binary;
import turgen.dtb.DOS2BinaryProcessingException;
import turgen.dtb.Segment;
import turgen.playlist.AbstractPlayListItem;
import turgen.tapeimage.DataChunk;
import turgen.tapeimage.TapeImageChunk;
import turgen.tapeimage.TurboChunk;

public class PlASTPlugin implements Plugin {

    private PlASTPluginPanel panel;
    private final int[] casChunkName = {116, 114, 112, 54};

    private ConfigClass cfcPlAST;
    private BooleanConfigEntry cfgInvertPolarity;
    private BooleanConfigEntry cfgBUTWaitForKeypress;
    private IntegerConfigEntry cfgPilotToneHeader;
    private IntegerConfigEntry cfgPilotToneBlocks;
    private IntegerConfigEntry cfgSilenceAfterBUTLoader;
    private PulseCorrectionConfigEntry cfgPulseCorrections;

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

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

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

        PlASTPlaylistItem pl;
        pl = (PlASTPlaylistItem) _pl;
        
        pl.pulses.applyCorrections(cfgPulseCorrections.getAsPulseCrate(), false);

        /* Initialization of streams and initial setup instructions*/
        InstructionStream is = new InstructionStream();
        Utils.addSetupInstruction(is, pl.pulses, casChunkName,/*1-*/ cfgInvertPolarity.getAsInt(), SignalGenerator.FLAG_ORDER_LH);

        /*Binary file*/
        if (pl.conversionType == PlASTPlaylistItem.CV_BINARY_AST) {
            convertBinaryAST(is, pl);
        }
        else {
            convertBinaryBUT(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_NARROW);

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

        is.add(data);

        /*Four safety narrow pulses*/
        for (int i = 0; i < 4; i++) {
            is.add(SignalGenerator.INSTR_NARROW);
        }

        /*128 stop pulses*/
        for (int i = 0; i < 128; i++) {
            is.add(SignalGenerator.INSTR_STOP);
        }

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

    }

    public AbstractPlayListItem getItemFromPanel() {
        PlASTPlaylistItem 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 PlASTPluginPanel();

        /*Initialize configuration class*/
        cfcPlAST = new ConfigClass("PlAST", "Atari Super Turbo");
        cfgInvertPolarity = new BooleanConfigEntry("invertPolarity", "Invert polarity of pulses", false);
        cfgBUTWaitForKeypress = new BooleanConfigEntry("butWaitForKeypress", "BUT loader will wait for any key", true);
        cfgPilotToneHeader = new IntegerConfigEntry("pilotToneHeader", "AST header block pilot tone length [pulse]", 10000, 256, 16384);
        cfgPilotToneBlocks = new IntegerConfigEntry("pilotToneBlock", "Data block pilot tone length [pulse]", 4000, 256, 16384);
        cfgSilenceAfterBUTLoader = new IntegerConfigEntry("silenceAfterBUTLoader", "Silence after BUT loader [0.1s]", 10, 1, 50);
        cfgPulseCorrections = new PulseCorrectionConfigEntry("pulseCorrections", "Pulse corrections");

        /*Add entries to the class*/
        cfcPlAST.addEntry(cfgInvertPolarity);
        cfcPlAST.addEntry(cfgPilotToneHeader);
        cfcPlAST.addEntry(cfgPilotToneBlocks);
        cfcPlAST.addEntry(cfgBUTWaitForKeypress);
        cfcPlAST.addEntry(cfgSilenceAfterBUTLoader);
        cfcPlAST.addEntry(cfgPulseCorrections);

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

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

    public void setInOutProvider(InOutProvider prov) {

        panel.ioProvider = prov;
    }

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

    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;

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

    }

    public BinaryConvertor[] getBinaryConvertors() {

        BinaryConvertor[] convertors = new BinaryConvertor[2];
        convertors[0] = new PlASTBinaryConvertor(this, "Atari Super Turbo (AST)", PlASTPlaylistItem.CV_BINARY_AST);
        convertors[1] = new PlASTBinaryConvertor(this, "Atari Super Turbo (BUT)", PlASTPlaylistItem.CV_BINARY_BUT);
        return convertors;

    }

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

    private void convertBinaryAST(InstructionStream is, PlASTPlaylistItem pl) throws Exception {

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

        /*Check that binary file is suitable*/
        if (pl.conversionType == PlASTPlaylistItem.CV_BINARY_AST) {
            if (dtb.getTotalSegmentCount() > 44) {
                throw new DOS2BinaryProcessingException("Binary file has more than 44 segments or has an INIT segment");
            }
        }

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

        /*Create header*/
        int[] header = createASTHeader(segments, pl.fname);
        addDataBlock(is, header, cfgPilotToneHeader.getAsInt());

        /*Generate data blocks*/
        int blockPilotPulses = cfgPilotToneBlocks.getAsInt();

        int segCount = segments.length;
        for (int i = 0; i < segCount; i++) {
            Segment seg = segments[i];
            addDataBlock(is, seg.data, blockPilotPulses);
        }

    }

    private void convertBinaryBUT(InstructionStream is, PlASTPlaylistItem 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;

        if (dtb.getTotalSegmentCount() > 254) {
            throw new DOS2BinaryProcessingException("Binary file has more than 254 segments");
        }

        int canonicalRun = dtb.getCanonicalRunAddress();
        int blockPilotPulses = cfgPilotToneBlocks.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);
        }

        /*BUT loader placeholder - header*/
        int[] abutHeader = {0, 5, 128, 7, 182, 9};
        addDataBlock(is, abutHeader, blockPilotPulses);

        /*1 second safety silence*/
        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(1);

        /*Copy of BUT loader placeholder - data block*/
        int[] abutCopy = new int[abut_data.length];
        System.arraycopy(abut_data, 0, abutCopy, 0, abut_data.length);

        /*Substitute run address and number of blocks*/
        abutCopy[ABUT_BLOCKNUM] = segments.length;
        abutCopy[ABUT_RUNADDR] = canonicalRun % 256;
        abutCopy[ABUT_RUNADDR + 1] = canonicalRun / 256;

        /*To wait for keypress or not to wait*/
        if (cfgBUTWaitForKeypress.getAsBoolean() == false) {
            abutCopy[ABUT_KINIT] = 32;
        }

        /*File name*/
        String fname = pl.fname;
        int l = Math.min(20, fname.length());
        char[] fnc = fname.substring(0, l).toCharArray();
        for (int i = 0; i < l; i++) {
            abutCopy[i + ABUT_FNAME] = fnc[i];
        }

        /*BUT loader placeholder done*/
        addDataBlock(is, abutCopy, blockPilotPulses);
        for (int i = 0; i < cfgSilenceAfterBUTLoader.getAsInt(); i++) {
            is.add(SignalGenerator.INSTR_SHORTSILENCE);
        }


        /*Generate data blocks*/
        int[] bHeader = new int[4];
        int segCount = segments.length;

        for (int i = 0; i < segCount; i++) {

            Segment seg = segments[i];

            int sLo = seg.start % 256;
            int sHi = seg.start / 256;
            int eLo = (seg.stop + 1) % 256;
            int eHi = (seg.stop + 1) / 256;

            bHeader[0] = sLo;
            bHeader[1] = sHi;
            bHeader[2] = eLo;
            bHeader[3] = eHi;
            addDataBlock(is, bHeader, blockPilotPulses);
            addDataBlock(is, seg.data, blockPilotPulses);

            /*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++;
                }
            }

        }

    }

    private int[] createASTHeader(Segment[] segments, String fname) {

        int[] header = new int[256];

        /*Copy skeleton*/
        System.arraycopy(headerSkeleton, 0, header, 0, 256);

        int segCount = segments.length;

        /*Number of blocks*/
        header[0] = segCount;

        /*Segment table clear*/
        for (int i = 2; i < 178; i++) {
            header[i] = 0;
        }

        /*File name clear*/
        for (int i = 180; i < 200; i++) {
            header[i] = 0;
        }

        /*Checksum of info block is always 0*/
        header[200] = 0;

        /*Segment information*/
        for (int i = 0; i < segCount; i++) {
            Segment seg = segments[i];

            int baseIndex = 2 + (i * 4);
            int chsumIndex = 201 + i;

            header[baseIndex] = seg.start % 256;
            header[baseIndex + 1] = seg.start / 256;
            header[baseIndex + 2] = seg.data.length % 256;
            header[baseIndex + 3] = seg.data.length / 256;

            /*Checksum*/
            header[chsumIndex] = getCheckSum(seg.data, 1);
        }


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

        header[178] = 102;
        header[179] = 85;

        /*Correction of checksum*/
        int chsum = getCheckSum(header, 2);
        header[1] = chsum;
        return header;

    }

    private int getCheckSum(int[] data, int offset) {

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

    private static final int[] headerSkeleton
            = {
                0x03, 0xe4, 0xe0, 0x02, 0x02, 0x00, 0xd5, 0x00, 0x0a, 0x00, 0x86, 0x04, 0x00, 0x3e, 0x3e, 0x00,
                0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x60, 0x60, 0x60, 0x3c, 0x00,
                0x00, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00,
                0x00, 0x0e, 0x18, 0x3e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x7c,
                0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00, 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3c, 0x00,
                0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3c, 0x00, 0x60, 0x60, 0x6c, 0x78, 0x6c, 0x66, 0x00,
                0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x66, 0x7f, 0x7f, 0x6b, 0x63, 0x00,
                0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00,
                0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06,
                0x00, 0x00, 0x7c, 0x66, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x00,
                0x00, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00,
                0x00, 0x00, 0x66, 0x55, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x22, 0x2f, 0x35, 0x2c, 0x24, 0x25, 0x32,
                0x00, 0x19, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0xd5, 0x75, 0xd0, 0x66, 0x3e, 0x0c, 0x78,
                0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00, 0x66, 0x66, 0x18, 0x3c, 0x66, 0x7e, 0x66, 0x00,
                0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x78, 0x7c, 0x6e, 0x66, 0x06, 0x00,
                0x08, 0x18, 0x38, 0x78, 0x70, 0x70, 0x4b, 0x00, 0xd6, 0x47, 0xb4, 0xcf, 0x4b, 0x00, 0xd6, 0x41

            };

    private static int[] abut_data = {0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x4c, 0x71, 0x08, 0xad, 0x66, 0x08, 0x8d, 0x63, 0x08, 0xa9, 0x8e, 0x85, 0xcb, 0xa9, 0x08, 0x85, 0xcc, 0x20, 0xda, 0x07, 0xa5, 0xcf, 0xc9, 0x04, 0xd0, 0x37, 0xa5,
        0xd0, 0xd0, 0x33, 0xa9, 0x62, 0x8d, 0xe2, 0x02, 0xa9, 0x08, 0x8d, 0xe3, 0x02, 0xad, 0x8e, 0x08, 0x85, 0xcb, 0xad, 0x8f, 0x08, 0x85, 0xcc, 0x20, 0xda, 0x07, 0xa5, 0xcb, 0xcd, 0x90, 0x08, 0xd0,
        0x15, 0xa5, 0xcc, 0xcd, 0x91, 0x08, 0xd0, 0x0e, 0x20, 0xd4, 0x07, 0xce, 0x63, 0x08, 0xd0, 0xbe, 0x6c, 0x64, 0x08, 0x6c, 0xe2, 0x02, 0x4c, 0x67, 0x08, 0x78, 0xa9, 0x00, 0x8d, 0x0e, 0xd4, 0x8d,
        0x00, 0xd4, 0xa9, 0x03, 0x8d, 0x0f, 0xd2, 0xa9, 0x34, 0x8d, 0x02, 0xd3, 0x8d, 0x03, 0xd3, 0x4c, 0x0f, 0x08, 0xa2, 0x00, 0xad, 0x0f, 0xd2, 0x29, 0x10, 0xf0, 0xf9, 0xa9, 0xba, 0x8d, 0x1a, 0xd0,
        0xe8, 0xad, 0x0f, 0xd2, 0x29, 0x10, 0xd0, 0xf8, 0xa9, 0x00, 0x8d, 0x1a, 0xd0, 0x60, 0xa0, 0x00, 0x20, 0xf3, 0x07, 0xe0, 0x1d, 0x90, 0xf7, 0xe0, 0x32, 0xb0, 0xf3, 0xc8, 0xd0, 0xf2, 0xa9, 0x00,
        0x85, 0xcf, 0x85, 0xd0, 0x20, 0xf3, 0x07, 0xe0, 0x1d, 0xb0, 0xf9, 0xa9, 0x00, 0xa0, 0x08, 0x48, 0x20, 0xf3, 0x07, 0x68, 0xe0, 0x08, 0x90, 0x1a, 0xe0, 0x32, 0xb0, 0x16, 0xe0, 0x1d, 0x6a, 0x88,
        0xd0, 0xed, 0x91, 0xcb, 0xe6, 0xcf, 0xd0, 0x02, 0xe6, 0xd0, 0xe6, 0xcb, 0xd0, 0xdd, 0xe6, 0xcc, 0xd0, 0xd9, 0x58, 0xa9, 0x3c, 0x8d, 0x02, 0xd3, 0x8d, 0x03, 0xd3, 0xa9, 0xff, 0x8d, 0x0e, 0xd4,
        0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0xa9, 0xff, 0x8d, 0xfc, 0x02, 0xa9, 0x03, 0x4c, 0x88, 0x08, 0xa2, 0x92, 0xa0, 0x08, 0x20, 0x42, 0xc6, 0xa9, 0x00, 0x8d, 0x44, 0x02, 0xa9, 0x01, 0x85, 0x09,
        0xa9, 0xff, 0x8d, 0xfc, 0x02, 0xa9, 0x01, 0x20, 0xfc, 0xfd, 0x4c, 0x89, 0x07, 0x00, 0x00, 0x00, 0x00, 0x42, 0x55, 0x54, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    private static final int ABUT_BLOCKNUM = 0x866 - 0x780;
    private static final int ABUT_RUNADDR = 0x864 - 0x780;
    private static final int ABUT_FNAME = 0x896 - 0x780;
    private static final int ABUT_KINIT = (0x881 + 1) - 0x780;

    public boolean isUsingAlternateFileChooser() {
        return false;
    }

}
