package de.uni_hildesheim.sse.trans.in.rsf;

import java.util.List;

import de.uni_hildesheim.sse.model.cst.ConstraintSyntaxTree;
import de.uni_hildesheim.sse.model.cst.OCLFeatureCall;
import de.uni_hildesheim.sse.model.cst.VariablePool;
import de.uni_hildesheim.sse.model.varModel.datatypes.Enum;
import de.uni_hildesheim.sse.model.varModel.datatypes.OclKeyWords;
import de.uni_hildesheim.sse.trans.in.AbstractReader;
import de.uni_hildesheim.sse.trans.in.ParserException;
import de.uni_hildesheim.sse.trans.in.ParserException.ParserExceptionType;

/**
 * Represents a condition read from a .rsf file.
 * 
 * @author Adam Krafczyk
 */
abstract class RSFCondition {

    protected static VariablePool varPool = new VariablePool();
    
    /**
     * Returns this condition as a pure boolean expression.
     * @param reader The reader to get the variables from.
     * @return A List of {@link ConstraintSyntaxTree}s representing this expression. The parts should be handled as if
     *         combined with AND. empty if expression is optimized out.
     * @throws ParserException If the conditions contains an operation, that is not supported by the parser.
     */
    abstract List<ConstraintSyntaxTree> toPureBooleanConstraintSyntaxTree(RSFReader reader) throws ParserException;
    
    /**
     * Returns this condition as a {@link ConstraintSyntaxTree}.
     * @param reader The reader to get the variables from.
     * @param tristate The Tristate datatype.
     * @return A {@link ConstraintSyntaxTree} representing this condition. <code>null</code> if condition is optimized
     *         out.
     * @throws ParserException If the conditions contains an operation, that is not supported by the parser.
     */
    abstract ConstraintSyntaxTree toNotBooleanConstraintSyntaxTree(AbstractReader reader, Enum tristate)
        throws ParserException;
    
    /**
     * Converts the given variable from a string to a {@link ConstraintSyntaxTree}.
     * A ! at the beginning will result in a {@link OCLFeatureCall} with NOT.
     * @param reader The reader to read variable declarations from.
     * @param var The string representing a variable.
     * @param originalVariable The variable that depends on the condition (used to handle 'm').
     * @return The variable as {@link ConstraintSyntaxTree}.
     */
    private ConstraintSyntaxTree getVariable(AbstractReader reader, String var, String originalVariable) {
        ConstraintSyntaxTree result = null;
        
        if (var.startsWith("!")) {
            var = var.substring(1);
            ConstraintSyntaxTree variable = getVariable(reader, var, originalVariable);
            result = new OCLFeatureCall(variable, OclKeyWords.NOT);
        }
        
        if (result == null) {
            result = handleConstantInVariable(reader, var, originalVariable);
        }
        
        if (result == null) {
            result = handleModuleInVariable(reader, var, originalVariable);
        }
        
        if (result == null) {
            result = handleStringInVariable(reader, var, originalVariable);
        }
        
        if (result == null) {
            // Remove ' around variable names
            if (var.startsWith("'")) {
                var = var.substring(1);
            }
            if (var.endsWith("'")) {
                var = var.substring(0, var.length() - 1);
            }
            result = varPool.obtainVariable(reader.getVariable(var));
        }
        return result;
    }
    
    /**
     * Handles constants (='y' etc.) in a variable.
     * @param reader The reader to read variable declarations from.
     * @param var The string representing a variable.
     * @param originalVariable The variable that depends on the condition.
     * @return A {@link ConstraintSyntaxTree} if the variable contained a constant expression; <code>null</code>
     *         otherwise
     */
    private ConstraintSyntaxTree handleConstantInVariable(AbstractReader reader, String var, String originalVariable) {
        ConstraintSyntaxTree result = null;
        if (var.endsWith("!='y'")) {
            var = var.substring(0, var.length() - "!='y'".length());
            result = getVariable(reader, "!" + var, originalVariable);
            
        } else if (var.endsWith("='y'")) {
            var = var.substring(0, var.length() - "='y'".length());
            result = getVariable(reader, var, originalVariable);
            
        } else if (var.endsWith("!='n'")) {
            var = var.substring(0, var.length() - "!='n'".length());
            result = getVariable(reader, var, originalVariable);
            
        } else if (var.endsWith("='n'")) {
            var = var.substring(0, var.length() - "='n'".length());
            result = getVariable(reader, "!" + var, originalVariable);
            
        }
        return result;
    }
    
    /**
     * Handles modules (='m', 'm' etc.) in a variable.
     * @param reader The reader to read variable declarations from.
     * @param var The string representing a variable.
     * @param originalVariable The variable that depends on the condition.
     * @return A {@link ConstraintSyntaxTree} if the variable contained a module expression; <code>null</code>
     *         otherwise
     */
    private ConstraintSyntaxTree handleModuleInVariable(AbstractReader reader, String var, String originalVariable) {
        ConstraintSyntaxTree result = null;
        if (var.endsWith("!='m'")) {
            var = var.substring(0, var.length() - "!='m'".length());
            result = getVariable(reader, "!" + var + "_MODULE", originalVariable);
            
        } else if (var.endsWith("='m'")) {
            var = var.substring(0, var.length() - "='m'".length());
            result = getVariable(reader, var + "_MODULE", originalVariable);
            
        } else if (var.equals("'m'")) {
            result = getVariable(reader, originalVariable + "_MODULE", originalVariable);
        }
        return result;
    }
    
