/*
// 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.type;

import org.olap4j.OlapException;
import org.olap4j.metadata.Hierarchy;

/**
 * Utility methods relating to types.
 *
 * <p>NOTE: This class is experimental. Not part of the public olap4j API.
 *
 * @author jhyde
 * @since Feb 17, 2005
 */
public class TypeUtil {

    /**
     * Given a set type, returns the element type. Or its element type, if it
     * is a set type. And so on.
     */
    private static Type stripSetType(Type type) {
        while (type instanceof SetType) {
            type = ((SetType) type).getElementType();
        }
        return type;
    }

    /**
     * Converts a type to a member or tuple type.
     * If it cannot, returns null.
     */
    private static Type toMemberOrTupleType(Type type) throws OlapException {
        type = stripSetType(type);
        if (type instanceof TupleType) {
            return (TupleType) type;
        } else {
            return toMemberType(type);
        }
    }

    /**
     * Converts a type to a member type.
     * If it is a set, strips the set.
     * If it is a member type, returns the type unchanged.
     * If it is a dimension, hierarchy or level type, converts it to
     * a member type.
     * If it is a tuple, number, string, or boolean, returns null.
     */
    static MemberType toMemberType(Type type) throws OlapException {
        type = stripSetType(type);
        if (type instanceof MemberType) {
            return (MemberType) type;
        } else if (type instanceof DimensionType
            || type instanceof HierarchyType
            || type instanceof LevelType)
        {
            return MemberType.forType(type);
        } else {
            return null;
        }
    }

    /**
     * Returns whether this type is union-compatible with another.
     * In general, to be union-compatible, types must have the same
     * dimensionality.
     *
     * @param type1 First type
     * @param type2 Second type
     * @return Whether types are union-compatible
     * @throws OlapException on error
     */
    static boolean isUnionCompatible(
        Type type1,
        Type type2)
        throws OlapException
    {
        if (type1 instanceof TupleType) {
            return type2 instanceof TupleType
                && ((TupleType) type1).isUnionCompatibleWith(
                    (TupleType) type2);
        } else {
            final MemberType memberType1 = toMemberType(type1);
            if (memberType1 == null) {
                return false;
            }
            final MemberType memberType2 = toMemberType(type2);
            if (memberType2 == null) {
                return false;
            }
            final Hierarchy hierarchy1 = memberType1.getHierarchy();
            final Hierarchy hierarchy2 = memberType2.getHierarchy();
            return equal(hierarchy1, hierarchy2);
        }
    }

    private static boolean equal(
        final Hierarchy hierarchy1, final Hierarchy hierarchy2)
    {
        if (hierarchy1 == null
            || hierarchy2 == null
            || hierarchy2.getUniqueName().equals(
                hierarchy1.getUniqueName()))
        {
            // They are compatible.
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns whether a value of a given type can be evaluated to a scalar
     * value.
     *
     * <p>The rules are as follows:<ul>
     * <li>Clearly boolean, numeric and string expressions can be evaluated.
     * <li>Member and tuple expressions can be interpreted as a scalar value.
     *     The expression is evaluated to establish the context where a measure
     *     can be evaluated.
     * <li>Hierarchy and dimension expressions are implicitly
     *     converted into the current member, and evaluated as above.
     * <li>Level expressions cannot be evaluated
     * <li>Cube and Set (even sets with a single member) cannot be evaluated.
     * </ul>
     *
     * @param type Type
     * @return Whether an expression of this type can be evaluated to yield a
     *   scalar value.
     */
    public static boolean canEvaluate(Type type) {
        return ! (type instanceof SetType
                  || type instanceof CubeType
                  || type instanceof LevelType);
    }

    /**
     * Returns whether a type is a set type.
     *
     * @param type Type
     * @return Whether a value of this type can be evaluated to yield a set.
     */
    public static boolean isSet(Type type) {
        return type instanceof SetType;
    }

    private static boolean couldBeMember(Type type) {
        return type instanceof MemberType
            || type instanceof HierarchyType
            || type instanceof DimensionType;
    }

    static boolean equal(Object o, Object p) {
        return o == null ? p == null : p != null && o.equals(p);
    }
}

// End TypeUtil.java

