package org.olap.server.processor.sql;


import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.olap.server.database.physical.PhysicalSchema;
import org.olap.server.database.physical.TableJoin;
import org.olap.server.driver.ServerCellSet;
import org.olap.server.driver.metadata.ServerLevel;
import org.olap.server.driver.metadata.ServerMeasure;
import org.olap.server.processor.LevelMemberSet;
import org.olap.server.processor.ResultAxis;
import org.olap.server.processor.functions.OlapOp;
import org.olap.server.processor.sql.TableMapping.LevelMapping;
import org.olap.server.processor.sql.TableMapping.MeasureMapping;
import org.olap4j.OlapException;
import org.olap4j.metadata.Member;

import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.SelectQuery;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;

public class SqlQuery implements ResultSetProcessor{
	
	private PhysicalSchema schema;
	private ServerCellSet cellSet;
	private SelectQuery sql;
	
	private Set<TableJoin> joins = new HashSet<TableJoin>();
	private List<DbColumn> groupings = new ArrayList<DbColumn>(); 
	private List<Condition> conditions = new ArrayList<Condition>();
	private List<Condition> having_conditions = new ArrayList<Condition>();
	
	private List<ResultAxis> resultAxes;
	private ResultAxis filterAxis;
	private TableMapping mapping;
	
	private static Log log = LogFactory.getLog(SqlQuery.class);
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SqlQuery(PhysicalSchema schema, ServerCellSet cellSet) {
		
		this.schema = schema;
		this.cellSet = cellSet;
		this.resultAxes = (List)cellSet.getAxes();
		this.filterAxis = (ResultAxis) cellSet.getFilterAxis();
		
	}


	@Override
	public String toString() {			
		return sql.validate().toString();
	}

	public void mapResultToCellSet(ResultSet result) throws SQLException {
		
		List<MeasureMapping> measures = mapping.getMeasures();
		
		while(result.next()){

			if(measures.isEmpty()){
				mapMeasure(result, null);
			}else{
				for(MeasureMapping measure : measures){
				
					Integer[] position_indexes = mapMeasure(result, measure);
					if(position_indexes!=null)
						cellSet.addCell(position_indexes, (Number) result.getObject(measure.alias), measure.measure.getFormat());
				
				}
			}
			
		}
		
	}


	private Integer[] mapMeasure(ResultSet result, MeasureMapping measure) throws SQLException {
		Integer[] position_indexes = new Integer[resultAxes.size()];
		boolean adding_member = true;
		
		for(int i=0; i<resultAxes.size(); i++ ){
			ResultAxis axis = resultAxes.get(i);
			
			List<Member> members = new ArrayList<Member>();
			
			for( LevelMemberSet layer : axis.getLayers() ){
				
				if(layer.isMeasure()){
					members.add(measure.measure);
				}else if(layer.getLevel() instanceof ServerLevel){
					LevelMapping lm = mapping.getMapping(layer.getLevel());
					String dim_value_name = result.getString(lm.name_alias);
					String dim_value_key = lm.key_alias==null ? dim_value_name : result.getString(lm.key_alias);
					Member member = layer.findMember( dim_value_key, dim_value_name);
					if(member!=null)
						members.add(member);
					else
						adding_member = false;
				}
				
				
			}
			
			if(adding_member)
				position_indexes[i] = axis.positionOrdinal(members);
		
		
		}

		return adding_member ? position_indexes : null;
		
	}

	
	public void generateQuery() throws OlapException{
		
		sql = new SelectQuery();
		
		this.mapping = TableMapping.mapToDatabase(schema, resultAxes, filterAxis);
		
		log.debug("SelectQuery using table: "+mapping.getAggregate().table);
		
		buildColumns();
		
		buildSubQueries();
		
		
		for(TableJoin join : joins){
			sql.addJoins(SelectQuery.JoinType.INNER, join.getDbJoin());
		}
		
		if(!groupings.isEmpty()){
			sql.addGroupings(groupings.toArray(new DbColumn[0]));
		}
		
		for(Condition c: conditions)
			sql.addCondition(c);

		for(Condition c: having_conditions)
			sql.addHaving(c);

		addSorters();
		
		
	}


