package turgen.plugin.kblock;

import turgen.plugin.*;
import turgen.generator.SignalGenerator;
import java.io.*;
import javax.swing.*;
import turgen.PulsesException;
import turgen.Turgen;
import turgen.Utils;
import turgen.dtb.DOS2Binary;
import turgen.playlist.AbstractPlayListItem;
import turgen.config.*;
import turgen.plugin.csturbo.LoaderBinaryData;
import turgen.plugin.csturbo.Turbo2000Plugin;
import turgen.tapeimage.TapeImageChunk;

public class KBlockPlugin implements Plugin {
    private KBlockPluginPanel panel;
    
    private final int[] casChunkName = {116, 114, 50, 107};

    private ConfigClass cfcKBlock;
    private IntegerConfigEntry cfgPilotLenA;
    private IntegerConfigEntry cfgPilotLenKBlock;
    private IntegerConfigEntry cfgSilenceAfterLoader;
    private IntegerConfigEntry cfgSilenceAfterHeader;
    private BooleanConfigEntry cfgLoaderNamedAsFile;
    private BooleanConfigEntry cfgPrependLoader;

    public static final int BL_NONE = 0;
    public static final int BL_MINITBL = 1;
    public static final int BL_NANOTBL = 2;
    public static final int BL_NANOTBL_UR = 3;
    public static final int BL_NANOTBL_U2 = 4;

    /**
     * Nova instance KBlockPlugin
     */
    public KBlockPlugin() {
    }

    /**
     * Identifikacni jmeno modulu
     */
    public String getName() {
        return "Turbo 2000 - kilobyte blocks";
    }

