package de.ugoe.cs.quest.plugin.mfc;

import java.util.ArrayList;
import java.util.List;

import org.jdom.Element;
import org.jdom.Namespace;

import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowsMessageType;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @version $Revision: $ $Date: 22.08.2012$
 * @author 2012, last modified by $Author: patrick$
 */
class EventGenerationRule {

    /**
     * <p>
     * the namespace used for parsing the rule
     * </p>
     */
    private Namespace namespace;

    /**
     * <p>
     * the name of the rule
     * </p>
     */
    private String name;

    /**
     * <p>
     * the list of conditions for the rule to be matched
     * </p>
     */
    private List<MessageCondition> messageConditions;

    /**
     * <p>
     * the list of parameters to be provided to the generated event
     * </p>
     */
    private List<Term> eventParameters;

    /**
     * <p>
     * the list of replay message generation rules
     * </p>
     */
    private List<ReplayMessageSpec> replayMessageSpecifications;

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param ruleElement
     * @param rulesNamespace 
     */
    @SuppressWarnings("unchecked")
    EventGenerationRule(Element ruleElement, Namespace rulesNamespace) {
        this.namespace = rulesNamespace;
        
        this.name = ruleElement.getAttributeValue("name");
        
        this.messageConditions = new ArrayList<MessageCondition>();
        this.eventParameters = new ArrayList<Term>();
        this.replayMessageSpecifications = new ArrayList<ReplayMessageSpec>();
        
        for (Element child : (List<Element>) ruleElement.getChildren()) {
            if ("msg".equals(child.getName()) && namespace.equals(child.getNamespace())) {
                messageConditions.add(new MessageCondition(child));
            }
            else if ("idinfo".equals(child.getName()) && namespace.equals(child.getNamespace())) {
                for (Element parameterElements : (List<Element>) child.getChildren()) {
                    eventParameters.add(new Term(parameterElements));
                }
            }
            else if ("genMsg".equals(child.getName()) && namespace.equals(child.getNamespace())) {
                replayMessageSpecifications.add(new ReplayMessageSpec(child));
            }
            else if ("genMsgSeq".equals(child.getName()) && namespace.equals(child.getNamespace())) {
                replayMessageSpecifications.add(new ReplayMessageSpec(child));
            }
            else {
                throw new IllegalArgumentException
                    ("the provided rules can not be parsed: unknown element " + child.getName());
            }
        }
    }