    /**
     * Handles strings (='Foo', !='' etc.) in a variable.
     * @param reader The reader to read variable declarations from.
     * @param var The string representing a variable.
     * @param originalVariable The variable that depends on the condition.
     * @return A {@link ConstraintSyntaxTree} if the variable contained a string comparison; <code>null</code>
     *         otherwise
     */
    private ConstraintSyntaxTree handleStringInVariable(AbstractReader reader, String var, String originalVariable) {
        ConstraintSyntaxTree result = null;
        if (var.contains("!=''")) {
            int index = var.indexOf("!=''");
            var = var.substring(0, index);
            
            result = getVariable(reader, var, originalVariable);
            
        } else if (var.contains("=''")) {
            int index = var.indexOf("!=''");
            var = var.substring(0, index);
            
            result = getVariable(reader, "!" + var, originalVariable);
            
        } else if (var.contains("!='")) {
            StringBuffer buffer = new StringBuffer(var);
            int index;
            while ((index = buffer.indexOf("'")) != -1) {
                buffer.deleteCharAt(index);
            }
            
            // move the ! to the beginning
            index = buffer.indexOf("!=");
            buffer.deleteCharAt(index);
            
            result = getVariable(reader, "!" + buffer.toString(), originalVariable);
            
        } else if (var.contains("='")) {
            StringBuffer buffer = new StringBuffer(var);
            int index;
            while ((index = buffer.indexOf("'")) != -1) {
                buffer.deleteCharAt(index);
            }
            result = getVariable(reader, buffer.toString(), originalVariable);
        }
        return result;
    }
    
    /**
     * Converts the condition into a pure boolean {@link ConstraintSyntaxTree}.
     * @param reader The reader to get the variables from.
     * @param condition A string representing the condition as read from a .rsf file.
     * @param variable The variable that depends on the condition (used to handle 'm').
     * @return A {@link ConstraintSyntaxTree} representing the condition. <code>null</code> if always true.
     * @throws ParserException If the conditions contains an operation, that is not supported by the parser.
     */
    protected ConstraintSyntaxTree getPureBooleanConstraintSyntaxTree(AbstractReader reader, String condition,
            String variable) throws ParserException {
        
        condition = condition.replace(" ", "");
        
        // We assume that ! can only be in front of variables
        
        String highestOperand = null;
        int highestOperandLevel = Integer.MAX_VALUE;
        int highestOperandPos = -1;
        int currentLevel = 0;
        
        for (int i = 0; i < condition.length(); i++) {
            char c = condition.charAt(i);
            switch (c) {
            case '(':
                currentLevel++;
                break;
            case ')':
                currentLevel--;
                break;
                
            case '&':
                if (condition.charAt(i + 1) != '&') {
                    throw new ParserException(ParserExceptionType.NOT_SUPPORTED_FIELD); // TODO
                }
                if (highestOperandLevel > currentLevel) {
                    highestOperand = OclKeyWords.AND;
                    highestOperandPos = i;
                    highestOperandLevel = currentLevel;
                }
                i++;
                break;
            
            case '|':
                if (condition.charAt(i + 1) != '|') {
                    throw new ParserException(ParserExceptionType.NOT_SUPPORTED_FIELD); // TODO
                }
                if (highestOperandLevel > currentLevel) {
                    highestOperand = OclKeyWords.OR;
                    highestOperandPos = i;
                    highestOperandLevel = currentLevel;
                }
                i++;
                break;
            
            default:
                break;
            }
        }
        
        ConstraintSyntaxTree result = null;
        
        if (highestOperandPos == -1) {
            if (!condition.equals("y")) {
                // we only have a variable
                result = getVariable(reader, condition, variable);
            }
            // result = null if condition is "y"
        } else {
            StringBuffer left = new StringBuffer();
            StringBuffer right = new StringBuffer();
            split(condition, highestOperandPos, highestOperandLevel, left, right);
            result = new OCLFeatureCall(getPureBooleanConstraintSyntaxTree(reader, left.toString(), variable),
                    highestOperand, getPureBooleanConstraintSyntaxTree(reader, right.toString(), variable));
        }
        
        return result;
    }
    
    /**
     * Converts the condition into a {@link ConstraintSyntaxTree}.
     * @param reader The reader to get the variables from.
     * @param condition A string representing the condition as read from a .rsf file.
     * @param variable The variable that depends on the condition (used to handle 'm').
     * @return A {@link ConstraintSyntaxTree} representing the condition. <code>null</code> if always true.
     * @throws ParserException If the conditions contains an operation, that is not supported by the parser.
     */
    protected ConstraintSyntaxTree getNotBooleanConstraintSyntaxTree(AbstractReader reader, String condition,
            String variable) throws ParserException {
        // TODO implement
        return null;
    }
    
    /**
     * Splits the given line at the given operand position into a left and right part.
     * @param line The whole line
     * @param operandPos The position of the operand in the line
     * @param operandLevel The logic level of the operand (by counting brackets)
     * @param left A {@link StringBuffer} where the left side of the operand will be written to
     * @param right A {@link StringBuffer} where the right side of the operand will be written to
     */
    private void split(String line, int operandPos, int operandLevel, StringBuffer left, StringBuffer right) {
        left.append(line.substring(0, operandPos));
        right.append(line.substring(operandPos + 2, line.length()));
        int currentLevel = 0;
        for (int i = 0; i < left.length(); i++) {
            char c = left.charAt(i);
            if ((c == '(' || c == ')') && currentLevel < operandLevel) {
                left.replace(i, i + 1, "");
            }
            if (c == '(') {
                currentLevel++;
            }
            if (c == ')') {
                currentLevel--;
            }
        }
        currentLevel = 0;
        for (int i = right.length() - 1; i >= 0; i--) {
            char c = right.charAt(i);
            if ((c == '(' || c == ')') && currentLevel < operandLevel) {
                right.replace(i, i + 1, "");
            }
            if (c == ')') {
                currentLevel++;
            }
            if (c == '(') {
                currentLevel--;
            }
        }
    }
    
}
