/*
 * ====================================================================
 * This software is subject to the terms of the Common Public License
 * Agreement, available at the following URL:
 *   http://www.opensource.org/licenses/cpl.html .
 * You must accept the terms of that agreement to use this software.
 * ====================================================================
 */
package org.pivot4j.transform.impl;

import org.olap4j.Axis;
import org.olap4j.OlapConnection;
import org.olap4j.OlapException;
import org.olap4j.metadata.Hierarchy;
import org.olap4j.metadata.Member;
import org.pivot4j.PivotException;
import org.pivot4j.PivotModel;
import org.pivot4j.impl.Quax;
import org.pivot4j.impl.QuaxUtil;
import org.pivot4j.impl.QueryAdapter;
import org.pivot4j.mdx.Exp;
import org.pivot4j.mdx.FunCall;
import org.pivot4j.mdx.Syntax;
import org.pivot4j.mdx.metadata.MemberExp;
import org.pivot4j.transform.AbstractTransform;
import org.pivot4j.transform.PlaceHierarchiesOnAxes;
import org.pivot4j.util.OlapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class PlaceHierarchiesOnAxesImpl extends AbstractTransform implements PlaceHierarchiesOnAxes {

	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * @param queryAdapter
	 * @param connection
	 */
	public PlaceHierarchiesOnAxesImpl(QueryAdapter queryAdapter, OlapConnection connection) {
		super(queryAdapter, connection);
	}

	public void placeHierarchies(Axis axis, List<Hierarchy> hierarchies, boolean expandAllMember) {
		placeHierarchies(axis, hierarchies, expandAllMember, true);
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#placeHierarchies(org.olap4j.Axis,
	 *      java.util.List, boolean, boolean)
	 */
	public void placeHierarchies(Axis axis, List<Hierarchy> hierarchies, boolean expandAllMember,
			boolean includeAllMember) {
		QueryAdapter adapter = getQueryAdapter();

		List<Exp> memberExpressions = new ArrayList<Exp>();
		for (Hierarchy hierarchy : hierarchies) {
			memberExpressions.add(createMemberExpression(hierarchy, expandAllMember, includeAllMember));
		}

		Quax quax = adapter.getQuax(axis);

		if (quax == null) {
			quax = adapter.createQuax(axis);
		}

		int nDimension = 0;
		for (Exp memberExpression : memberExpressions) {
			if (memberExpression != null) {
				++nDimension;
			}
		}

		List<Exp> sets = new ArrayList<Exp>(nDimension);

		for (Exp memberExpression : memberExpressions) {
			// null possible due to access control
			if (memberExpressions != null) {
				// object generated by createMemberExpression or
				// CalcSet.createAxisExpression
				sets.add(memberExpression);
			}
		}

		// generate the crossjoins
		quax.regeneratePosTree(sets, true);

		if (logger.isDebugEnabled()) {
			logger.debug("setQueryAxis axis={}, nDimension={}", quax.getOrdinal(), nDimension);
			logger.debug("Expression for the axis : ", quax);
		}
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#addHierarchy(org.olap4j.Axis,
	 *      org.olap4j.metadata.Hierarchy, boolean, int)
	 */
	@Override
	public void addHierarchy(Axis axis, Hierarchy hierarchy, boolean expandAllMember, int position) {
		addHierarchy(axis, hierarchy, expandAllMember, true, position);
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#addHierarchy(org.olap4j.Axis,
	 *      org.olap4j.metadata.Hierarchy, boolean, boolean, int)
	 */
	@Override
	public void addHierarchy(Axis axis, Hierarchy hierarchy, boolean expandAllMember, boolean includeAllMember,
			int position) {
		List<Hierarchy> hierarchies = findVisibleHierarchies(axis);

		if (hierarchies.contains(hierarchy)) {
			moveHierarchy(axis, hierarchy, position);
			return;
		}

		hierarchies = new ArrayList<Hierarchy>(hierarchies);

		if (position < 0 || position >= hierarchies.size()) {
			hierarchies.add(hierarchy);
		} else {
			hierarchies.add(position, hierarchy);
		}

		placeHierarchies(axis, hierarchies, expandAllMember, includeAllMember);
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#moveHierarchy(org.olap4j
	 *      .Axis, org.olap4j.metadata.Hierarchy, int)
	 */
	@Override
	public void moveHierarchy(Axis axis, Hierarchy hierarchy, int position) {
		List<Hierarchy> hierarchies = findVisibleHierarchies(axis);

		if (!hierarchies.contains(hierarchy)) {
			if (logger.isWarnEnabled()) {
				logger.warn("The specified axis does not contain the hierarhcy to be moved.");
			}
			return;
		}

		hierarchies = new ArrayList<Hierarchy>(hierarchies);

		if (position < 0 || position >= hierarchies.size()) {
			hierarchies.remove(hierarchy);
			hierarchies.add(hierarchy);
		} else {
			int index = hierarchies.indexOf(hierarchy);

			if (position < index) {
				hierarchies.remove(hierarchy);
				hierarchies.add(position, hierarchy);
			} else if (position > index) {
				hierarchies.add(position, hierarchy);
				hierarchies.remove(index);
			}
		}

		placeHierarchies(axis, hierarchies, false);
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#removeHierarchy(org
	 *      .olap4j.Axis, org.olap4j.metadata.Hierarchy)
	 */
	@Override
	public void removeHierarchy(Axis axis, Hierarchy hierarchy) {
		List<Hierarchy> hierarchies = findVisibleHierarchies(axis);

		if (!hierarchies.contains(hierarchy)) {
			if (logger.isWarnEnabled()) {
				logger.warn("The specified axis does not contain the hierarhcy to be moved.");
			}
			return;
		}

		hierarchies = new ArrayList<Hierarchy>(hierarchies);
		hierarchies.remove(hierarchy);

		placeHierarchies(axis, hierarchies, false);
	}

	/**
	 * @see org.pivot4j.transform.PlaceHierarchiesOnAxes#findVisibleHierarchies
	 *      (org.olap4j.Axis)
	 */
	@Override
	public List<Hierarchy> findVisibleHierarchies(Axis axis) {
		QueryAdapter adapter = getQueryAdapter();

		// find the Quax for this hierarchy
		Quax quax = adapter.getQuax(axis);
		if (quax == null) {
			// should not occur
			return Collections.emptyList();
		}

		return quax.getHierarchies();
	}

	/**
	 * @param hierarchy
	 * @param expandAllMember
	 * @return
	 */
	protected Exp createMemberExpression(Hierarchy hierarchy, boolean expandAllMember, boolean includeAllMember) {
		// if the query does not contain the hierarchy,
		// just return the highest level
		QueryAdapter adapter = getQueryAdapter();

		// find the Quax for this hier
		Quax quax = adapter.findQuax(hierarchy.getDimension());
		if (quax == null) {
			adapter.getCurrentMdx(true);
			// the hierarchy was not found on any axis
			return topLevelMembers(hierarchy, expandAllMember, includeAllMember);
			// return top level members of the hierarchy
		}

		// the member expression is the list of members plus the list of
		// FunCalls
		// for this dimension
		int iDimension = quax.dimIdx(hierarchy.getDimension());
		return quax.genExpForDim(iDimension);
	}

	/**
	 * TODO Merge with {@link QuaxUtil#topLevelMembers(Hierarchy, boolean)}
	 *
	 * @param hierarchy
	 * @param expandAllMember
	 * @return
	 */
	protected Exp topLevelMembers(Hierarchy hierarchy, boolean expandAllMember, boolean includeAllMember) {
		try {
			if (hierarchy.hasAll()) {
				// an "All" member is present -get it
				// does this call work with parent-child
				Member allMember = hierarchy.getDefaultMember();

				if (allMember == null || !allMember.isAll()) {
					allMember = null;

					List<Member> topMembers = hierarchy.getRootMembers();
					for (Member member : topMembers) {
						if (member.isAll()) {
							allMember = member;
							break;
						}
					}
				}

				if (allMember != null && OlapUtils.isVisible(allMember)) {
					PivotModel model = getModel();

					OlapUtils utils = new OlapUtils(model.getCube());
					utils.setMemberHierarchyCache(getQueryAdapter().getModel().getMemberHierarchyCache());

					if (!expandAllMember) {
						return new MemberExp(utils.wrapRaggedIfNecessary(allMember));
					}

					// must expand
					// create Union({AllMember}, AllMember.children)
					if (includeAllMember) {
						Exp allExp = new MemberExp(allMember);

						FunCall allSet = new FunCall("{}", Syntax.Braces);
						allSet.getArgs().add(allExp);

						FunCall mAllChildren = new FunCall("Children", Syntax.Property);
						mAllChildren.getArgs().add(allExp);

						FunCall union = new FunCall("Union", Syntax.Function);
						union.getArgs().add(allSet);
						union.getArgs().add(mAllChildren);

						return union;
					} else {
						Exp allExp = new MemberExp(allMember);

						FunCall mAllChildren = new FunCall("Children", Syntax.Property);
						mAllChildren.getArgs().add(allExp);
						return mAllChildren;
					}
				}
			}

			List<Member> topMembers = hierarchy.getRootMembers();

			int size = topMembers.size();

			if (size == 0) {
				return null;
			}

			Member firstMember = topMembers.get(0);

			if (topMembers.size() == 1 && OlapUtils.isVisible(firstMember)) {
				// single member
				return new MemberExp(firstMember);
			}

			List<Exp> args = new ArrayList<Exp>(topMembers.size());
			for (Member member : topMembers) {
				if (!member.isHidden() && OlapUtils.isVisible(member)) {
					args.add(new MemberExp(member));
				}
			}

			return new FunCall("{}", Syntax.Braces, args);
		} catch (OlapException e) {
			throw new PivotException(e);
		}
	}
}