package turgen;

import java.io.*;
import java.util.*;

import turgen.dtb.DOS2Binary;
import turgen.generator.SignalGenerator;
import turgen.generator.PulseCrate;
import turgen.plugin.InstructionStream;

/**Various utility functions*/
public class Utils {

    /**Return string representation of T2K-KB header block*/
    public static String kblockHeaderToString(int[] header) {
        byte[] nm = new byte[16];
        for(int i=0;i<16;i++) {
            nm[i]=(byte)header[2+i];
        }
        return new String(nm);
    }
    
    
    /**Convert list of number to int[] array*/
    public static int[] parseNumberList(String s) {
        
        StringTokenizer st = new StringTokenizer(s,",");
        Vector v = new Vector();
        int p=0;
        while(st.hasMoreTokens()) {
            try {
                p=Integer.parseInt(st.nextToken());
            } catch(Exception e) {
                p=0;
            }
            v.add(new Integer(p));
        }
        int[] retVal = new int[v.size()];
        for(int i=0;i<retVal.length;i++) {
            retVal[i]=((Integer)(v.elementAt(i))).intValue();
        }
        return retVal;

      
        
    }
    
    /**Create Turbo 2000 header*/
    public static int[] createT2KHeader(String name,int type,int load,int length,int run) {
        return createT2KHeader(name, type, load, length, run,false);
    }

    /**Create Turbo 2000 header*/
    public static int[] createT2KHeader(String name,int type,int load,int length,int run,boolean isLoader) {
        int[] retVal=new int[19];
        for(int i=2;i<12;i++) {
            retVal[i]=32;
        }
        retVal[0]=0;
        retVal[1]=type;
        int bar=0;
        byte[] bfn = name.getBytes();
        if (bfn.length>10) bar=10; else bar=bfn.length;
        for(int k=0;k<bar;k++) {
            retVal[2+k]=(bfn[k]<0) ? bfn[k]+256 : bfn[k];
        }
        if (isLoader==true) {
            retVal[11]=76+128;
        }

        retVal[12]=load % 256;
        retVal[13]=load / 256;
        retVal[14]=length % 256;
        retVal[15]=length / 256;
        retVal[16]=run % 256;
        retVal[17]=run / 256;
            
        int chsum = retVal[0];
        for(int i=1;i<18;i++) {
            chsum=chsum^retVal[i];
        }
        retVal[18]=chsum;
        
        return retVal;
    }

    /**Verify checksum of Super turbo block*/
    public static boolean checkSTBlock(int[] dta) {
        
        int ln=dta.length;
        if (ln<2) return false;
        
        int sum=dta[0];
        for(int i=1;i<ln-1;i++) {
            sum=sum^dta[i];
        }
        
        if (sum!=dta[ln-1]) return false;else return true;
    }

    /**Verify checksum of T2K block*/
    public static boolean checkT2KBlock(int[] dta) {
        
        int ln=dta.length;
        if (ln<2) return false;
        
        int sum=dta[0];
        for(int i=1;i<ln-1;i++) {
            sum=sum^dta[i];
        }
        
        if (sum!=dta[ln-1]) return false;else return true;
    }

    /**Return string representation of ST header*/
    public static String stHeaderToString(int[] dta) {
        StringBuffer sb = new StringBuffer();
        
        /*Name*/
        byte[] nm = new byte[20];
        for(int i=2;i<22;i++) nm[i-2]=(byte)dta[i];
        String s = new String(nm);
        sb.append(s);
        /*Addr*/
        sb.append(", LO:");
        sb.append(dta[22]+256*dta[23]);
        sb.append(", LN:");
        sb.append(dta[24]+256*dta[25]);
        sb.append(", RU:");
        sb.append(dta[26]+256*dta[27]);
        sb.append(", TP:");
        sb.append(dta[1]);
        
        return sb.toString();
    }

