package turgen.plugin.btape;

import turgen.plugin.*;
import turgen.generator.SignalGenerator;

import turgen.*;
import java.io.*;
import javax.swing.*;
import turgen.dtb.DOS2Binary;
import turgen.playlist.AbstractPlayListItem;
import turgen.config.*;
import turgen.generator.PulseCrate;
import turgen.generator.PulseCreator;
import turgen.plugin.csturbo.LoaderBinaryData;
import turgen.plugin.csturbo.Turbo2000Plugin;
import turgen.tapeimage.DataChunk;
import turgen.tapeimage.TapeImageChunk;
import turgen.tapeimage.TurboChunk;

public class BTapePlugin implements Plugin {
    
    
    private BTapePluginPanel panel;
    
    private final int[] casChunkName={116,114,98,116};
    private final int[] casChunkNameTr2k = {116,114,50,107};

    private ConfigClass cfcBTape;

    private IntegerConfigEntry cfgPilotLenBlock;
    private BooleanConfigEntry cfgProlongatePilotTone;
    private IntegerConfigEntry cfgSilenceAfterLoader;
    private IntegerConfigEntry cfgSilenceAfterFirstBlock;
    private BooleanConfigEntry cfgLoaderNamedAsFile;
    private BooleanConfigEntry cfgForceTr2kTapeImageChunk;
    private BooleanConfigEntry cfgPrependLoader;

    public static final int BL_NONE=0;
    public static final int BL_NANOBTAPE=1;
    public static final int BL_NANOBTAPE_UR=2;
    public static final int BL_NANOBTAPE_U2=3;

    
    /** Nova instance BTapePlugin */
    public BTapePlugin() {
    }
    
    
    
    /** Identifikacni jmeno zasuvneho modulu*/
    public String getName() {
        return "B-TAPE";
    }
    
    /*************************************************************/
    
    /**Tvorba instrukci pro generator*/
    public int[] convertItem(AbstractPlayListItem _pl) throws Exception {
        
        InstructionStream is = new InstructionStream();
        RandomAccessFile raf = null;
        
        BTapePlayListItem pl;
        pl=(BTapePlayListItem)_pl;
        
        /*Mirne navyseni poctu zavadecich pulzu*/
        int headPulses = cfgPilotLenBlock.getAsInt();
        if (cfgProlongatePilotTone.getAsBoolean()==true) {
            headPulses=PulseCreator.prolongatePilotTone(headPulses,32,pl.pulses.pilotTonePulseWidth);
        }
        
        try {
            
            /*Otevreni vsech souboru a docasnych proudu*/
            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];
            }
            
            
            
            /*If loader is to be prepended, then we prepend it*/
            if (cfgPrependLoader.getAsBoolean()==true) {
                    is.add(LoaderBinaryData.universalTurboLoader);
            }
            
            /*Pripadne pridani zavadece*/
            if (pl.loader!=BL_NONE) {
                prependLoader(is,pl);
            }
            
            /*Nastaveni*/
            if (cfgForceTr2kTapeImageChunk.getAsBoolean()==false) {
                Utils.addSetupInstruction(is, pl.pulses, casChunkName,SignalGenerator.FLAG_POLARITY_10,SignalGenerator.FLAG_ORDER_HL);
            }
            else {
                Utils.addSetupInstruction(is, Turgen.pulseCache.getPulsesForPlugin(this)[0], casChunkNameTr2k,SignalGenerator.FLAG_POLARITY_10,SignalGenerator.FLAG_ORDER_HL);
            }
            
            
            /*ZAPIS JEDNOTLIVYCH BLOKU*/
            int[] block = new int[1026];
            int fptr=0;
            int chsum=0;
            int rest=0;
            int blockCount=1;
            byte randomByte=(byte)System.currentTimeMillis();
            
            int[] nameArray = new int[11];
            /*Vymezerovat nazev*/
            for(int i=0;i<11;i++) {
                nameArray[i]=32;
            }
            
            if (Utils.createBTapeFilename(pl.fname,nameArray)==false) {
                throw new Exception("Invalid B-TAPE file name");
            }
            
            /*Instrukce pred kazdym blokem*/
            int[] heading= new int[5];
            heading[0]=SignalGenerator.INSTR_PILOTTONE;
            heading[1]=headPulses;
            heading[2]=SignalGenerator.INSTR_NARROW;
            heading[3]=SignalGenerator.INSTR_DATA;
            heading[4]=1026;
            
            /*Instukce za kazdym blokem*/
            int[] leadOut;
            if (pl.longGaps==false) {
                leadOut = new int[2];
                /*Safety pulse + safety pause*/
                leadOut[0]=SignalGenerator.INSTR_NARROW;
                leadOut[1]=SignalGenerator.INSTR_BLOCKSEP;
            }
            
