/**
 * A <code>RestrictedMemberReader</code> reads only the members of a hierarchy allowed by a role's
 * access profile.
 *
 * @author jhyde
 * @since Feb 26, 2003
 * @version $Id$
 */
class RestrictedMemberReader extends DelegatingMemberReader {

  private final Role.HierarchyAccess hierarchyAccess;
  private final boolean ragged;
  private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance();

  /**
   * Creates a <code>RestrictedMemberReader</code>.
   *
   * <p>There's no filtering to be done unless either the role has restrictions on this hierarchy,
   * or the hierarchy is ragged; there's a pre-condition to this effect.
   *
   * @param memberReader Underlying (presumably unrestricted) member reader
   * @param role Role whose access profile to obey. The role must have restrictions on this
   *     hierarchy
   * @pre role.getAccessDetails(memberReader.getHierarchy()) != null ||
   *     memberReader.getHierarchy().isRagged()
   */
  RestrictedMemberReader(MemberReader memberReader, Role role) {
    super(memberReader);
    RolapHierarchy hierarchy = memberReader.getHierarchy();
    ragged = hierarchy.isRagged();
    if (role.getAccessDetails(hierarchy) == null) {
      assert ragged;
      hierarchyAccess = RoleImpl.createAllAccess(hierarchy);
    } else {
      hierarchyAccess = role.getAccessDetails(hierarchy);
    }
  }

  public boolean setCache(MemberCache cache) {
    // Don't support cache-writeback. It would confuse the cache!
    return false;
  }

  public RolapMember getLeadMember(RolapMember member, int n) {
    int i = 0;
    int increment = 1;
    if (n < 0) {
      increment = -1;
      n = -n;
    }
    while (i < n) {
      member = memberReader.getLeadMember(member, increment);
      if (member.isNull()) {
        return member;
      }
      if (canSee(member)) {
        ++i;
      }
    }
    return member;
  }

