package turgen.plugin.plturbo;

import turgen.plugin.*;
import turgen.generator.SignalGenerator;
import turgen.config.Configuration;
import java.io.*;
import java.util.Arrays;
import javax.swing.*;
import turgen.Turgen;
import turgen.Utils;
import turgen.config.BooleanConfigEntry;
import turgen.config.ConfigClass;
import turgen.dtb.DOS2Binary;
import turgen.playlist.AbstractPlayListItem;
import turgen.tapeimage.DataChunk;
import turgen.tapeimage.TapeImageChunk;
import turgen.tapeimage.TurboChunk;

public class PlTurboBlizzardPlugin implements Plugin {

    
    private String lastError;
    private PlTurboPluginPanel panel;
    private InOutProvider ioProvider;

    int[] casChunkName={116,114,112,51};

    private ConfigClass cfcPlTurboBlizzard;
    private BooleanConfigEntry cfgLongGapsBetweenBlocks;
    private BooleanConfigEntry cfgInvertPolarity;

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


    /** Identifikacni jmeno zasuvneho modulu*/
    public String getName() {
        return "Turbo Blizzard";
    }

    /*************************************************************/

    /**Tvorba instrukci pro generator*/
    public int[] getInstructions(AbstractPlayListItem _pl) {

        RandomAccessFile raf = null;

        SimplePlaylistItem pl;
        pl=(SimplePlaylistItem)_pl;
        try {

            boolean longGaps=cfgLongGapsBetweenBlocks.getAsBoolean();

            /*Otevreni souboru a kontrola velikosti*/
            raf = new RandomAccessFile(pl.inFile,"r");
            int l = (int)raf.length();
            if (l>1024*1024) throw new FileTooLargeException(pl.inFile);

            /*Read all data from file and close it*/
            byte[] fData = new byte[l];
            int[] fileData = new int[l];

            raf.readFully(fData);
            raf.close();

            for(int i=0;i<fData.length;i++) {
                fileData[i]=fData[i]<0?fData[i]+256:fData[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];
            }


            /*Otevreni proudu pro zapis instrukci*/
            InstructionStream is = new InstructionStream();

            /*Zahajeni instrukci typu SETUP*/
            Utils.addSetupInstruction(is, pl.pulses, casChunkName,cfgInvertPolarity.getAsInt(),SignalGenerator.FLAG_ORDER_HL);

            /*Ticho 3 sekundy*/
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            is.add(3);

            /*Synchronizacni blok*/
            is.add(SignalGenerator.INSTR_PILOTTONE);
            is.add(3072);
            is.add(SignalGenerator.INSTR_NARROW);
            is.add(SignalGenerator.INSTR_NARROW);
            
            is.add(SignalGenerator.INSTR_DATA);
            is.add(0);
            

            /*Ticho 0.3 sekundy*/
            is.add(SignalGenerator.INSTR_SHORTSILENCE);
            is.add(SignalGenerator.INSTR_SHORTSILENCE);
            is.add(SignalGenerator.INSTR_SHORTSILENCE);

            /*Hlavicka:
              1. Zavadeci ton
             *2. 2 kratke pulzy
             *3. 76 znaku nazev
             *4. Kontrolni soucet (soucet modulo 256)
             *5. Vyplnovy byte
             */
            is.add(SignalGenerator.INSTR_PILOTTONE);
            is.add(302);
            is.add(SignalGenerator.INSTR_NARROW);
            is.add(SignalGenerator.INSTR_NARROW);
            is.add(SignalGenerator.INSTR_DATA); 
            is.add(78);

            int[] header = new int[76];

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

            int max=76;
            int hl = pl.fname.length();
            if (hl>max) hl=max;

            /*Jmeno souboru*/
            for(int i=0;i<hl;i++) {
                header[i]=(byte)pl.fname.charAt(i);
            }

            /*Kontrolni soucet hlavicky a zapis hlavicky do proudu*/
            int chsum=0;
            for(int i=0;i<76;i++) {
                chsum+=header[i];
            }
            is.add(header);
            is.add(chsum % 256);

            /*Vyplnovy byte*/
            is.add(0);

            

            /*Ticho 5 sekund*/
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            is.add(5);


            /*ZAPIS JEDNOTLIVYCH BLOKU*/
            int[] block = new int[1024+2+3];
            int fptr=0;
            int rest=0;

            int startPtr;
            int endPtr;

            /*Pokud neni vse zapsano*/
            while (fptr<l) {

                startPtr=fptr;

                /*Zapsat zavadeci ton*/
                is.add(SignalGenerator.INSTR_PILOTTONE);
                is.add(302);
                is.add(SignalGenerator.INSTR_NARROW);
                is.add(SignalGenerator.INSTR_NARROW);
                
                is.add(SignalGenerator.INSTR_DATA);
                is.add(1024+2+3);

                /*Kolik zbyva do konce a je-li to vice nez 1024 bytu*/
                rest=l-fptr;
                if (rest>1024) rest=1024;

                /*2 byty Delka */
                block[0]=rest % 256;
                block[1]=rest / 256;

                /*Data bloku*/
                for(int i=0;i<rest;i++) {
                    block[i+2]=fileData[fptr];fptr++;
                }

                /*Doplneni bloku nulami*/
                for (int i=rest;i<1024;i++) {
                    block[i+2]=0;
                }

                /*Vzdy nulovy byte*/
                block[1026]=0;

                /*Vypocet kontrolniho souctu*/
                chsum=0;
                for (int i=0;i<1027;i++) {
                    chsum+=block[i];

                }
                block[1027]=(chsum % 256);

                /*Vyplnovy byte*/
                block[1028]=0;

                /*Zapis bloku*/
                is.add(block);
                

                endPtr=fptr;

                /*Silence after init or simple block separation*/
                int wait=Utils.calculateInitSilence(startPtr, endPtr-1, initLocations, pl.pauses);
                if (wait==0) {
                    if (longGaps==true) {
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                    }
                    else {
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                        is.add(SignalGenerator.INSTR_SHORTSILENCE);
                    }
                }
                else {
                    is.add(SignalGenerator.INSTR_LONGSILENCE);
                    is.add(Math.min(240, wait));
                }
                
            }

            /*Byl-li posledni blok dlouhy 1024 bytu, tak je potreba zapsat
             *EOF blok, protoze jinak se konec proste poznat neda. Kontrolni
             *soucet je vzdy nula
             */
            if (rest==1024) {
                is.add(SignalGenerator.INSTR_PILOTTONE);
                is.add(302);
                is.add(SignalGenerator.INSTR_NARROW);
                is.add(SignalGenerator.INSTR_NARROW);
                is.add(SignalGenerator.INSTR_DATA);
                is.add(1024+2+3);
                Arrays.fill(block, 0, block.length, 0);
                is.add(block);

                /*Safety pause*/
                is.add(SignalGenerator.INSTR_BLOCKSEP);

            }

            is.add(SignalGenerator.INSTR_END);
            return is.getInstructions();
        }

        catch(Exception e) {
            e.printStackTrace();
            if (e.getMessage()==null) lastError="Unable to produce instructions: "+e.toString();
            else lastError=e.toString();
            return null;

        }

        finally {
            try {
            raf.close();
            }
            catch(Exception e) {
                /*No reactions*/
            }
        }


    }