    /**Return string representation of T2K header*/
    public static String t2kHeaderToString(int[] dta) {
        StringBuffer sb = new StringBuffer();
        
        
        byte[] nm = new byte[10];
        for(int i=2;i<12;i++) nm[i-2]=(byte)dta[i];
        String s = new String(nm);
        sb.append(s);
        sb.append(", LO:");
        sb.append(dta[12]+256*dta[13]);
        sb.append(", LN:");
        sb.append(dta[14]+256*dta[15]);
        sb.append(", RU:");
        sb.append(dta[16]+256*dta[17]);
        sb.append(", TP:");
        sb.append(dta[1]);
        
        return sb.toString();
    }
    
    public static String stHeaderToHeaderFile(int[] dta) {
        StringBuffer sb = new StringBuffer();
        
        /*Eyecatcher*/
        sb.append("cs.superturbo.header,");
        
        /*Name*/
        byte[] nm = new byte[20];
        for(int i=2;i<22;i++) nm[i-2]=(byte)dta[i];
        String s = new String(nm);
        sb.append(s);
        sb.append(',');
        
        sb.append(dta[1]);sb.append(',');
        sb.append(dta[22]+256*dta[23]);sb.append(',');
        sb.append(dta[24]+256*dta[25]);sb.append(',');
        sb.append(dta[26]+256*dta[27]);
        
        
        return sb.toString();
    }
    
    public static String t2kHeaderToHeaderFile(int[] dta) {
        StringBuffer sb = new StringBuffer();
        
        /*Eyecatcher*/
        sb.append("cs.turbo2000.header,");
        
        /*Name*/
        byte[] nm = new byte[10];
        for(int i=2;i<12;i++) nm[i-2]=(byte)dta[i];
        String s = new String(nm);
        sb.append(s);
        sb.append(',');
        
        sb.append(dta[1]);sb.append(',');
        sb.append(dta[12]+256*dta[13]);sb.append(',');
        sb.append(dta[14]+256*dta[15]);sb.append(',');
        sb.append(dta[16]+256*dta[17]);
        
        
        return sb.toString();
    }
    
    /**Create ST header*/
    public static int[] createSTHeader(String name,int type,int load,int length,int run) {
        int[] retVal=new int[29];
        for(int i=2;i<22;i++) {
            retVal[i]=32;
        }
        retVal[0]=183;
        retVal[1]=type;
        int bar=0;
        byte[] bfn = name.getBytes();
        if (bfn.length>20) bar=20; else bar=bfn.length;
        for(int k=0;k<bar;k++) {
            retVal[2+k]=(bfn[k]<0) ? bfn[k]+256 : bfn[k];
        }
        retVal[22]=load % 256;
        retVal[23]=load / 256;
        retVal[24]=length % 256;
        retVal[25]=length / 256;
        retVal[26]=run % 256;
        retVal[27]=run / 256;
            
        int chsum = retVal[0];
        for(int i=1;i<28;i++) {
            chsum=chsum^retVal[i];
        }
        retVal[28]=chsum;
        
        return retVal;
    }
    

    /**Get a message string for some Exception*/
    public static String getExceptionMessage(Exception ex) {
        StringBuffer sb = new StringBuffer();
        sb.append(ex.getClass().getName());
        String m=ex.getMessage();
        if (m!=null) {
            sb.append(':');
            sb.append(' ');
            sb.append(m);
        }
        
        return sb.toString();
        
    }

    /**Get HTML formatted message for some exception with title specified*/
    public static String getTitledExceptionMessage(String title,Exception ex) {
        StringBuffer sb = new StringBuffer();
        sb.append("<HTML><B>");
        sb.append(title);
        sb.append("</B><BR>");
        sb.append(Utils.getExceptionMessage(ex));
        sb.append("</HTML>");
        return sb.toString();
        
    }
    
    

