// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package org.dogtagpki.legacy.core.policy;

import java.util.Enumeration;
import java.util.Vector;

import org.dogtagpki.legacy.policy.EPolicyException;
import org.dogtagpki.legacy.policy.IExpression;

import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.request.IRequest;
import com.netscape.cmscore.util.AssertionException;
import com.netscape.cmscore.util.Debug;

/**
 * This class represents an expression of the form var = val,
 * var != val, var < val, var > val, var <= val, var >= val.
 *
 * Expressions are used as predicates for policy selection.
 *
 * @author kanda
 * @version $Revision$, $Date$
 */
public class SimpleExpression implements IExpression {
    private String mPfx;
    private String mVar;
    private String mVal;
    private String mPartialMatch;
    private int mOp;
    private boolean hasWildCard;
    public static final char WILDCARD_CHAR = '*';

    // This is just for indicating a null expression.
    public static SimpleExpression NULL_EXPRESSION = new SimpleExpression("null", OP_EQUAL, "null");

    public static IExpression parse(String input)
            throws EPolicyException {
        // Get the index of operator
        // Debug.trace("SimpleExpression::input: " + input);
        String var = null;
        int op = -1;
        String val = null;

        // XXX - Kanda - Need to change this parsing code eventually.
        ExpressionComps comps = parseForEquality(input);

        if (comps == null)
            comps = parseForInEquality(input);
        if (comps == null)
            comps = parseForGE(input);
        if (comps == null)
            comps = parseForLE(input);
        if (comps == null)
            comps = parseForGT(input);
        if (comps == null)
            comps = parseForLT(input);
        if (comps == null)
            throw new EPolicyException(CMS.getUserMessage("CMS_POLICY_BAD_POLICY_EXPRESSION", input));
        String pfx = null;
        String rawVar = comps.getAttr();
        int dotIdx = rawVar.indexOf('.');

        if (dotIdx != -1) {
            pfx = rawVar.substring(0, dotIdx).trim();
            var = rawVar.substring(dotIdx + 1).trim();
        } else {
            var = rawVar;
        }
        op = comps.getOp();
        val = comps.getVal();
        return new SimpleExpression(pfx, var, op, val);
    }

    public SimpleExpression(String var, int op, String val) {
        this(null, var, op, val);
    }

    public SimpleExpression(String prefix, String var, int op, String val) {
        // Assert that op has to be either IExpression.OP_EQUAL or
        // IExpression.OP_NEQUAL.
        // If val or var is null throw an exception!
        mPfx = prefix;
        mVar = var;
        mOp = op;
        mVal = val;
        int firstIndex;

        if ((firstIndex = mVal.indexOf(WILDCARD_CHAR)) >= 0) {
            hasWildCard = true;
            int nextIndex = mVal.indexOf(WILDCARD_CHAR, firstIndex + 1);

            if (nextIndex == -1) {
                if (firstIndex == 0)
                    mPartialMatch = mVal.substring(1);
                else
                    mPartialMatch = mVal.substring(0, firstIndex);
            } else
                mPartialMatch = mVal.substring(firstIndex + 1, nextIndex);
        } else
            hasWildCard = false;
    }

    public boolean evaluate(IRequest req)
            throws EPolicyException {
        // mPfx and mVar are looked up case-indendently
        String givenVal = req.getExtDataInString(mPfx, mVar);

        if (Debug.ON)
            Debug.trace("mPfx: " + mPfx + " mVar: " + mVar +
                    ",Given Value: " + givenVal + ", Value to compare with: " + mVal);

        return matchValue(givenVal);
    }

    private boolean matchVector(Vector<?> value)
            throws EPolicyException {
        boolean result = false;
        Enumeration<?> e = value.elements();

        for (; e.hasMoreElements();) {
            result = matchValue(e.nextElement());
            if (result)
                break;
        }
        return result;
    }

    private boolean matchStringArray(String[] value)
            throws EPolicyException {
        boolean result = false;

        for (int i = 0; i < value.length; i++) {
            result = matchValue(value[i]);
            if (result)
                break;
        }
        return result;
    }

    private boolean matchValue(Object value)
            throws EPolicyException {
        boolean result;

        // There is nothing to compare with!
        if (value == null)
            return false;

        // XXX - Kanda: We need a better way of handling this!.
        if (value instanceof String)
            result = matchStringValue((String) value);
        else if (value instanceof Integer)
            result = matchIntegerValue((Integer) value);
        else if (value instanceof Boolean)
            result = matchBooleanValue((Boolean) value);
        else if (value instanceof Vector)
            result = matchVector((Vector<?>) value);
        else if (value instanceof String[])
            result = matchStringArray((String[]) value);
        else
            throw new EPolicyException(CMS.getUserMessage("CMS_POLICY_INVALID_ATTR_VALUE",
                        value.getClass().getName()));
        return result;
    }

