

package cz.karry.dop.parser;

import cz.karry.dop.core.Features;
import cz.karry.dop.Logger;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author karry
 */
public class Parser {
    private String[] source;
    private ArrayList<Block> blocks = new ArrayList<Block>();
    private MInstruction[] memory = new MInstruction[Features.MEMORY_SIZE];

    public Parser(String source) {
        this.source = source.split("\n");
    }


    public void run() throws SyntaxError, MemorySizeError {
        this.parseBlocks();
        this.findDuplicateLabels();
        this.checkBlocks();
        this.tryFillMemory();
        this.setNextAddress();
    }

    private void checkBlocks() throws SyntaxError{
        MInstruction prevInstr = null;
        String firstLabel = null;
        for (int block=0;block<blocks.size();block++){
            for (int instr=0; instr<blocks.get(block).instructions.size(); instr++){
                
                MInstruction instruction = blocks.get(block).instructions.get(instr);
                if (firstLabel==null)
                    firstLabel = instruction.getLabel();
                
                if (prevInstr!=null){
                    if (prevInstr.getNextLabel()==null)
                        prevInstr.setNextLabel(instruction.getLabel());
                    else
                        prevInstr.setNextLabel("u_"+prevInstr.getNextLabel());
                    
                    this.checkInstructionLabels(prevInstr);
                }
                
                prevInstr = instruction;
            }
        }
        
        if (prevInstr!=null){
            if (prevInstr.getNextLabel()==null)
                prevInstr.setNextLabel(firstLabel);
            else
                prevInstr.setNextLabel("u_"+prevInstr.getNextLabel());

            this.checkInstructionLabels(prevInstr);
        }else{
            throw new SyntaxError("No instructions defined?!",0);
        }
    }
    
    private void checkInstructionLabels(MInstruction instr) throws SyntaxError {
        for (int block=0;block<blocks.size();block++){
            if (instr.getConditionCode()>0){
                if (blocks.get(block).getFirstLabel().equals(instr.getNextLabel())){
                    
                    blocks.get(block).requiredLength( Features.REQUIRED_CONDITION_LENGTH[instr.getConditionCode()], instr.getSrcLine());

                    return;
                }
            }else{
                for (int i=0;i< blocks.get(block).instructions.size();i++){
                    if (blocks.get(block).instructions.get(i).getLabel().equals(instr.getNextLabel())){
                        return;
                    }   
                }
            }
        }
        throw new SyntaxError("Undefined label "+instr.getNextLabel().substring(2)+" on line "+instr.getSrcLine()+"!",instr.getSrcLine());
    }

    private void findDuplicateLabels() throws SyntaxError {
        for (int block=0;block<blocks.size();block++){
            for (int i=0;i< blocks.get(block).instructions.size();i++){
                String findLabel = blocks.get(block).instructions.get(i).getLabel();
                for (int block2=block;block2<blocks.size();block2++){
                    for (int i2=i;i2< blocks.get(block2).instructions.size();i2++){
                        if ( !((i==i2) && (block==block2)) && (findLabel.equals(blocks.get(block2).instructions.get(i2).getLabel() )))
                            throw new SyntaxError("Duplicit block label "+findLabel.substring(2)+" on line "+blocks.get(block).instructions.get(i).getSrcLine()+" and "+blocks.get(block2).instructions.get(i2).getSrcLine()+"!", blocks.get(block2).instructions.get(i2).getSrcLine());
                    }
                }
            }
        }
    }

    private int findFirsUnusedMemory(MInstruction[] memory, int requiredLength) throws MemorySizeError{
        int free=0;
        int first=-1;
        for (int addr=0; addr < memory.length; addr++){
            if (memory[addr]==null){
                if (first==-1){
                    if (addr % requiredLength == 0){
                        first=addr;
                        free=1;
                    }else{
                        first=-1;
                        free=0;                        
                    }
                }else{
                    free++;
                }
                if (free==requiredLength){
                    return first;
                }
            }else{
                first=-1;
                free=0;
            }
        }
        throw new MemorySizeError("Memory is too small!");
    }
    
    private int getLabelAddress(String label){
        for (int block=0;block<blocks.size();block++){
            for (int i=0;i< blocks.get(block).instructions.size();i++){
                if (blocks.get(block).instructions.get(i).getLabel().equals(label)){
                    return blocks.get(block).instructions.get(i).getAddress();
                }   
            }
        }
        return 0;
    }

