/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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.olap4j.mdx;

import org.olap4j.type.Type;

import java.util.Arrays;
import java.util.List;

/**
 * A parse tree node representing a call to a function or operator.
 *
 * <p>Examples of calls include:<ul>
 * <li><code>5 + 2</code>, a call to the infix arithmetic operator '+'</li>
 * <li><code>[Measures].[Unit Sales] IS NULL</code>, a call applying the
 *   {@link Syntax#Postfix postfix} operator
 *   <code>IS NULL</code> to a member expression</li>
 * <li><code>CrossJoin({[Gender].Children}, {[Store]})</code>, a call to the
 *   <code>CrossJoin</code> function</li>
 * <li><code>[Gender].Children</code>, a call to the <code>Children</code>
 *   operator, which has {@link Syntax#Property property syntax}</li>
 * <li><code>[Gender].Properties("FORMAT_STRING")</code>, a call to the
 *   <code>Properties</code> operator, which has
 *   {@link Syntax#Method method syntax}</li>
 * </ul>
 *
 * @author jhyde
 * @since Jan 6, 2006
 */
public class CallNode implements ParseTreeNode {

    private final String name;
    private final Syntax syntax;
    private final List<ParseTreeNode> argList;
    private final ParseRegion region;
    private Type type;

    /**
     * Creates a CallNode.
     *
     * <p>The <code>syntax</code> argument determines whether this is a prefix,
     * infix or postfix operator, a function call, and so forth.
     *
     * <p>The list of arguments <code>args</code> must be specified, even if
     * there are zero arguments, and each argument must be not null.
     *
     * <p>The type is initially null, but can be set using {@link #setType}
     * after validation.
     *
     * @param region Region of source code
     * @param name Name of operator or function
     * @param syntax Syntax of call
     * @param args List of zero or more arguments
     */
    public CallNode(
        ParseRegion region,
        String name,
        Syntax syntax,
        List<ParseTreeNode> args)
    {
        this.region = region;
        assert name != null;
        assert syntax != null;
        assert args != null;
        this.name = name;
        this.syntax = syntax;
        this.argList = args;

        // Check special syntaxes.
        switch (syntax) {
        case Braces:
            assert name.equals("{}");
            break;
        case Parentheses:
            assert name.equals("()");
            break;
        case Internal:
            assert name.startsWith("$");
            break;
        case Empty:
            assert name.equals("");
            break;
        default:
            assert !name.startsWith("$")
                && !name.equals("{}")
                && !name.equals("()");
            break;
        }
    }

    /**
     * Creates an CallNode using a variable number of arguments.
     *
     * <p>The <code>syntax</code> argument determines whether this is a prefix,
     * infix or postfix operator, a function call, and so forth.
     *
     * <p>The list of arguments <code>args</code> must be specified, even if
     * there are zero arguments, and each argument must be not null.
     *
     * @param region Region of source code
     * @param name Name of operator or function
     * @param syntax Syntax of call
     * @param args List of zero or more arguments
     */
    public CallNode(
        ParseRegion region,
        String name,
        Syntax syntax,
        ParseTreeNode... args)
    {
        this(region, name, syntax, Arrays.asList(args));
    }

    public ParseRegion getRegion() {
        return region;
    }

    /**
     * Sets the type of this CallNode.
     *
     * <p>Typically, this method would be called by the validator when it has
     * deduced the argument types, chosen between any overloaded functions
     * or operators, and determined the result type of the function or
     * operator.
     *
     * @param type Result type of this call
     */
    public void setType(Type type) {
        this.type = type;
    }

    public Type getType() {
        return type;
    }

    public void unparse(ParseTreeWriter writer) {
        syntax.unparse(name, argList, writer);
    }

    public <T> T accept(ParseTreeVisitor<T> visitor) {
        final T o = visitor.visit(this);
        // visit the call's arguments
        for (ParseTreeNode arg : argList) {
            arg.accept(visitor);
        }
        return o;
    }

    /**
     * Returns the name of the function or operator.
     *
     * @return name of the function or operator
     */
    public String getOperatorName() {
        return name;
    }

    /**
     * Returns the syntax of this call.
     *
     * @return the syntax of the call
     */
    public Syntax getSyntax() {
        return syntax;
    }

    /**
     * Returns the list of arguments to this call.
     *
     * @return list of arguments
     */
    public List<ParseTreeNode> getArgList() {
        return argList;
    }

    public CallNode deepCopy() {
        return new CallNode(
            this.region,
            this.name,
            this.syntax,
            MdxUtil.deepCopyList(argList));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((argList == null) ? 0 : argList.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((syntax == null) ? 0 : syntax.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        CallNode other = (CallNode) obj;
        if (argList == null) {
            if (other.argList != null) {
                return false;
            }
        } else if (!argList.equals(other.argList)) {
            return false;
        }
        if (name == null) {
            if (other.name != null) {
                return false;
            }
        } else if (!name.equals(other.name)) {
            return false;
        }
        if (syntax == null) {
            if (other.syntax != null) {
                return false;
            }
        } else if (!syntax.equals(other.syntax)) {
            return false;
        }
        return true;
    }
}

// End CallNode.java