            else {
                leadOut = new int[3];
                /*Safety pulse + pause*/
                leadOut[0]=SignalGenerator.INSTR_NARROW;
                leadOut[1]=SignalGenerator.INSTR_LONGSILENCE;
                leadOut[2]=1;
            }
            
            
            int aux2=128;
            if (pl.longGaps==true) aux2-=128;
            if (pl.doubleBlocks==true) aux2+=64;


            int startPtr;
            int endPtr;


            /*Pokud neni vse zapsano*/
            while (fptr<l) {
               
                startPtr=fptr;
                
                /*Kolik zbyva do konce a je-li to vice nez 3072 bytu*/
                rest=l-fptr;
                boolean lastTag=true;
                if (rest>1008) {rest=1008;lastTag=false;}
                
                /*Cislo bloku*/
                block[0]=blockCount;
                
                /*Rezim zapisu*/
                block[1]=aux2;
                
                /*Delka bloku a priznak konce*/
                int tmp=16+(rest&0x07FF);
                block[2]=tmp % 256;
                if (lastTag==false) {
                    block[3]=tmp / 256;
                }
                else {
                    block[3]=(tmp/256)|128;
                }
                
                /*Nepouzite*/
                block[4]=0;
                
                /*Nahodne cislo*/
                block[5]=randomByte;
                
                /*Jmeno souboru*/
                for(int i=0;i<11;i++) {
                    block[6+i]=nameArray[i];
                }
                
                
                /*1008 dat bloku*/
                for(int i=0;i<rest;i++) {
                    block[i+17]=fileData[fptr];fptr++;
                }
                
                endPtr=fptr;
                
                /*Doplnit nulami*/
                for (int i=rest;i<1008;i++) {
                    block[i+17]=0;
                }
                
                /*Kontrolni soucet a zapis dat*/
                chsum=blockCount;
                for (int i=1;i<1025;i++) {
                    chsum^=block[i];
                }
                block[1025]=chsum;
                
                /*Zapis tri poli*/
                int cnt=1;
                if (blockCount==1 || pl.doubleBlocks==true) cnt=2;
                for(int k=0;k<cnt;k++) {
                    
                    
                    is.add(heading);
                    is.add(block);
                    is.add(leadOut);

                    if (blockCount==1 && k==0) {
                        is.add(SignalGenerator.INSTR_LONGSILENCE);
                        is.add(cfgSilenceAfterFirstBlock.getAsInt());
                    }
                    
                }

                /*Silence after init or simple block separation*/
                int wait=Utils.calculateInitSilence(startPtr, endPtr-1, initLocations, pl.silenceList);
                if (wait!=0) {
                    is.add(SignalGenerator.INSTR_LONGSILENCE);
                    is.add(wait);
                }
                
                /*Dalsi blok*/
                blockCount++;
                
            }
            