    private void setNextAddress() {
        for (int block=0;block<this.blocks.size();block++){
            for (int instr=0; instr<this.blocks.get(block).instructions.size();instr++){
                this.blocks.get(block).instructions.get(instr).setNextAddress(this.getLabelAddress( this.blocks.get(block).instructions.get(instr).getNextLabel() ) );
            }   
        }
    }
    
    private void tryFillMemory() throws MemorySizeError{
        System.err.println("Method "+this.getClass().getName()+".tryFillMemory() need optimisation!");

        for (int block=0;block<this.blocks.size();block++){
            int addr = this.findFirsUnusedMemory(this.memory,this.blocks.get(block).getRequiredLength());
            int instr=0;
            for (instr=0; instr<this.blocks.get(block).getRequiredLength();instr++){
                this.blocks.get(block).instructions.get(instr).setAddress(addr);
                this.memory[addr] = this.blocks.get(block).instructions.get(instr);
                addr++;
            }
            for (; instr<this.blocks.get(block).instructions.size();instr++){
                addr = this.findFirsUnusedMemory(this.memory,1);
                this.blocks.get(block).instructions.get(instr).setAddress( addr );
                this.memory[addr] = this.blocks.get(block).instructions.get(instr);
            }
        }
    }    

    private void parseBlocks() throws SyntaxError {
        Block block=null;
        String label=null;
        
        Pattern emptyLine    = Pattern.compile( "(^[\\s]*$)|(^[\\s]*;.*$)" );    // empty or comment Line
        Pattern startBlockLine = Pattern.compile( "(^[\\s]*[\\w]*:[\\s]*\\{[\\s]*$)|(^[\\s]*\\{[\\s]*$)" );
        Pattern endBlockLine = Pattern.compile( "^[\\s]*\\}[\\s]*$" );
        Pattern instrLine    = Pattern.compile( "^[^,;]*\\,[\\s]*[0-9]+[\\s]*\\,[\\s]*[0-9]+[\\s]*,?[\\s]*[\\w]*[\\s]*;.*$" );
        
        Pattern withLabel    = Pattern.compile( "[\\w]*:[^;]*;.*" );
        Pattern blockLabel   = Pattern.compile( "[\\w]*:" );
        
        for (int i=0; i<this.source.length; i++){
            if (!emptyLine.matcher(this.source[i]).find()){
                
                if (startBlockLine.matcher(this.source[i]).find()){
                    if (block != null){
                        throw new SyntaxError("Nested block on line "+(i+1)+"!",i+1);
                    }else{
                        Matcher m = blockLabel.matcher(this.source[i]);
                        if (m.find()){
                            label = "u_"+this.source[i].substring(m.start(), m.end()-1);
                        }                        
                        block = new Block();
                        blocks.add(block);
                    }
                }else if (endBlockLine.matcher(this.source[i]).find()){
                    if (block != null) block = null;
                    else throw new SyntaxError("Find block end, but neither is open! (Error on line "+(i+1)+")",i+1);                    
                }else if (instrLine.matcher(this.source[i]).find()){
                    if (withLabel.matcher(this.source[i]).find()){
                        if (block != null){
                            throw new SyntaxError("Nested block on line "+(i+1)+"!",i+1);
                        }else{
                            Matcher m = blockLabel.matcher(this.source[i]);
                            if (m.find()){
                                label = "u_"+this.source[i].substring(m.start(), m.end()-1);
                            }                                                  
                        }
                    }
                    
                    Block actualBlock;
                    if (block==null){
                        actualBlock = new Block();
                        blocks.add(actualBlock);                          
                    }else
                        actualBlock = block;
                    
                    if (label==null)
                        label= "l_"+(i+1);
                    actualBlock.addMInstruction( new MInstruction(i+1, this.source[i], label));
                    label = null;
                }else{
                    throw new SyntaxError("Bad syntax on line "+(i+1)+"! (Missing \";\"?)",i+1);
                }
                
            }
        }
    }

    public void printMemory(){
        printMemory(null);
    }

    public void printMemory(cz.karry.dop.Logger logger) {
        for (int i=0;i<memory.length;i++){
            if (memory[i]==null){
                if (logger==null){
                    System.out.println(i+": ---------------------------------------------");
                }else{
                    logger.addMessage(i+": ---------------------------------------------");
                }                    
            }else
                memory[i].print(logger);
        }
    }
    
    public MInstruction[] getMemory(){
        return memory;
    }
}