    /**
     * **********************************************************
     */
    /**
     * Tvorba instrukci pro generator
     */
    public int[] convertItem(AbstractPlayListItem _pl) throws Exception {

        KBlockPlayListItem pl;

        pl = (KBlockPlayListItem) _pl;
        RandomAccessFile raf = null;

        try {

            /*Nacteni vstupniho souboru jako pole int[]*/
            int[] fileData;
            byte[] b;
            raf = new RandomAccessFile(pl.inFile, "r");
            if (raf.length() > 1024 * 1024) {
                throw new FileTooLargeException(pl.inFile);
            }
            b = new byte[(int) raf.length()];
            raf.readFully(b);

            /*Uzavreni vstupniho souboru*/
            raf.close();

            /*Konverze na int*/
            fileData = new int[b.length];
            for (int i = 0; i < b.length; i++) {
                fileData[i] = b[i] < 0 ? b[i] + 256 : b[i];
            }

            /*Check for binary file*/
            int[] initLocations;
            try {
                DOS2Binary dtb = new DOS2Binary(pl.inFile);
                dtb.analyzeFromData(fileData);
                initLocations = dtb.getInitLocations();
            } catch (Exception e) {
                initLocations = new int[0];
            }

            InstructionStream is = new InstructionStream();

            /*If loader is to be prepended, then we prepend it*/
            if (cfgPrependLoader.getAsBoolean() == true) {
                for (int i = 0; i < LoaderBinaryData.universalTurboLoader.length; i++) {
                    is.add(LoaderBinaryData.universalTurboLoader[i]);
                }
            }

            /*Pripadne pridani zavadece*/
            prependBinaryLoader(is, pl);

            /*Nastaveni*/
            Utils.addSetupInstruction(is, pl.pulses, casChunkName, SignalGenerator.FLAG_POLARITY_10, SignalGenerator.FLAG_ORDER_HL);

            /*Hlavicka*/
            is.add(SignalGenerator.INSTR_PILOTTONE);
            is.add(cfgPilotLenA.getAsInt());
            is.add(SignalGenerator.INSTR_SYNC);
            is.add(SignalGenerator.INSTR_DATA);
            is.add(19);

            int[] header = new int[19];
            header[0] = 0;
            header[1] = 0;

            /*Vymezerovat nazev*/
            for (int i = 2; i < 18; i++) {
                header[i] = 32;
            }

            /*Nazev - 16 bajtu*/
            int bar = pl.fname.length() < 16 ? pl.fname.length() : 16;
            byte[] nameBytes = pl.fname.substring(0, bar).getBytes();
            /*Konverze na int*/
            for (int i = 0; i < nameBytes.length; i++) {
                header[2 + i] = (nameBytes[i] < 0) ? nameBytes[i] + 256 : nameBytes[i];
            }

            /*Kontrolni soucet hlavicky*/
            int chsum = header[0];
            for (int i = 1; i < 18; i++) {
                chsum = chsum ^ header[i];
            }
            /*Chsum*/
            header[18] = chsum;
            is.add(header);

            /*Safety pulse + safety pause*/
            is.add(SignalGenerator.INSTR_NARROW);
            is.add(SignalGenerator.INSTR_BLOCKSEP);

            /*Dalsi pauza*/
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            is.add(cfgSilenceAfterHeader.getAsInt());

            /*Vlastni bloky dat, po 1024 bytech*/
            int ptr = 0;  /*Pozice v datech*/

            int dl = fileData.length - 1;   /*Index posledniho prvku dat*/

            int bcount = 0;

            int startPtr;
            int endPtr;

            /*Pokud nejsou poslana vsechna data*/
            while (ptr < fileData.length) {

                startPtr = ptr;

                /*Zavadeci ton bloku*/
                is.add(SignalGenerator.INSTR_PILOTTONE);
                is.add(cfgPilotLenKBlock.getAsInt());

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

                /*Vlastni blok*/
                is.add(SignalGenerator.INSTR_DATA);
                is.add(1026);

                /*Typ bloku*/
                /*Zbyva 1024 a vice bytu - 255*/
                if ((dl - ptr + 1) >= 1024) {
                    is.add(255);
                    chsum = 255;
                    bcount = 1024;
                }
                /*Zbyva mene nez 1024 bajtu*/
                else {
                    /*Kdyz je to vice nez 0, je to neuplny blok*/
                    if ((dl - ptr + 1) > 0) {
                        /*Kolik je to stranek ?*/
                        is.add(((dl - ptr + 1) / 256) + 251);
                        chsum = ((dl - ptr + 1) / 256) + 251;
                        bcount = dl - ptr + 1;
                    }
                    /*Je to 0, to bude koncovy blok*/
                    else {
                        is.add(250);
                        chsum = 250;
                        bcount = 0;
                    }
                }

                /*Data bloku*/
                for (int i = 0; i < bcount; i++) {
                    chsum = chsum ^ fileData[ptr];
                    is.add(fileData[ptr]);
                    ptr++;

                }
                /*Doplnit nulami na 1023*/
                for (int i = 0; i < (1023 - bcount); i++) {
                    chsum = chsum ^ 0;
                    is.add(0);
                }

                if (bcount < 1024) {
                    chsum = chsum ^ (bcount % 256);
                    is.add(bcount % 256);
                }

                is.add(chsum);

                endPtr = ptr;

                /*Safety pulse*/
                is.add(SignalGenerator.INSTR_NARROW);

                /*Silence after init or simple block separation*/
                int wait = Utils.calculateInitSilence(startPtr, endPtr - 1, initLocations, pl.silenceList);
                if (wait == 0) {
                    is.add(SignalGenerator.INSTR_BLOCKSEP);
                }
                else {
                    is.add(SignalGenerator.INSTR_LONGSILENCE);
                    is.add(wait);
                }

            }

            /*EOF BLOK*/
            /*Zavadeci ton bloku*/
            is.add(SignalGenerator.INSTR_PILOTTONE);
            is.add(cfgPilotLenKBlock.getAsInt());
            /*Synchropulz*/
            is.add(SignalGenerator.INSTR_SYNC);

            is.add(SignalGenerator.INSTR_DATA);
            is.add(1026);
            is.add(250);
            chsum = 250;
            for (int i = 0; i < 1024; i++) {
                chsum = chsum ^ 0;
                is.add(0);
            }
            is.add(chsum);

            /*Safety pulse + safety pause*/
            is.add(SignalGenerator.INSTR_NARROW);
            is.add(SignalGenerator.INSTR_BLOCKSEP);

            is.add(SignalGenerator.INSTR_END);

            return is.getInstructions();

        } catch (Exception e) {

            /*Uklidit*/
            try {
                if (raf!=null) raf.close();
            } catch (IOException xe) {
                
            }
            throw e;

        }

    }

