/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package org.apache.synapse.mediators.filters;

import org.apache.synapse.Mediator;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseLog;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.mediators.AbstractListMediator;
import org.apache.synapse.mediators.ListMediator;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The filter mediator combines the regex and xpath filtering functionality. If an xpath
 * is set, it is evaluated; else the given regex is evaluated against the source xpath.
 */
public class FilterMediator extends AbstractListMediator implements
    org.apache.synapse.mediators.FilterMediator {

    private SynapseXPath source = null;
    private Pattern regex = null;
    private SynapseXPath xpath = null;
    private ListMediator elseMediator = null;
    private boolean thenElementPresent = false;
    private String thenKey = null;
    private String elseKey = null;

    @Override
    public void init(SynapseEnvironment se) {
        super.init(se);
        if (elseMediator != null) {
            elseMediator.init(se);
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        if (elseMediator != null) {
            elseMediator.destroy();
        }
    }

    /**
     * Executes the list of sub/child mediators, if the filter condition is satisfied
     *
     * @param synCtx the current message
     * @return true if filter condition fails. else returns as per List mediator semantics
     */
    public boolean mediate(MessageContext synCtx) {

        SynapseLog synLog = getLog(synCtx);

        if (synLog.isTraceOrDebugEnabled()) {
            synLog.traceOrDebug("Start : Filter mediator");

            if (synLog.isTraceTraceEnabled()) {
                synLog.traceTrace("Message : " + synCtx.getEnvelope());
            }
        }

        boolean result = false;
        if (test(synCtx)) {

            if (thenKey != null) {

                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug((xpath == null ?
                        "Source : " + source + " against : " + regex.pattern() + " matches" :
                        "XPath expression : "  + xpath + " evaluates to true") +
                        " - executing then sequence with key : " + thenKey);
                }

                Mediator seq = synCtx.getSequence(thenKey);
                if (seq != null) {
                    result = seq.mediate(synCtx);
                } else {
                    handleException("Couldn't find the referred then sequence with key : "
                        + thenKey, synCtx);
                }
                
            } else {

                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug((xpath == null ?
                        "Source : " + source + " against : " + regex.pattern() + " matches" :
                        "XPath expression : "  + xpath + " evaluates to true") +
                        " - executing child mediators");
                }

                result = super.mediate(synCtx);
            }

        } else {

            if (elseKey != null) {

                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug((xpath == null ?
                        "Source : " + source + " against : " + regex.pattern() + " does not match" :
                        "XPath expression : "  + xpath + " evaluates to false") +
                        " - executing the else sequence with key : " + elseKey);
                }

                Mediator elseSeq = synCtx.getSequence(elseKey);

                if (elseSeq != null) {
                    result = elseSeq.mediate(synCtx);
                } else {
                    handleException("Couldn't find the referred else sequence with key : "
                        + elseKey, synCtx);
                }
                
            } else if (elseMediator != null) {

                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug((xpath == null ?
                        "Source : " + source + " against : " + regex.pattern() + " does not match" :
                        "XPath expression : "  + xpath + " evaluates to false") +
                        " - executing the else path child mediators");
                }
                result = elseMediator.mediate(synCtx);

            } else {

                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug((xpath == null ?
                        "Source : " + source + " against : " + regex.pattern() + " does not match" :
                        "XPath expression : "  + xpath + " evaluates to false and no else path") +
                        " - skipping child mediators");
                }
                result = true;
            }
        }

        synLog.traceOrDebug("End : Filter mediator ");
        return result;
    }

    /**
     * Tests the supplied condition after evaluation against the given XPath
     * or Regex (against a source XPath). When a regular expression is supplied
     * the source XPath is evaluated into a String value, and matched against
     * the given regex
     *
     * @param synCtx the current message for evaluation of the test condition
     * @return true if evaluation of the XPath/Regex results in true
     */
    public boolean test(MessageContext synCtx) {

        SynapseLog synLog = getLog(synCtx);

        if (xpath != null) {
            try {
                return xpath.booleanValueOf(synCtx);
            } catch (JaxenException e) {
                handleException("Error evaluating XPath expression : " + xpath, e, synCtx);
            }

        } else if (source != null && regex != null) {
            String sourceString = source.stringValueOf(synCtx);
            if (sourceString == null) {
                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug("Source String : " + source + " evaluates to null");
                }
                return false;
            }
            Matcher matcher = regex.matcher(sourceString);
            if (matcher == null) {
                if (synLog.isTraceOrDebugEnabled()) {
                    synLog.traceOrDebug("Regex pattern matcher for : " + regex.pattern() +
                        "against source : " + sourceString + " is null");
                }
                return false;
            }
            return matcher.matches();
        }

        return false; // never executes
    }


    public SynapseXPath getSource() {
        return source;
    }

    public void setSource(SynapseXPath source) {
        this.source = source;
    }

    public Pattern getRegex() {
        return regex;
    }

    public void setRegex(Pattern regex) {
        this.regex = regex;
    }

    public SynapseXPath getXpath() {
        return xpath;
    }

    public void setXpath(SynapseXPath xpath) {
        this.xpath = xpath;
    }

    public ListMediator getElseMediator() {
        return elseMediator;
    }

    public void setElseMediator(ListMediator elseMediator) {
        this.elseMediator = elseMediator;
    }

    public boolean isThenElementPresent() {
        return thenElementPresent;
    }

    public void setThenElementPresent(boolean thenElementPresent) {
        this.thenElementPresent = thenElementPresent;
    }

    public String getThenKey() {
        return thenKey;
    }

    public void setThenKey(String thenKey) {
        this.thenKey = thenKey;
    }

    public String getElseKey() {
        return elseKey;
    }

    public void setElseKey(String elseKey) {
        this.elseKey = elseKey;
    }

    @Override
    public boolean isContentAware() {
        if (xpath != null) {
            return xpath.isContentAware();
        } else if (source != null) {
            return source.isContentAware();
        }
        return false;
    }
}