    /**Replace some elements in the string by other string*/
    public static String replaceElements(String s,String what,String replacement) {
        
        int whatLength=what.length();
        int idx=-1;
        
        StringBuffer sb = new StringBuffer();
       
        /*If the string that will be replaced has no length, we do nothing*/
        if (what.length()==0) return s;
        
        /*1000 iterations is maximum*/
        int iterations=0;
        
        while (s.length()>0 && iterations<1000) {
            
            iterations++;
            
            /*idx holds index of first letter of what*/
            idx=s.indexOf(what);
            
            /*If what is not in the input string, we are done*/
            if (idx==-1) {
                sb.append(s);
                break;
            }
            /*We add everything before what to the buffer*/
            sb.append(s.substring(0,idx));
            /*From input string we remove everything to the end of what*/
            s=s.substring(idx+whatLength);
            /*Replaced string to the buffer*/
            sb.append(replacement);
                    
        }
        
        
        return sb.toString();
    }
    
    /**This will convert command line - plain string with tokens
     * separated by space to array of strings. "x x" is one token
     */
    public static String[] getCommandArrayFromCommandLine(String cmdLine) {
        
        /*Length of input string*/
        int l =cmdLine.length();
        /*Vector of commands*/
        Vector cmdV=new Vector();
        /*Inside parenthesis ?*/
        int depth=0;
        /*Current position*/
        int index;
        
        StringBuffer sb = new StringBuffer();
        
        char ch;
        
        for(index=0;index<l;index++) {
            ch=cmdLine.charAt(index);
            /*SPACE, token separator when depth=0*/
            if (ch==' ' && depth==0) {cmdV.add(sb.toString());sb.setLength(0);continue;}
            if (ch=='"' && depth==1) {depth=0;continue;}
            if (ch=='"' && depth==0) {depth=1;continue;}
            sb.append(ch);
        }
        cmdV.add(sb.toString());
        
        String[] cmdArray = new String[cmdV.size()];
        cmdV.toArray(cmdArray);
        
        return cmdArray;
    }
    
    
    public static void printCommandLine(String[] cmdArray) {
        
        int l = cmdArray.length;
        if (l<1) {
            System.out.println("<EMPTY COMMAND>");
            return;
        }
        
        System.out.print("Command: "+cmdArray[0]+"|");
        System.out.print("Parameters: ");
        for(int i=1;i<l;i++) {
            System.out.print(cmdArray[i]);
            if (i<l-1) System.out.print(',');
        }
        System.out.println();
    }
    
    public static String getHtmlFormatedMessageList(Vector v) {
        if (v==null || v.size()==0) return "";
        int l = v.size();
        StringBuffer buf = new StringBuffer();
        buf.append("<HTML>");
        for(int i=0;i<l;i++) {
            buf.append((String)v.elementAt(i));
            buf.append("<BR>");
        }
        buf.append("</HTML>");
        return buf.toString();
                
    }
    
    public static String changeFileExtension(String filespec,String ext) {
        
        if (filespec.equals("")) return filespec;
        
        File f = new File(filespec);
        
        String parent = f.getParent();
        String name =f.getName();
        
        
        /*Ext should be at least 2 chars long*/
        if (ext.length()<2) return filespec+".badExtension";
        
        if (name.toLowerCase().endsWith(ext.toLowerCase())) return filespec;
        
        int l = name.length();
        int i = name.lastIndexOf('.');
        
        /*No dot in name*/
        if (i<0) {
            filespec+=ext;
            return filespec;
        }
        /*Dot at the end or dot is file name*/
        if (i==l-1) {
            filespec+=ext.substring(1);
            return filespec;
        }
        /*Dot at the beginning but not in the end*/
        if (i==0) {
            filespec+=ext;
            return filespec;
        }
        /*Dot in the middle*/
        name=name.substring(0,i);
        
        /*Parent exists, does not end with file separator*/
        if (parent!=null && !(parent.endsWith(Turgen.sp))) return parent+Turgen.sp+name+ext;
        /*Parent exists, trailing file separator present*/
        if (parent!=null && (parent.endsWith(Turgen.sp))) return parent+name+ext;
        /*Paren does not exist*/
        return name+ext;
        
        
        
    }
    
  
    /**Create and add SETUP insruction to the stream of generator instructions*/
    public static void addSetupInstruction(InstructionStream is, PulseCrate speed, int[] chunk,int polarity,int order) throws PulsesException,IOException {
        is.add(SignalGenerator.INSTR_SETUP);
        
        is.add(speed.pilotTonePulseWidth);
        is.add(speed.longPulseWidth);
        is.add(speed.shortPulseWidth);
        is.add(speed.syncPulseWidth);
        is.add(speed.stopPulseWidth);
        is.add(polarity);
        is.add(order);

        is.add(speed.baudRate);
        for (int i = 0; i < 4; i++) {
            is.add(chunk[i]);
        }
    }
    