	protected void addSorters() {
		
		for(ResultAxis axis : resultAxes){
			
			List<Sorter> axisSorters = new ArrayList<Sorter>();
			
			boolean breaks_hierarchy = false;
			for(LevelMemberSet layer : axis.getLayers()){
				if(layer.getSorter()!=null){
					axisSorters.add(layer.getSorter());
					if(layer.getSorter().isBreakingHierarchy())
						breaks_hierarchy = true;
				}
			}
			
			if(breaks_hierarchy){
				for(Iterator<Sorter> it = axisSorters.iterator(); it.hasNext();){
					Sorter sorter =  it.next();
					if(sorter.isBelongs_to_hierarchy())
						it.remove();
				}
			}
			
			for(Sorter sorter : axisSorters){
				if(sorter.getColumn()!=null){
					sql.addOrdering(sorter.getColumn(), sorter.getDirection());
				}else{
					sql.addCustomOrdering(sorter.getExpression(), sorter.getDirection());
				}
			}
			
		}
	}


	private void buildSubQueries() throws OlapException {
	
		
		for(ResultAxis axis : resultAxes){
			
			for(LevelMemberSet layer : axis.getLayers()){
				filterLayer(layer);					
			}
			
		}
		
		for(LevelMemberSet layer : filterAxis.getLayers()){
			filterLayer(layer);					
		}
		
		
	}


	protected void filterLayer(LevelMemberSet layer) throws OlapException {
		OlapOp func =layer.getFunction();
		if(func!=null){
			SetSubquery sub_query = func.query(mapping, layer);
			if(sub_query!=null){
				
				Condition condition = sub_query.condition();
				if(condition!=null)
					addCondition(condition);

				Condition having_condition = sub_query.havingCondition();
				if(having_condition!=null)
					addHavingCondition(having_condition);
				
			}
				

		}
	}



	protected void buildColumns() throws OlapException {
		DbTable fromTable = null;
		for(ResultAxis axis : resultAxes){
			
			for(LevelMemberSet layer : axis.getLayers()){
				
				if(layer.isMeasure()){
					
					for(Member m : layer.getMembers()){
						TableMapping.MeasureMapping mmap = mapping.getMapping((ServerMeasure) m);						
						sql.addAliasedColumn(mmap.column.getExpression(), mmap.alias);
					}
						
				}else if(layer.getLevel() instanceof ServerLevel){
					
					TableMapping.LevelMapping lmap = mapping.getMapping(layer.getLevel());
					fromTable = lmap.name_column.getDbColumn().getTable();			
					sql.addAliasedColumn(lmap.name_column.getDbColumn(), lmap.name_alias);
										
					if(lmap.key_column!=lmap.name_column){
						if(lmap.key_column==null)
							throw new OlapException("Key column for name:"+lmap.name_column.column+" not defined in tables.json, layer "+layer.toString());
						sql.addAliasedColumn(lmap.key_column.getDbColumn(), lmap.key_alias);
						addGroupedBy(lmap.key_column.getDbColumn());
					}

					if(lmap.order_column!=lmap.name_column && lmap.order_column!=lmap.key_column){
						addGroupedBy(lmap.order_column.getDbColumn());
					}
					
					layer.setSorter(new Sorter(lmap.order_column.getDbColumn(), lmap.order_column.sort_direction));
					
					addGroupedBy(lmap.name_column.getDbColumn());
					
					addJoin(lmap.join);					
					
				}
				
			}
			
		}
		
		if(fromTable==null){
			log.debug("Adding from table:"+mapping.getAggregate().getDbTable().getName());
			sql.addFromTable(mapping.getAggregate().getDbTable());
		}
		
	}
	

	private void addCondition(Condition condition){
		conditions.add(condition);
	}
	
	private void addHavingCondition(Condition having_condition) {
		having_conditions.add(having_condition);
	}

	
	private void addJoin(TableJoin join){
		joins.add(join);
	}

	
	private void addGroupedBy(DbColumn col){
		if(!groupings.contains(col))
			groupings.add(col);
	}


	public SelectQuery getSql() {
		return sql;
	}


	public TableMapping getMapping() {
		return mapping;
	}


	@Override
	public void process(ResultSet rs) throws SQLException, OlapException {
		mapResultToCellSet(rs);
	}





}