            is.add(SignalGenerator.INSTR_END);
            return is.getInstructions();
     
            
        }
        
        catch(Exception e) {
            throw e;
            
        }
        
        finally {
            try {
            if (raf!=null) raf.close();
            }
            catch(IOException e) {
                /*No reactions*/
            }
        }
       
        
    }
    
    public AbstractPlayListItem getItemFromPanel() {
        BTapePlayListItem 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 BTapePluginPanel(this);
        

        /*Initialize configuration class*/
        cfcBTape = new ConfigClass("BTape","B-TAPE");

        /*Initialize configuration entries*/
        cfgPilotLenBlock=new IntegerConfigEntry("pilotLenBlock","Pilot tone length [pulse]",2048,256,8192);
        cfgProlongatePilotTone=new BooleanConfigEntry("prolongatePilotTone","Prolongate pilot tone",true);
        cfgSilenceAfterFirstBlock=new IntegerConfigEntry("silenceAfterFirstBlock","Silence after first block [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);
        cfgForceTr2kTapeImageChunk = new BooleanConfigEntry("forceTr2kTapeImageChunk","Force tr2k tape image chunks",false);
        cfgPrependLoader = new BooleanConfigEntry("prependLoader","Prepend Universal Turbo loader",false);
        
        /*Add entries to the class*/
        cfcBTape.addEntry(cfgPilotLenBlock);
        cfcBTape.addEntry(cfgProlongatePilotTone);
        cfcBTape.addEntry(cfgSilenceAfterFirstBlock);
        cfcBTape.addEntry(cfgSilenceAfterLoader);
        cfcBTape.addEntry(cfgLoaderNamedAsFile);
        cfcBTape.addEntry(cfgForceTr2kTapeImageChunk);
        cfcBTape.addEntry(cfgPrependLoader);

        /*Register configuration class*/
        Configuration.registerClass(cfcBTape);
        
    }
    
    public void fillPanel(AbstractPlayListItem plr) {
        BTapePlayListItem dpr = (BTapePlayListItem)plr;
        panel.fill(dpr);
    }

   

    public void setInOutProvider(InOutProvider prov) {
        panel.setIOProvider(prov);
    }
    
    private int[] nanoBTAPEData = null;
    private int[] nanoBTAPEURData = null;
    private int[] nanoBTAPEU2Data = null;
    
    private void prependLoader(InstructionStream is,BTapePlayListItem 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_NANOBTAPE: {
                if (nanoBTAPEData==null) {
                    nanoBTAPEData = Utils.getLoaderFromResource("/turgen/plugin/btape/loaders/nanobtape.xex");
                }
                loaderData=nanoBTAPEData;
                fname="NanoBTAPE";
                
                break;
            }
            case BL_NANOBTAPE_UR: {
                if (nanoBTAPEURData==null) {
                    nanoBTAPEURData = Utils.getLoaderFromResource("/turgen/plugin/btape/loaders/nanobtape_ur.xex");
                }
                loaderData=nanoBTAPEURData;
                fname="NanoBTAPE";
                
                break;
                
            }
            case BL_NANOBTAPE_U2: {
                if (nanoBTAPEU2Data==null) {
                    nanoBTAPEU2Data = Utils.getLoaderFromResource("/turgen/plugin/btape/loaders/nanobtape_u2.xex");
                }
                loaderData=nanoBTAPEU2Data;
                fname="NanoBTAPE";
                
                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());
       
    }


     public String[] getSupportedTapeImageChunks() {
        return new String[] {"trbt","trtt"};
    }

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

        /*trXX chunk*/
        if (chunk instanceof TurboChunk) {
            int bdr = ((TurboChunk)(chunk)).getBaudRate();

            /*Upkeep baud rate range*/
            if (bdr<2270) bdr=2270;
            if (bdr>9600) bdr=9600;

            /*Choose closest known pulses*/
            PulseCrate speed = Utils.getNearestBaudRate(bdr, Turgen.pulseCache.getPulsesForPlugin(this));

            /*Generate setup instruction*/
            Utils.addSetupInstruction(is, speed, chunk.getType(),SignalGenerator.FLAG_POLARITY_10,SignalGenerator.FLAG_ORDER_HL);

            return;
        }

        /*Only data chunk accepted*/
        if (!(chunk instanceof DataChunk)) return;

        DataChunk dataChunk = (DataChunk)chunk;
        

        /*Zavadeci ton a data bloku*/
        is.add(SignalGenerator.INSTR_PILOTTONE);
        is.add(dataChunk.getAux());

        /*Kratky pulz*/
        is.add(SignalGenerator.INSTR_NARROW);

        /*Data bloku*/
        is.add(SignalGenerator.INSTR_DATA);
        is.add(dataChunk.getLength());

        int[] dta = dataChunk.getData();

        for(int i=0;i<dataChunk.getLength();i++) {
            is.add(dta[i]);
        }

        /*Bezpecnostni pulz*/
        is.add(SignalGenerator.INSTR_NARROW);
    }

    public BinaryConvertor[] getBinaryConvertors() {
        
        BinaryConvertor bcs[] = new BinaryConvertor[4];
        bcs[0]=new BTapeBinaryConvertor(this,"B-TAPE + NANOBTAPE",2820, 3434, 1792, 1792+1023 ,BL_NANOBTAPE);
        bcs[1]=new BTapeBinaryConvertor(this,"B-TAPE + NANOBTAPE[UR]",1796, 2468, 49152, 49152+1023,BL_NANOBTAPE_UR);
        bcs[2]=new BTapeBinaryConvertor(this,"B-TAPE + NANOBTAPE[U2]",1796, 2468, 63488, 63488+1023,BL_NANOBTAPE_U2);
        bcs[3]=new BTapeBinaryConvertor(this,"B-TAPE",0, 0, 0, 0, BL_NONE);
        return bcs;
    }

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

    public boolean isUsingAlternateFileChooser() {
        return false;
    }
    
    /** Shorten a file name to 8.3 form*/
    String get83Filename(String filename) {
        
        StringBuilder sb = new StringBuilder();
        char[] nameChars = filename.toCharArray();
        
        /*Index of the last dot*/
        int dotIndex = filename.lastIndexOf('.');
        int sentinelIndex = nameChars.length;
        if (dotIndex!=-1) sentinelIndex=dotIndex;
        
        int count=0;
        for(int i=0;i<sentinelIndex && count<8 && i<nameChars.length;i++) {
            if (nameChars[i]!='.') {
                sb.append(nameChars[i]);
                count++;
            }
        }
        sb.append('.');
        count=0;
        for(int i=(sentinelIndex+1);i<nameChars.length && count<3;i++) {
            if (nameChars[i]!='.') {
                sb.append(nameChars[i]);
                count++;
            }
        }
        
        
        return sb.toString();
    }

}