    public static void addSetupInstruction(InstructionStream is, PulseCrate speed, String chunk,int polarity, int order) throws PulsesException,IOException {

        char[] chunkChars = chunk.toCharArray();
        int[] chunkInts = new int[4];

        for (int i = 0; i < 4; i++) {
            chunkInts[i]=(int)chunkChars[i];
        }

        addSetupInstruction(is, speed, chunkInts, polarity,order);
    }

    /**Verify whether file name is compatible with 8.3 convention*/
    public static boolean verifyBTapeFileName(String fn) {
        
        /*Length*/
        if (fn.length()>12 || fn.length()==0) return false;
        
        /*Dots*/
        int dp1 = fn.indexOf('.');
        int dp2 = fn.lastIndexOf('.');
        
        /*No dot */
        if (dp1==-1) {
            if (fn.length()>8) return false;else return true;
        }
        
        /*Only one dot, index must be <=8*/
        if (dp1!=dp2 || dp1>8) return false;
        
        /*Extension <=3 chars*/
        int li=fn.length()-1;
        if (li-dp1>3) return false;
        
        /*OK*/
        return true;
    }
    
    public static boolean createBTapeFilename(String fn,int[] nma) {
        
        /*verify file name*/
        if (Utils.verifyBTapeFileName(fn)==false) return false;
        
        /*Formatting according to  B-TAPE*/
        int ln = fn.length();
        int li = ln-1;
        int di = fn.indexOf('.');
        
        /*No dot*/
        if (di==-1) {
            for(int z=0;z<ln;z++) nma[z]=fn.charAt(z);
            return true;
        }
        
        /*Dot present*/
        for(int j=0;j<di;j++) {
            nma[j]=fn.charAt(j);
        }
        
        /*File name*/
        int cntr=0;
        for(int k=di+1;k<ln;k++) {
            nma[8+cntr]=fn.charAt(k);
            cntr++;
        }
        
        /*Done*/
        return true;
    }


    public static PulseCrate getNearestBaudRate(int originalBDR,PulseCrate[] speeds) {

        PulseCrate retVal=null;
        int mindif=Integer.MAX_VALUE;
        int cbdr;
        int dif;

        for(int i=0;i<speeds.length;i++) {
            cbdr=speeds[i].baudRate;
            dif=Math.abs(originalBDR-cbdr);
            if (dif<mindif) {
                retVal=speeds[i];
                mindif=dif;
            }
        }

        return retVal;
    }
    
    public static String checkForBinaryLoader(String fname,int ldrStart,int ldrEnd,int blkStart,int blkEnd,int maxSegments) {
        return checkForBinaryLoader(fname,ldrStart,ldrEnd,blkStart,blkEnd,maxSegments,false);
    }
    