    public String getLastError() {
        return lastError;
    }

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

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

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

    public JPanel getItemPanel() {
        return panel;
    }

    public void init() {
        panel = new PlTurboPluginPanel(76);

        /*Initialize configuration class*/
        cfcPlTurboBlizzard = new ConfigClass("PlTurboBlizzard", "Turbo Blizzard");

        /*Initialize configuration entries*/
        cfgInvertPolarity =new BooleanConfigEntry("invertPolarity","Invert polarity of pulses",false);
        cfgLongGapsBetweenBlocks=new BooleanConfigEntry("longGapsBetweenBlocks","Long gaps between blocks",false);

        /*Add entries to the class*/
        cfcPlTurboBlizzard.addEntry(cfgInvertPolarity);
        cfcPlTurboBlizzard.addEntry(cfgLongGapsBetweenBlocks);

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

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


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

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

    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,cfgInvertPolarity.getAsInt(),SignalGenerator.FLAG_ORDER_HL);
            return;
        }

        /*Only data chunk accepted*/
        if (!(chunk instanceof DataChunk)) return;
        DataChunk dataChunk = (DataChunk)chunk;
        
        int l = dataChunk.getLength();
        /*Put 3 seconds of silence before the header*/
        if (l==0) {
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            is.add(3);
        }

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

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

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

        /*Put 5 seconds of silence after the header*/
        if (l==78) {
            is.add(SignalGenerator.INSTR_LONGSILENCE);
            is.add(5);
        }
        else {
            is.add(SignalGenerator.INSTR_BLOCKSEP);
        }

    }

    public BinaryConvertor[] getBinaryConvertors() {
       BinaryConvertor[] bcs = new BinaryConvertor[1];
        bcs[0]=new PlTurboBlizzardBinaryConvertor(this, "Turbo Blizzard");

        return bcs;
    }


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

    public boolean isUsingAlternateFileChooser() {
        return false;
    }

}