    /**
     * @return the name
     */
    String getName() {
        return name;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @return
     */
    List<MessageCondition> getMessageConditions() {
        return messageConditions;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @return
     */
    List<Term> getEventParameters() {
        return eventParameters;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @return
     */
    List<ReplayMessageSpec> getReplayMessageSpecifications() {
        return replayMessageSpecifications;
    }

    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 22.08.2012$
     * @author 2012, last modified by $Author: patrick$
     */
    class MessageCondition {

        /**
         * <p>
         * true, if the condition defines to match several conditions
         * </p>
         */
        private boolean matchMultiple;
        
        /**
         * <p>
         * the type of the message matched by the condition
         * </p>
         */
        private WindowsMessageType messageType;

        /**
         * <p>
         * the term conditions associate with the rule condition
         * </p>
         */
        private List<AttributeCondition> attributeConditions;

        /**
         * <p>
         * the list of messages to be stored, if the message matches, for continuing the
         * rule application
         * </p>
         */
        private ArrayList<Term> messagesToStore;

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param msgChild
         */
        @SuppressWarnings("unchecked")
        private MessageCondition(Element msgChild) {
            this.matchMultiple = "true".equals(msgChild.getAttributeValue("multiple"));
            this.messageType =
                WindowsMessageType.parseMessageType(msgChild.getAttributeValue("type"));
            
            this.attributeConditions = new ArrayList<AttributeCondition>();
            for (Element childElement : (List<Element>) msgChild.getChildren("equals", namespace)) {
                attributeConditions.add(new AttributeCondition(childElement));
            }
            
            this.messagesToStore = new ArrayList<Term>();
            for (Element childElement : (List<Element>) msgChild.getChildren("store", namespace)) {
                messagesToStore.add(new Term(childElement));
            }
            for (Element childElement :
                 (List<Element>) msgChild.getChildren("storeSeq", namespace))
            {
                messagesToStore.add(new Term(childElement));
            }
        }

        /**
         * @return the matchMultiple
         */
        boolean matchMultiple() {
            return matchMultiple;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        WindowsMessageType getMessageType() {
            return messageType;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        List<AttributeCondition> getAttributeConditions() {
            return attributeConditions;
        }

        /**
         * @return the valuesToStore
         */
        ArrayList<Term> getMessagesToStore() {
            return messagesToStore;
        }

    }

    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 22.08.2012$
     * @author 2012, last modified by $Author: patrick$
     */
    class AttributeCondition {

        /**
         * <p>
         * the left hand side of the condition
         * </p>
         */
        private Term leftHandSide;
        
        /**
         * <p>
         * the left hand side of the condition
         * </p>
         */
        private Term rightHandSide;

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param childElement
         */
        private AttributeCondition(Element conditionElement) {
            this.leftHandSide = new Term((Element) conditionElement.getChildren().get(0));
            this.rightHandSide = new Term((Element) conditionElement.getChildren().get(1));
        }
        
        /**
         * @return the leftHandSide
         */
        Term getLeftHandSide() {
            return leftHandSide;
        }

        /**
         * @return the rightHandSide
         */
        Term getRightHandSide() {
            return rightHandSide;
        }

    }

    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 22.08.2012$
     * @author 2012, last modified by $Author: patrick$
     */
    class Term {

        /**
         * <p>
         * the name of the term
         * </p>
         */
        private String name;
        
        /**
         * <p>
         * the value of the term, if it is a constValue, null instead
         * </p>
         */
        private String value;

        /**
         * <p>
         * the variable name of the object, i.e. a message, of which a parameter is identified if
         * the term is a winInfoValue or a msgInfoValue; null instead
         * </p>
         */
        private String messageId;

        /**
         * <p>
         * the name of the parameter of the object, e.g. a message, of which a parameter is
         * identified if the term is a paramValue, null instead
         * </p>
         */
        private String messageParameterName;

        /**
         * <p>
         * the variable name of the message sequence denoted by the term in case of a seqValue;
         * null instead
         * </p>
         */
        private String sequenceId;

        /**
         * <p>
         * the name of the parameter of the sequence of which a parameter is
         * identified if the term is a seqValue, null instead
         * </p>
         */
        private String sequenceParameterName;

        /**
         * <p>
         * the name of the parameter of the window of the object, e.g. a message, of which a
         * parameter is identified if the term is a winInfoValue, null instead
         * </p>
         */
        private String windowParameterName;

        /**
         * <p>
         * the name of the info of the message of which a parameter is identified if the
         * term is a msgInfoValue, null instead
         * </p>
         */
        private String messageInfoName;

        /**
         * <p>
         * the name of the parameter of the message into which a value shall be stored if the
         * term is a resolveHwnd, null instead
         * </p>
         */
        private String storeParameterName;

        /**
         * <p>
         * the list of handles to be resolved in case the term is a store or storeSeq, null instead
         * </p>
         */
        private List<Term> resolveHandles;

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param object
         */
        @SuppressWarnings("unchecked")
        private Term(Element termElement) {
            this.name = termElement.getName();
            
            if ("constValue".equals(name)) {
                this.value = termElement.getAttributeValue("value");
            }
            else if ("paramValue".equals(name)) {
                this.messageId = termElement.getAttributeValue("obj");
                this.messageParameterName = termElement.getAttributeValue("param");
            }
            else if ("winInfoValue".equals(name)) {
                this.messageId = termElement.getAttributeValue("obj");
                this.windowParameterName = termElement.getAttributeValue("winParam");
            }
            else if ("msgInfoValue".equals(name)) {
                this.messageId = termElement.getAttributeValue("obj");
                this.messageInfoName = termElement.getAttributeValue("msgParam");
            }
            else if ("seqValue".equals(name)) {
                this.sequenceId = termElement.getAttributeValue("seqObj");
                this.sequenceParameterName = termElement.getAttributeValue("param");
            }
            else if ("store".equals(name)) {
                this.messageId = termElement.getAttributeValue("var");
                if ((termElement.getChildren() != null) && (termElement.getChildren().size() > 0)) {
                    this.resolveHandles = new ArrayList<Term>();
                    for (Element child : (List<Element>) termElement.getChildren()) {
                        this.resolveHandles.add(new Term(child));
                    }
                }
            }
            else if ("storeSeq".equals(name)) {
                this.sequenceId = termElement.getAttributeValue("varSeq");
                if ((termElement.getChildren() != null) && (termElement.getChildren().size() > 0)) {
                    this.resolveHandles = new ArrayList<Term>();
                    for (Element child : (List<Element>) termElement.getChildren()) {
                        this.resolveHandles.add(new Term(child));
                    }
                }
            }
            else if ("resolveHwnd".equals(name)) {
                this.messageParameterName = termElement.getAttributeValue("param");
                this.storeParameterName = termElement.getAttributeValue("storeParam");
            }
        }

        /**
         * @return the name
         */
        String getName() {
            return name;
        }

        /**
         * @return the value
         */
        String getValue() {
            return value;
        }

        /**
         * @return the object
         */
        String getMessageId() {
            return messageId;
        }

        /**
         * @return the objectParameter
         */
        String getMessageParameterName() {
            return messageParameterName;
        }

        /**
         * @return the sequenceId
         */
        String getSequenceId() {
            return sequenceId;
        }

        /**
         * @return the sequenceParameter
         */
        String getSequenceParameterName() {
            return sequenceParameterName;
        }

        /**
         * @return the windowParameter
         */
        String getWindowParameterName() {
            return windowParameterName;
        }

        /**
         * @return the messageParameter
         */
        String getMessageInfoName() {
            return messageInfoName;
        }

        /**
         * @return the storeParameter
         */
        String getStoreParameterName() {
            return storeParameterName;
        }

        /**
         * @return the resolveHandles
         */
        List<Term> getResolveHandles() {
            return resolveHandles;
        }

    }
    
    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 22.08.2012$
     * @author 2012, last modified by $Author: patrick$
     */
    class ReplayMessageSpec {

        /**
         * <p>
         * determines, if this specification defines one, or a sequence of messages
         * </p>
         */
        private boolean generateSingleMessage;
        
        /**
         * <p>
         * the id of a concrete message of message sequence to be replayed as is
         * </p>
         */
        private String replayObjectId;

        private Term type;

        private Term target;

        private Term lparamLoWord;

        private Term lparamHiWord;

        private Term lparam;

        private Term wparamLoWord;

        private Term wparamHiWord;

        private Term wparam;

        private int delay;
        
        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param child
         */
        @SuppressWarnings("unchecked")
        private ReplayMessageSpec(Element replayMessageSpecElement) {
            List<Element> children = replayMessageSpecElement.getChildren();
            if ("genMsg".equals(replayMessageSpecElement.getName())) {
                generateSingleMessage = true;
                if (children.size() == 1) {
                    replayObjectId = children.get(0).getAttributeValue("obj");
                }
            }
            else {
                generateSingleMessage = false;
                if (children.size() == 1) {
                    replayObjectId = children.get(0).getAttributeValue("seqObj");
                }
            }
            
            this.delay = Integer.parseInt(replayMessageSpecElement.getAttributeValue("delay"));
            
            if (children.size() > 1) {
                for (Element child : children) {
                    Element termElement = (Element) child.getChildren().get(0);
                    
                    if (child.getName().equals("type")) {
                        this.type = new Term(termElement);
                    }
                    else if (child.getName().equals("target")) {
                        this.target = new Term(termElement);
                        
                        if (!generateSingleMessage) {
                            // in this case, the target is always a sequence value term, i.e.
                            // the targets of the originally recorded sequence. So the
                            // replay object id is set to this sequence
                            replayObjectId = target.getSequenceId();
                        }
                    }
                    else if (child.getName().equals("LPARAM")) {
                        Element loWordElement = child.getChild("LOWORD", namespace);
                        if (loWordElement != null) {
                            this.lparamLoWord =
                                new Term((Element) loWordElement.getChildren().get(0));
                        }
                        
                        Element hiWordElement = child.getChild("HIWORD", namespace);
                        if (hiWordElement != null) {
                            this.lparamHiWord =
                                 new Term((Element) hiWordElement.getChildren().get(0));
                        }
                        
                        if ((lparamLoWord == null) && (lparamHiWord == null)) {
                            this.lparam = new Term(termElement);
                        }
                    }
                    else if (child.getName().equals("WPARAM")) {
                        Element loWordElement = child.getChild("LOWORD", namespace);
                        if (loWordElement != null) {
                            this.wparamLoWord =
                                new Term((Element) loWordElement.getChildren().get(0));
                        }
                        
                        Element hiWordElement = child.getChild("HIWORD", namespace);
                        if (hiWordElement != null) {
                            this.wparamHiWord =
                                 new Term((Element) hiWordElement.getChildren().get(0));
                        }
                        
                        if ((wparamLoWord == null) && (wparamHiWord == null)) {
                            this.wparam = new Term(termElement);
                        }
                    }
                }
            }
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        boolean generateSingleMessage() {
            return generateSingleMessage;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @return
         */
        String getReplayObjectId() {
            return replayObjectId;
        }

        /**
         * @return the type
         */
        Term getType() {
            return type;
        }

        /**
         * @return the target
         */
        Term getTarget() {
            return target;
        }

        /**
         * @return the lparamLoWord
         */
        Term getLparamLoWord() {
            return lparamLoWord;
        }

        /**
         * @return the lparamHiWord
         */
        Term getLparamHiWord() {
            return lparamHiWord;
        }

        /**
         * @return the lparam
         */
        Term getLparam() {
            return lparam;
        }

        /**
         * @return the wparamLoWord
         */
        Term getWparamLoWord() {
            return wparamLoWord;
        }

        /**
         * @return the wparamHiWord
         */
        Term getWparamHiWord() {
            return wparamHiWord;
        }

        /**
         * @return the wparam
         */
        Term getWparam() {
            return wparam;
        }

        /**
         * @return the delay
         */
        int getDelay() {
            return delay;
        }

    }
}