  public void getMemberChildren(RolapMember parentMember, List<RolapMember> children) {
    MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null);
    getMemberChildren(parentMember, children, constraint);
  }

  public void getMemberChildren(
      RolapMember parentMember, List<RolapMember> children, MemberChildrenConstraint constraint) {
    List<RolapMember> fullChildren = new ArrayList<RolapMember>();
    memberReader.getMemberChildren(parentMember, fullChildren, constraint);
    processMemberChildren(fullChildren, children, constraint);
  }

  private void processMemberChildren(
      List<RolapMember> fullChildren,
      List<RolapMember> children,
      MemberChildrenConstraint constraint) {
    // todo: optimize if parentMember is beyond last level
    List<RolapMember> grandChildren = null;
    for (int i = 0; i < fullChildren.size(); i++) {
      RolapMember member = fullChildren.get(i);
      // If a child is hidden (due to raggedness) include its children.
      // This must be done before applying access-control.
      if (ragged) {
        if (member.isHidden()) {
          // Replace this member with all of its children.
          // They might be hidden too, but we'll get to them in due
          // course. They also might be access-controlled; that's why
          // we deal with raggedness before we apply access-control.
          fullChildren.remove(i);
          if (grandChildren == null) {
            grandChildren = new ArrayList<RolapMember>();
          } else {
            grandChildren.clear();
          }
          memberReader.getMemberChildren(member, grandChildren, constraint);
          fullChildren.addAll(i, grandChildren);
          // Step back to before the first child we just inserted,
          // and go through the loop again.
          --i;
          continue;
        }
      }
      // Filter out children which are invisible because of
      // access-control.
      final Access access;
      if (hierarchyAccess != null) {
        access = hierarchyAccess.getAccess(member);
      } else {
        access = Access.ALL;
      }
      switch (access) {
        case NONE:
          break;
        default:
          children.add(member);
          break;
      }
    }
  }

  /**
   * Writes to members which we can see.
   *
   * @param members Input list
   * @param filteredMembers Output list
   */
  private void filterMembers(List<RolapMember> members, List<RolapMember> filteredMembers) {
    for (RolapMember member : members) {
      if (canSee(member)) {
        filteredMembers.add(member);
      }
    }
  }

  private boolean canSee(final RolapMember member) {
    if (ragged && member.isHidden()) {
      return false;
    }
    if (hierarchyAccess != null) {
      final Access access = hierarchyAccess.getAccess(member);
      return access != Access.NONE;
    }
    return true;
  }

  public void getMemberChildren(List<RolapMember> parentMembers, List<RolapMember> children) {
    MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null);
    getMemberChildren(parentMembers, children, constraint);
  }

  public synchronized void getMemberChildren(
      List<RolapMember> parentMembers,
      List<RolapMember> children,
      MemberChildrenConstraint constraint) {
    //        for (Iterator i = parentMembers.iterator(); i.hasNext();) {
    //            RolapMember parentMember = (RolapMember) i.next();
    //            getMemberChildren(parentMember, children, constraint);
    //        }
    List<RolapMember> fullChildren = new ArrayList<RolapMember>();
    super.getMemberChildren(parentMembers, fullChildren, constraint);
    processMemberChildren(fullChildren, children, constraint);
  }

  public List<RolapMember> getRootMembers() {
    int topLevelDepth = hierarchyAccess.getTopLevelDepth();
    if (topLevelDepth > 0) {
      RolapLevel topLevel = (RolapLevel) getHierarchy().getLevels()[topLevelDepth];
      final List<RolapMember> memberList = getMembersInLevel(topLevel, 0, Integer.MAX_VALUE);
      if (memberList.isEmpty()) {
        throw MondrianResource.instance()
            .HierarchyHasNoAccessibleMembers
            .ex(getHierarchy().getUniqueName());
      }
      return memberList;
    }
    return super.getRootMembers();
  }

  public List<RolapMember> getMembersInLevel(RolapLevel level, int startOrdinal, int endOrdinal) {
    TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null);
    return getMembersInLevel(level, startOrdinal, endOrdinal, constraint);
  }

  public List<RolapMember> getMembersInLevel(
      RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) {
    if (hierarchyAccess != null) {
      final int depth = level.getDepth();
      if (depth < hierarchyAccess.getTopLevelDepth()) {
        return Collections.emptyList();
      }
      if (depth > hierarchyAccess.getBottomLevelDepth()) {
        return Collections.emptyList();
      }
    }
    final List<RolapMember> membersInLevel =
        memberReader.getMembersInLevel(level, startOrdinal, endOrdinal, constraint);
    List<RolapMember> filteredMembers = new ArrayList<RolapMember>();
    filterMembers(membersInLevel, filteredMembers);
    return filteredMembers;
  }

  public RolapMember getDefaultMember() {
    RolapMember defaultMember = (RolapMember) getHierarchy().getDefaultMember();
    if (defaultMember != null) {
      Access i = hierarchyAccess.getAccess(defaultMember);
      if (i != Access.NONE) {
        return defaultMember;
      }
    }
    return getRootMembers().get(0);
  }

  public RolapMember getMemberParent(RolapMember member) {
    RolapMember parentMember = member.getParentMember();
    // Skip over hidden parents.
    while (parentMember != null && parentMember.isHidden()) {
      parentMember = parentMember.getParentMember();
    }
    // Skip over non-accessible parents.
    if (parentMember != null) {
      if (hierarchyAccess.getAccess(parentMember) == Access.NONE) {
        return null;
      }
    }
    return parentMember;
  }
}
 public void getMemberChildren(RolapMember parentMember, List<RolapMember> children) {
   MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null);
   getMemberChildren(parentMember, children, constraint);
 }
 public List<RolapMember> getMembersInLevel(RolapLevel level, int startOrdinal, int endOrdinal) {
   TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null);
   return getMembersInLevel(level, startOrdinal, endOrdinal, constraint);
 }