    public AbstractPlayListItem getItemFromPanel() {
        KBlockPlayListItem 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 KBlockPluginPanel();

        /*Initialize configuration class*/
        cfcKBlock = new ConfigClass("KBlock", "Turbo 2000 - kilobyte blocks");

        /*Initialize configuration entries*/
        cfgPilotLenA = new IntegerConfigEntry("pilotLenA", "Header block pilot tone length [pulse]", 3072, 256, 8192);
        cfgPilotLenKBlock = new IntegerConfigEntry("pilotLenBlock", "Data block pilot tone length [pulse]", 2048, 256, 8192);
        cfgSilenceAfterHeader = new IntegerConfigEntry("silenceAfterHeader", "Silence after header [s]", 3, 0, 30);
        cfgSilenceAfterLoader = new IntegerConfigEntry("silenceAfterLoader", "Silence after loader [s]", 3, 0, 30);
        cfgLoaderNamedAsFile = new BooleanConfigEntry("loaderNamedAsFile", "Name loader same as file", false);
        cfgPrependLoader = new BooleanConfigEntry("prependLoader", "Prepend Universal Turbo loader", false);

        /*Add entries to the class*/
        cfcKBlock.addEntry(cfgPilotLenA);
        cfcKBlock.addEntry(cfgPilotLenKBlock);
        cfcKBlock.addEntry(cfgSilenceAfterHeader);
        cfcKBlock.addEntry(cfgSilenceAfterLoader);
        cfcKBlock.addEntry(cfgLoaderNamedAsFile);
        cfcKBlock.addEntry(cfgPrependLoader);

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

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

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

    private void prependBinaryLoader(InstructionStream is, KBlockPlayListItem pli) throws Exception {
        
        /*No loader, do nothing*/
        if (pli.loader==BL_NONE) return;
        
        /*Check for turbo 2000 plugin*/
        Turbo2000Plugin t2kPlugin = (Turbo2000Plugin)Turgen.pluginManager.getPlugin("turgen.plugin.csturbo.Turbo2000Plugin");
        if (t2kPlugin == null) {
            throw new PluginException("The Turbo 2000 plugin required to convert the binary loader is not present");
        }
        
        int[] loaderData=null;
        String fname=null;
        
        /*Determine which binary loader will be prepended*/
        switch (pli.loader) {
            case BL_MINITBL: {
                if (miniTBLData==null) {
                    miniTBLData = Utils.getLoaderFromResource("/turgen/plugin/kblock/loaders/minitbl.xex");
                }
                loaderData=miniTBLData;
                fname="MiniTBL";
                
                break;
            }
            case BL_NANOTBL: {
                if (nanoTBLData==null) {
                    nanoTBLData = Utils.getLoaderFromResource("/turgen/plugin/kblock/loaders/nanotbl.xex");
                }
                loaderData=nanoTBLData;
                fname="NanoTBL";
                
                break;
            }
            case BL_NANOTBL_UR: {
                if (nanoTBLURData==null) {
                    nanoTBLURData = Utils.getLoaderFromResource("/turgen/plugin/kblock/loaders/nanotbl_ur.xex");
                }
                loaderData=nanoTBLURData;
                fname="NanoTBL";
                
                break;
            }
            case BL_NANOTBL_U2: {
                if (nanoTBLU2Data==null) {
                    nanoTBLU2Data = Utils.getLoaderFromResource("/turgen/plugin/kblock/loaders/nanotbl_u2.xex");
                }
                loaderData=nanoTBLU2Data;
                fname="NanoTBL";
                
                break;
            }
            default: {
                return;
            }
        }
        
        /*Prepend the loader using Turbo 2000 plugin*/
        if (cfgLoaderNamedAsFile.getAsBoolean()==true) {
            fname=pli.fname;
        }
        t2kPlugin.convertMonolithicBinaryUsingData(is, loaderData, fname, true);
        is.add(SignalGenerator.INSTR_LONGSILENCE);
        is.add(cfgSilenceAfterLoader.getAsInt());

    }

    private int[] miniTBLData= null;
    private int[] nanoTBLData = null;
    private int[] nanoTBLURData = null;
    private int[] nanoTBLU2Data = null;

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

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

    }

    public BinaryConvertor[] getBinaryConvertors() {
        BinaryConvertor bcs[] = new BinaryConvertor[5];
        bcs[0] = new KBlockBinaryConvertor(this, "Turbo 2000 KB blocks + MINITBL", 2818, 3489, 1792, 1792 + 1023, BL_MINITBL);
        bcs[1] = new KBlockBinaryConvertor(this, "Turbo 2000 KB blocks + NANOTBL", 2818, 3318, 1792, 1792 + 1023, BL_NANOTBL);
        bcs[2] = new KBlockBinaryConvertor(this, "Turbo 2000 KB blocks + NANOTBL[UR]", 1794, 2349, 49152, 49152 + 1023, BL_NANOTBL_UR);
        bcs[3] = new KBlockBinaryConvertor(this, "Turbo 2000 KB blocks + NANOTBL[U2]", 1794, 2349, 63488, 63488 + 1023, BL_NANOTBL_U2);
        bcs[4] = new KBlockBinaryConvertor(this, "Turbo 2000 KB blocks", 0, 0, 0, 0, BL_NONE);
        return bcs;
    }

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

    public boolean isUsingAlternateFileChooser() {
        return false;
    }

}
