/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2001-2005 Julian Hyde
// Copyright (C) 2005-2015 Pentaho and others
// All Rights Reserved.
*/
package mondrian.olap;

import mondrian.resource.MondrianResource;
import mondrian.spi.MemberFormatter;
import mondrian.util.Bug;

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

/**
 * <code>MemberBase</code> is a partial implementation of {@link Member}.
 *
 * @author jhyde
 * @since 6 August, 2001
 */
public abstract class MemberBase
    extends OlapElementBase
    implements Member
{

    protected Member parentMember;
    protected final Level level;
    protected String uniqueName;

    /**
     * Combines member type and other properties, such as whether the member
     * is the 'all' or 'null' member of its hierarchy and whether it is a
     * measure or is calculated, into an integer field.
     *
     * <p>The fields are:<ul>
     * <li>bits 0, 1, 2 ({@link #FLAG_TYPE_MASK}) are member type;
     * <li>bit 3 ({@link #FLAG_HIDDEN}) is set if the member is hidden;
     * <li>bit 4 ({@link #FLAG_ALL}) is set if this is the all member of its
     *     hierarchy;
     * <li>bit 5 ({@link #FLAG_NULL}) is set if this is the null member of its
     *     hierarchy;
     * <li>bit 6 ({@link #FLAG_CALCULATED}) is set if this is a calculated
     *     member.
     * <li>bit 7 ({@link #FLAG_MEASURE}) is set if this is a measure.
     * </ul>
     *
     * NOTE: jhyde, 2007/8/10. It is necessary to cache whether the member is
     * 'all', 'calculated' or 'null' in the member's state, because these
     * properties are used so often. If we used a virtual method call - say we
     * made each subclass implement 'boolean isNull()' - it would be slower.
     * We use one flags field rather than 4 boolean fields to save space.
     */
    protected final int flags;

    private static final int FLAG_TYPE_MASK = 0x07;
    private static final int FLAG_HIDDEN = 0x08;
    private static final int FLAG_ALL = 0x10;
    private static final int FLAG_NULL = 0x20;
    private static final int FLAG_CALCULATED = 0x40;
    private static final int FLAG_MEASURE = 0x80;

    /**
     * Cached values of {@link mondrian.olap.Member.MemberType} enumeration.
     * Without caching, get excessive calls to {@link Object#clone}.
     */
    private static final MemberType[] MEMBER_TYPE_VALUES = MemberType.values();

    protected MemberBase(
        Member parentMember,
        Level level,
        MemberType memberType)
    {
        this.parentMember = parentMember;
        this.level = level;
        this.flags = memberType.ordinal()
            | (memberType == MemberType.ALL ? FLAG_ALL : 0)
            | (memberType == MemberType.NULL ? FLAG_NULL : 0)
            | (computeCalculated(memberType) ? FLAG_CALCULATED : 0)
            | (level.getHierarchy().getDimension().isMeasures()
               ? FLAG_MEASURE
               : 0);
    }

    protected MemberBase() {
        this.flags = 0;
        this.level = null;
    }

    public String getQualifiedName() {
        return MondrianResource.instance().MdxMemberName.str(getUniqueName());
    }

    public abstract String getName();

    public String getUniqueName() {
        return uniqueName;
    }

    public String getCaption() {
        // if there is a member formatter for the members level,
        //  we will call this interface to provide the display string
        MemberFormatter mf = getLevel().getMemberFormatter();
        if (mf != null) {
            return mf.formatMember(this);
        }
        final String caption = super.getCaption();
        return (caption != null)
            ? caption
            : getName();
    }

    public String getParentUniqueName() {
        return parentMember == null
            ? null
            : parentMember.getUniqueName();
    }

    public Dimension getDimension() {
        return level.getDimension();
    }

    public Hierarchy getHierarchy() {
        return level.getHierarchy();
    }

    public Level getLevel() {
        return level;
    }

    public MemberType getMemberType() {
        return MEMBER_TYPE_VALUES[flags & FLAG_TYPE_MASK];
    }

    public String getDescription() {
        return (String) getPropertyValue(Property.DESCRIPTION.name);
    }

    public boolean isMeasure() {
        return (flags & FLAG_MEASURE) != 0;
    }

    public boolean isAll() {
        return (flags & FLAG_ALL) != 0;
    }

    public boolean isNull() {
        return (flags & FLAG_NULL) != 0;
    }

    public boolean isCalculated() {
        return (flags & FLAG_CALCULATED) != 0;
    }

    public boolean isEvaluated() {
        // should just call isCalculated(), but called in tight loops
        // and too many subclass implementations for jit to inline properly?
        return (flags & FLAG_CALCULATED) != 0;
    }

    public OlapElement lookupChild(
        SchemaReader schemaReader,
        Id.Segment childName,
        MatchType matchType)
    {
        return schemaReader.lookupMemberChildByName(
            this, childName, matchType);
    }

    // implement Member
    public Member getParentMember() {
        return parentMember;
    }

    // implement Member
    public boolean isChildOrEqualTo(Member member) {
        // REVIEW: Using uniqueName to calculate ancestry seems inefficient,
        //   because we can't afford to store every member's unique name, so
        //   we want to compute it on the fly
        assert !Bug.BugSegregateRolapCubeMemberFixed;
        return (member != null) && isChildOrEqualTo(member.getUniqueName());
    }

   /**
    * Returns whether this <code>Member</code>'s unique name is equal to, a
    * child of, or a descendent of a member whose unique name is
    * <code>uniqueName</code>.
    */
    public boolean isChildOrEqualTo(String uniqueName) {
        if (uniqueName == null) {
            return false;
        }

        return isChildOrEqualTo(this, uniqueName);
    }

    private static boolean isChildOrEqualTo(Member member, String uniqueName) {
        while (true) {
            String thisUniqueName = member.getUniqueName();
            if (thisUniqueName.equals(uniqueName)) {
                // found a match
                return true;
            }
            // try candidate's parentMember
            member = member.getParentMember();
            if (member == null) {
                // have reached root
                return false;
            }
        }
    }

    /**
     * Computes the value to be returned by {@link #isCalculated()}, so it can
     * be cached in a variable.
     *
     * @param memberType Member type
     * @return Whether this member is calculated
     */
    protected boolean computeCalculated(final MemberType memberType) {
        // If the member is not created from the "with member ..." MDX, the
        // calculated will be null. But it may be still a calculated measure
        // stored in the cube.
        return isCalculatedInQuery() || memberType == MemberType.FORMULA;
    }

    public int getSolveOrder() {
        return -1;
    }

    /**
     * Returns the expression by which this member is calculated. The expression
     * is not null if and only if the member is not calculated.
     *
     * @post (return != null) == (isCalculated())
     */
    public Exp getExpression() {
        return null;
    }

    // implement Member
    public List<Member> getAncestorMembers() {
        final SchemaReader schemaReader =
            getDimension().getSchema().getSchemaReader();
        final ArrayList<Member> ancestorList = new ArrayList<Member>();
        schemaReader.getMemberAncestors(this, ancestorList);
        return ancestorList;
    }

    /**
     * Returns the ordinal of this member within its hierarchy.
     * The default implementation returns -1.
     */
    public int getOrdinal() {
        return -1;
    }

    /**
     * Returns the order key of this member among its siblings.
     * The default implementation returns null.
     */
    public Comparable getOrderKey() {
        return null;
    }

    public boolean isHidden() {
        return false;
    }

    public Member getDataMember() {
        return null;
    }

    public String getPropertyFormattedValue(String propertyName) {
        return getPropertyValue(propertyName).toString();
    }

    public boolean isParentChildPhysicalMember() {
        return false;
    }

    public boolean isParentChildLeaf() {
        return false;
    }
}

// End MemberBase.java