    public static String checkForBinaryLoader(String fname,int ldrStart,int ldrEnd,int blkStart,int blkEnd,int maxSegments,boolean initSegmentsHarm) {

        /*First, try to load binary file*/
        File f = new File(fname);

        if (!(f.exists() && f.isFile())) {
            return "Unable to check loader: Input file does not exist or is not a regular file.";

        }

        DOS2Binary dtb;

        try {
             dtb = new DOS2Binary(fname);
             dtb.analyzeFromFile();
        }
        catch (Exception e) {
            return Utils.getExceptionMessage(e);

        }

        /*Check number of segments*/
        if (dtb.getTotalSegmentCount()>maxSegments) return LOADER_TOO_MUCH_SEGMENTS;
        
        /*Check if INIT harm*/
        if (initSegmentsHarm==true) {
            if (dtb.hasInitSegment()) return LOADER_HAS_INIT_SEGMENTS;
        }

        /*Check whether some segment covers memory occupied by the loader*/
        boolean b1=false;
        boolean b2=false;

        b1=dtb.coversMemory(ldrStart,ldrEnd);
        b2=dtb.coversMemory(blkStart,blkEnd);

        if (b1==true || b2==true) return LOADER_DESTROY;

        /*Check for INIT segment*/
        if (dtb.hasInitSegment()==true) return LOADER_PROB_NOT_DESTROY;
        else return LOADER_NOT_DESTROY;
    }

    private static final String LOADER_TOO_MUCH_SEGMENTS="Binary file has too many segments to be loaded with the loader.";
    private static final String LOADER_DESTROY="<HTML><FONT COLOR=\"RED\">Binary file will destroy the loader,<BR> because DATA segments of the binary file overlap with memory area(s) used by the loader.</FONT></HTML>";
    private static final String LOADER_PROB_NOT_DESTROY="<HTML>Input binary file will probably not destroy the loader.<BR><BR>DATA segments do not overlap with memory area(s) used by the loader.<BR> However the binary file contains INIT segment(s).</HTML>";
    private static final String LOADER_NOT_DESTROY="<HTML><FONT COLOR=\"BLUE\">Binary file will not destroy the loader.</FONT></HTML>";
    private static final String LOADER_HAS_INIT_SEGMENTS="INIT segment(s) will prevent execution of the binary file by the loader.";

    public static String getPolishedFilespec(char[] data,int ofset,int count) {

         StringBuffer sb = new StringBuffer();
         
         for (int i = ofset; i < ofset+count; i++) {

            char ch = data[i];
            if (data[i] > 128) {
                ch = (char) (data[i] - 128);
            }
            if (Character.isLetterOrDigit(ch) || ch=='.') {
                sb.append(ch);
                continue;
            }
            if (Character.isWhitespace(ch)) {
                sb.append(" ");
                continue;
            }
            if (!Character.isLetterOrDigit(ch)) {
                sb.append("0x" + Integer.toHexString(data[i]));
            }
        }
         
        String s1 = sb.toString().trim();
        s1=s1.replace(' ','_');
        return s1;
    }

    public static String getPolishedFilespec(int[] data,int ofset,int count) {

        char[] ch = new char[data.length];
        for(int i=0;i<ch.length;i++) {
            ch[i]=(char)data[i];
        }
        return getPolishedFilespec(ch, ofset, count);
    }

    public static boolean checkKSOBlock(int[] dta) {
        int ln=dta.length;
        if (ln<2) return false;

        int sum=dta[0];
        for(int i=1;i<ln-1;i++) {
            sum=sum+dta[i];
        }

        if ( (sum%256)!=dta[ln-1]) return false;else return true;
    }

    public static String padZeros(String s,int numZeros) {

        return padChars(s,numZeros,'0');
    }
    
    public static String padChars(String s,int numChars,char padChar) {
       int l = s.length();
        int k = numChars-l;
        String str = s;

        if (k<1) return s;

        for(int i=0;i<k;i++) {
            str=padChar+str;
        }

        return str;   
    }
    

    public static int[] getAsIntArray(byte[] byteArray) {

        int l = byteArray.length;
        int[] intArray =new int[l];

        byte b;
        
        for(int i=0;i<l;i++) {
            /*Orezani znamenka*/
            b=byteArray[i];
            intArray[i]=(b<0) ? b+256 : b;
        }

        return intArray;
    }

    public static int calculateInitSilence(int firstOffset,int lastOffset,int[] initLocations,int[] silenceList) {

        int wait = 0;
        for (int i = 0; i < initLocations.length; i++) {
            /*Which init locations hit last block*/
            if (initLocations[i] >= firstOffset && initLocations[i] <= lastOffset) {
                if (i < silenceList.length) {
                    wait += silenceList[i];
                }
            }
        }
        return wait;
    }

