package org.flashtool.parsers.ta;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TAFileParser {
    private static final Pattern COMMENT_LINE_PATTERN = Pattern.compile("^//.*");
    private static final Pattern BLANK_LINE_PATTERN = Pattern.compile("^[\\s]*$");
    private static final Pattern PARTITION_LINE_PATTERN = Pattern.compile("^\\p{XDigit}{2}$");
    private static final Pattern DATA_LINE_PATTERN = Pattern.compile("^(\\p{XDigit}{8})(?: |\\t)+(\\p{XDigit}{4,8})((?:(?: |\\t)+\\p{XDigit}{2}{2})*)\\s*$");
    private static final Pattern CONTINUATION_DATA_LINE_PATTERN = Pattern.compile("^((?:(?: |\\t)+\\p{XDigit}{2})+)\\s*$");
    private BufferedReader aReader;
    protected int aPartition;
    private long aUnit;
    private int aUnitSize;
    private ArrayList<String> aContinuationUnitDataList = new ArrayList();
    private byte[] aUnitDataArray;
    private boolean aFoundPartition = false;
    private boolean aContinuationData = false;
    private Vector<TAUnit> units = new Vector<TAUnit>();
    private File tafile;

    public TAFileParser(File taf)  throws TAFileParseException, IOException {
    	tafile=taf;
    	FileInputStream inputStream = new FileInputStream(taf);
    	this.parse(inputStream);
        inputStream.close();
    }

    public String getName() {
    	return tafile.getName();
    }
    
    public TAFileParser(InputStream inputStream) throws TAFileParseException, IOException {
        this.parse(inputStream);
        inputStream.close();
    }


    public void parse(InputStream inputStream) throws TAFileParseException, IOException {
        this.aReader = new BufferedReader(new InputStreamReader(inputStream));
        this.aPartition = -1;
        this.aFoundPartition = false;
        this.aContinuationData = false;
        this.parsePartition();
        while (getNextUnit()>0) {
        	getUnitData();
        	TAUnit unit = new TAUnit((int)this.aUnit,this.aUnitDataArray);
        	units.addElement(unit);
        }
    }

    public Vector<TAUnit> entries() {
    	return units;
    }

    public int getPartition() {
        return this.aPartition;
    }


    public boolean overridePartition() {
        return false;
    }


    public long getNextUnit() throws TAFileParseException, IOException {
        this.aUnitDataArray = null;
        this.aContinuationUnitDataList = new ArrayList();
        if (this.aContinuationData) {
            throw new TAFileParseException("Expecting more data from unit :[" + this.aUnit + "] before reading next unit.");
        }
        String string = "";
        while ((string = this.aReader.readLine()) != null) {
            if (this.matchDataLine(string)) {
                return this.aUnit;
            }
            if (this.matchComment(string) || this.matchBlankLine(string)) continue;
            throw new TAFileParseException("Expected unit data line, at line:[" + string + "].");
        }
        return -1;
    }

    public byte[] getUnitData() throws TAFileParseException, IOException {
        while (this.aContinuationData) {
            String string = "";
            string = this.aReader.readLine();
            if (string == null) {
                throw new TAFileParseException("Expected more data for unit:[" + this.aUnit + "].");
            }
            if (this.matchContinuationDataLine(string)) continue;
            throw new TAFileParseException("Expected more data for unit:[" + this.aUnit + "] at line:[" + string + "].");
        }
        return this.aUnitDataArray;
    }

    private int parsePartition() throws TAFileParseException, IOException {
        if (this.aPartition != -1) {
            return this.aPartition;
        }
        String string = "";
        while ((string = this.aReader.readLine()) != null) {
            if (this.matchPartition(string)) {
                return this.aPartition;
            }
            if (this.matchComment(string) || this.matchBlankLine(string)) continue;
            throw new TAFileParseException("Expected partition at line:[" + string + "].");
        }
        throw new TAFileParseException("No partition found in data.");
    }

    private boolean matchComment(String string) {
        Matcher matcher = COMMENT_LINE_PATTERN.matcher((CharSequence)string);
        return matcher.matches();
    }

    private boolean matchPartition(String string) throws TAFileParseException {
        Matcher matcher = PARTITION_LINE_PATTERN.matcher((CharSequence)string);
        if (matcher.matches()) {
            if (this.aFoundPartition) {
                throw new TAFileParseException("Parse exception: Duplicate partitions in data.");
            }
            String string2 = matcher.group();
            this.aPartition = Integer.parseInt(string2, 16);
            if (this.aPartition != 1 && this.aPartition != 2) {
                throw new TAFileParseException("Unexpected partition in data :[" + string2 + "]");
            }
            this.aFoundPartition = true;
            return true;
        }
        return false;
    }

    private boolean matchDataLine(String string) throws TAFileParseException {
        Matcher matcher = DATA_LINE_PATTERN.matcher((CharSequence)string);
        if (matcher.matches()) {
            if (!this.aFoundPartition) {
                throw new TAFileParseException("Parse exception: Expected partition.");
            }
            String string2 = matcher.group(1);
            this.aUnit = Long.parseLong(string2, 16);
            string2 = matcher.group(2);
            this.aUnitSize = Integer.parseInt(string2, 16);
            string2 = matcher.group(3);
            String[] arrstring = string2.trim().split("(?: |\\t)+");
            this.aContinuationUnitDataList = new ArrayList();
            if (arrstring.length < this.aUnitSize) {
                for (int i = 0; i < arrstring.length; ++i) {
                    this.aContinuationUnitDataList.add(arrstring[i]);
                }
                this.aContinuationData = true;
                return true;
            }
            if (arrstring.length > this.aUnitSize) {
                if (this.aUnitSize == 0 && arrstring.length == 1 && arrstring[0].equals("")) {
                    this.aUnitDataArray = new byte[0];
                    return true;
                }
                throw new TAFileParseException("Parse exception: Too much data for unit [" + this.aUnit + "]");
            }
            this.aUnitDataArray = this.asBytes(arrstring);
            return true;
        }
        return false;
    }

    private boolean matchContinuationDataLine(String string) throws TAFileParseException {
        if (!this.aFoundPartition) {
            throw new TAFileParseException("Parse exception: Expected partition.");
        }
        Matcher matcher = CONTINUATION_DATA_LINE_PATTERN.matcher((CharSequence)string);
        if (matcher.matches()) {
            String string2 = matcher.group(1);
            String[] arrstring = string2.trim().split("(?: |\\t)+");
            long l = this.aContinuationUnitDataList.size() + arrstring.length;
            for (int i = 0; i < arrstring.length; ++i) {
                this.aContinuationUnitDataList.add(arrstring[i]);
            }
            if (l < (long)this.aUnitSize) {
                this.aContinuationData = true;
                return true;
            }
            if (l > (long)this.aUnitSize) {
                throw new TAFileParseException("Parse exception: Too much data for unit [" + this.aUnit + "] : " + l);
            }
            this.aUnitDataArray = this.asBytes(this.aContinuationUnitDataList);
            this.aContinuationData = false;
            return true;
        }
        return false;
    }

    private boolean matchBlankLine(String string) {
        Matcher matcher = BLANK_LINE_PATTERN.matcher((CharSequence)string);
        return matcher.matches();
    }

    private byte[] asBytes(String[] arrstring) {
        byte[] arrby = new byte[arrstring.length];
        for (int i = 0; i < arrstring.length; ++i) {
            arrby[i] = (byte)Integer.parseInt(arrstring[i], 16);
        }
        return arrby;
    }

    private byte[] asBytes(Collection<String> collection) {
        byte[] arrby = new byte[collection.size()];
        int n = 0;
        Iterator<String> iterator = collection.iterator();
        while (iterator.hasNext()) {
            arrby[n++] = (byte)Integer.parseInt(iterator.next(), 16);
        }
        return arrby;
    }
}