    private boolean matchStringValue(String givenVal)
            throws EPolicyException {
        boolean result;

        switch (mOp) {
        case OP_EQUAL:
            if (hasWildCard)
                result = (givenVal.indexOf(mPartialMatch) >= 0);
            else
                result = givenVal.equalsIgnoreCase(mVal);
            break;

        case OP_NEQUAL:
            if (hasWildCard)
                result = (givenVal.indexOf(mPartialMatch) < 0);
            else
                result = !givenVal.equalsIgnoreCase(mVal);
            break;

        case OP_LT:
            result = (givenVal.compareTo(mVal) < 0);
            break;

        case OP_GT:
            result = (givenVal.compareTo(mVal) > 0);
            break;

        case OP_GE:
            result = (givenVal.compareTo(mVal) >= 0);
            break;

        case OP_LE:
            result = (givenVal.compareTo(mVal) >= 0);
            break;

        default:
            throw new AssertionException("Invalid operation code");
        }
        return result;
    }

    private boolean matchIntegerValue(Integer intVal)
            throws EPolicyException {
        boolean result;
        int storedVal;
        int givenVal = intVal.intValue();

        try {
            storedVal = new Integer(mVal).intValue();
        } catch (Exception e) {
            throw new EPolicyException(CMS.getUserMessage("CMS_POLICY_INVALID_ATTR_VALUE", mVal));

        }
        switch (mOp) {
        case OP_EQUAL:
            result = (givenVal == storedVal);
            break;

        case OP_NEQUAL:
            result = (givenVal != storedVal);
            break;

        case OP_LT:
            result = (givenVal < storedVal);
            break;

        case OP_GT:
            result = (givenVal > storedVal);
            break;

        case OP_GE:
            result = (givenVal >= storedVal);
            break;

        case OP_LE:
            result = (givenVal >= storedVal);
            break;

        default:
            throw new AssertionException("Invalid operation code");
        }
        return result;
    }

    private boolean matchBooleanValue(Boolean givenVal)
            throws EPolicyException {
        boolean result;
        Boolean storedVal;

        if (!(mVal.equalsIgnoreCase("true") || mVal.equalsIgnoreCase("false")))
            throw new EPolicyException(CMS.getUserMessage("CMS_POLICY_INVALID_ATTR_VALUE",
                        mVal));
        storedVal = Boolean.valueOf(mVal);
        switch (mOp) {
        case OP_EQUAL:
            result = (givenVal.equals(storedVal));
            break;

        case OP_NEQUAL:
        case OP_LT:
        case OP_GT:
        case OP_GE:
        case OP_LE:
            result = (!givenVal.equals(storedVal));
            break;

        default:
            throw new AssertionException("Invalid operation code");
        }
        return result;
    }

    public String toString() {
        String op = null;

        switch (mOp) {
        case IExpression.OP_EQUAL:
            op = IExpression.EQUAL_STR;
            break;

        case IExpression.OP_NEQUAL:
            op = IExpression.NEQUAL_STR;
            break;

        case IExpression.OP_GT:
            op = IExpression.GT_STR;
            break;

        case IExpression.OP_LT:
            op = IExpression.LT_STR;
            break;

        case IExpression.OP_GE:
            op = IExpression.GE_STR;
            break;

        case IExpression.OP_LE:
            op = IExpression.LE_STR;
            break;
        }
        if (mPfx != null && mPfx.length() > 0)
            return mPfx + "." + mVar + " " + op + " " + mVal;
        else
            return mVar + " " + op + " " + mVal;
    }

    private static ExpressionComps parseForEquality(String expression) {
        int index = expression.indexOf(IExpression.EQUAL_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_EQUAL;
            String val = expression.substring(index + 2).trim();

            return new ExpressionComps(attr, op, val);
        }
    }

    private static ExpressionComps parseForInEquality(String expression) {
        int index = expression.indexOf(IExpression.NEQUAL_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_NEQUAL;
            String val = expression.substring(index + 2).trim();

            return new ExpressionComps(attr, op, val);
        }
    }

    private static ExpressionComps parseForGT(String expression) {
        int index = expression.indexOf(IExpression.GT_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_GT;
            String val = expression.substring(index + 1).trim();

            return new ExpressionComps(attr, op, val);
        }
    }

    private static ExpressionComps parseForLT(String expression) {
        int index = expression.indexOf(IExpression.LT_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_LT;
            String val = expression.substring(index + 1).trim();

            return new ExpressionComps(attr, op, val);
        }
    }

    private static ExpressionComps parseForGE(String expression) {
        int index = expression.indexOf(IExpression.GE_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_GE;
            String val = expression.substring(index + 2).trim();

            return new ExpressionComps(attr, op, val);
        }
    }

    private static ExpressionComps parseForLE(String expression) {
        int index = expression.indexOf(IExpression.LE_STR);

        if (index < 0)
            return null;
        else {
            String attr = expression.substring(0, index).trim();
            int op = OP_LE;
            String val = expression.substring(index + 2).trim();

            return new ExpressionComps(attr, op, val);
        }
    }
}

class ExpressionComps {
    String attr;
    int op;
    String val;

    public ExpressionComps(String a, int o, String v) {
        attr = a;
        op = o;
        val = v;
    }

    public String getAttr() {
        return attr;
    }

    public int getOp() {
        return op;
    }

    public String getVal() {
        return val;
    }
}