    public static int ascii2Internal(char c) {

        /*No inverse character*/
        if (c>128) c-=128;

        /*Minus 32 block*/
        if (c>=32 && c<=95) return c-32;
        /*Same block*/
        if (c>=96 && c<=127) return c;
        /*Plus 64 block*/
        if (c>=0 && c<=31)  return c+64;

        /*Non ASCII characters*/
        return 31;

    }

    public static String createNumberList(int[] numbers) {

        StringBuffer sb = new StringBuffer();
        if (numbers==null) return "";
        for(int i=0;i<numbers.length;i++) {
            sb.append(numbers[i]);
            if (i<numbers.length-1) sb.append(',');
        }
        return sb.toString();
    }

    public static String[] loadcsTurboHeaderFromFile(String headerSpec) {
        
        try {
            String[] retVal = new String[5];
            
            FileReader fr = new FileReader(headerSpec);
            BufferedReader br = new BufferedReader(fr);
            String line = br.readLine().trim();
            br.close();
            
            /*Check number of tokens*/
            StringTokenizer tk = new StringTokenizer(line,",");
            if (tk.countTokens()!=6) {
                return null;
            }
            
            /*Check eyecatcher*/
            String s = tk.nextToken();
            if (!s.equals("cs.turbo2000.header") && !s.equals("cs.superturbo.header")) {
                return null;
            }
            
            for(int i=0;i<5;i++) {
                retVal[i]=tk.nextToken();
            }
            
            return retVal;
        }
        catch(Exception e) {
            e.printStackTrace();
            return null;
        }
        
        
    }

    public static boolean checkBlizzardBlock(int[] data) {
        int ln=data.length;
        if (ln<3) return false;

        int sum=data[0];
        for(int i=1;i<ln-1;i++) {
            sum=sum+data[i];
        }

        if ( (sum%256)!=data[ln-1]) return false;else return true;
    }

    public static boolean checkTurboROMHeader(int[] data) {
        int sum = data[1];
        for(int i=2;i<data.length;i++) {
            sum^=data[i];
        }
        if ( sum!=data[0]) return false;else return true;
    }

    public static boolean checkTurboROMBlock(int[] data, int expectedCheckSum) {
        int sum = data[0];
        for(int i=1;i<data.length;i++) {
            sum^=data[i];
        }
        if (sum!=expectedCheckSum) return false;else return true;
    }

    public static void internalToAscii(char[] nameChars) {
        for (int i=0;i<nameChars.length;i++) {
            
            char oldChar=nameChars[i];
            
            /*No inverse video*/
            if (oldChar>=128) oldChar-=128;
            
            if (oldChar<63) {
                nameChars[i]=(char)(oldChar+32);
                continue;
            }
            
            if (oldChar>=64 && oldChar<=95) {
                nameChars[i]=(char)(oldChar-64);
                continue;
            }
            
            nameChars[i]=oldChar;
        }
    }

    public static boolean checkASTBlock(int[] data, int expectedCheckSum) {
        int chsum = data[0];
        for(int i=1;i<data.length;i++) {
            chsum ^=data[i];
        }
        return (chsum==expectedCheckSum);
    }
    
    
    public static String checkIfHeaderCanBeSet(File f) {
        return checkIfHeaderCanBeSet(f, true);
    }
    
    /** Check if a header can be set using given file specifier
     * 
     * @param f File
     * @param firstPart Display also first part of the text
     * @return Error message or null when file can be used
     * 
     */
    public static String checkIfHeaderCanBeSet(File f,boolean firstPart) {
        
        String fName = f.getName();
        String firstPartText = (firstPart==true)?"Unable to auto set header. ":"";
        
        if (!f.exists()) {
            return firstPartText+"File \""+fName+"\" does not exist.";
        }
        if (!f.isFile()) {
            return firstPartText+"File \""+fName+"\" is not a regular file.";
        }
        
        return null;
        
    }
}
