/*!
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * Copyright (c) 2002-2018 Hitachi Vantara..  All rights reserved.
 */

package org.pentaho.commons.metadata.mqleditor.editor.service.util;

import org.apache.commons.lang.NotImplementedException;
import org.pentaho.commons.metadata.mqleditor.AggType;
import org.pentaho.commons.metadata.mqleditor.ColumnType;
import org.pentaho.commons.metadata.mqleditor.CombinationType;
import org.pentaho.commons.metadata.mqleditor.MqlCategory;
import org.pentaho.commons.metadata.mqleditor.MqlColumn;
import org.pentaho.commons.metadata.mqleditor.MqlCondition;
import org.pentaho.commons.metadata.mqleditor.MqlDomain;
import org.pentaho.commons.metadata.mqleditor.MqlModel;
import org.pentaho.commons.metadata.mqleditor.MqlOrder;
import org.pentaho.commons.metadata.mqleditor.MqlQuery;
import org.pentaho.commons.metadata.mqleditor.Operator;
import org.pentaho.commons.metadata.mqleditor.beans.Category;
import org.pentaho.commons.metadata.mqleditor.beans.Column;
import org.pentaho.commons.metadata.mqleditor.beans.Condition;
import org.pentaho.commons.metadata.mqleditor.beans.Domain;
import org.pentaho.commons.metadata.mqleditor.beans.Model;
import org.pentaho.commons.metadata.mqleditor.beans.Order;
import org.pentaho.commons.metadata.mqleditor.beans.Query;
import org.pentaho.commons.metadata.mqleditor.utils.ModelSerializer;
import org.pentaho.commons.metadata.mqleditor.utils.ModelUtil;
import org.pentaho.metadata.model.LogicalColumn;
import org.pentaho.metadata.model.LogicalModel;
import org.pentaho.metadata.model.concept.types.AggregationType;
import org.pentaho.metadata.model.concept.types.DataType;
import org.pentaho.metadata.query.model.Constraint;
import org.pentaho.metadata.query.model.Parameter;
import org.pentaho.metadata.query.model.util.QueryXmlHelper;
import org.pentaho.metadata.repository.IMetadataDomainRepository;
import org.pentaho.pms.mql.MQLQuery;
import org.pentaho.pms.mql.MQLQueryImpl;
import org.pentaho.pms.mql.OrderBy;
import org.pentaho.pms.mql.Selection;
import org.pentaho.pms.mql.WhereCondition;
import org.pentaho.pms.schema.BusinessCategory;
import org.pentaho.pms.schema.BusinessColumn;
import org.pentaho.pms.schema.BusinessModel;
import org.pentaho.pms.schema.SchemaMeta;
import org.pentaho.pms.schema.concept.types.aggregation.AggregationSettings;
import org.pentaho.pms.schema.concept.types.datatype.DataTypeSettings;
import org.pentaho.pms.util.UniqueList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * This delegate class provides the majority of functionality needed by an implementation of the MQLEditor Service. If
 * you wish to use this file as a starting point for your implementation you'll need to provide a CWM isntance and
 * CWMSchemaFactory
 *
 * This delegate is used in the debug services provided in the base application.
 *
 */

public class MQLEditorServiceDelegate {

  private String locale = Locale.getDefault().toString();

  private Map<String, MqlDomain> domains = new ConcurrentHashMap<>();

  private IMetadataDomainRepository domainRepository;

  private final ConditionFormatter conditionFormatter = new ConditionFormatter( new OperatorFormatter() );

  /**
   * Keeps track of where a particular model came from.
   */
  private Map<String, SchemaMeta> modelIdToSchemaMetaMap = new HashMap<String, SchemaMeta>();

  public MQLEditorServiceDelegate( IMetadataDomainRepository domainRepository ) {
    this.domainRepository = domainRepository;

    for ( String id : domainRepository.getDomainIds() ) {
      if ( !domains.containsKey( id ) ) {
        // add the domain
        addThinDomain( id );
      }
    }
  }

  public MQLEditorServiceDelegate() {
  }

  protected String getLocale() {
    return locale;
  }

  public List<MqlDomain> refreshMetadataDomains() {
    domains.clear();
    if ( domainRepository != null ) {
      for ( String id : domainRepository.getDomainIds() ) {
        if ( !domains.containsKey( id ) ) {
          // add the domain
          addThinDomain( id );
        }
      }
    }
    return new ArrayList<>( domains.values() );
  }

  public void addThinDomain( String id ) {
    org.pentaho.metadata.model.Domain thinDomain = domainRepository.getDomain( id );
    try {
      Domain domain = new Domain();
      domain.setName( thinDomain.getId() );
      for ( LogicalModel model : thinDomain.getLogicalModels() ) {
        Model myModel = createModel( model );
        domain.getModels().add( myModel );
      }
      domains.put( domain.getName(), domain );
    } catch ( Exception e ) {
      e.printStackTrace();
      // log error
    }

  }

  public String[][] getPreviewData( MqlQuery query, int page, int limit ) {
    throw new NotImplementedException( "Preview not implemented in the Deligate" );
  }

  private Model createModel( BusinessModel m ) {
    Model model = new Model();
    model.setName( m.getName( getLocale() ) );
    model.setId( m.getId() );

    UniqueList<BusinessCategory> cats = m.getRootCategory().getBusinessCategories();
    for ( BusinessCategory cat : cats ) {
      model.getCategories().add( createCategory( m, cat ) );
    }

    return model;
  }

  private Model createModel( LogicalModel m ) {
    Model model = new Model();
    model.setName( m.getName( getLocale() ) );
    model.setId( m.getId() );
    for ( org.pentaho.metadata.model.Category cat : m.getCategories() ) {
      model.getCategories().add( createCategory( m, cat ) );
    }

    return model;

  }

  private Category createCategory( BusinessModel m, BusinessCategory c ) {
    Category cat = new Category();
    cat.setName( c.getName( getLocale() ) );
    cat.setId( c.getId() );
    UniqueList<org.pentaho.pms.schema.BusinessColumn> cols = c.getBusinessColumns();
    for ( org.pentaho.pms.schema.BusinessColumn col : cols ) {
      cat.getBusinessColumns().add( createColumn( m, col ) );
    }

    return cat;
  }

  private Category createCategory( LogicalModel m, org.pentaho.metadata.model.Category c ) {
    Category cat = new Category();
    cat.setName( c.getName( getLocale() ) );
    cat.setId( c.getId() );
    for ( LogicalColumn col : c.getLogicalColumns() ) {
      boolean isHidden = ( col.getProperty( "hidden" ) != null ) ? (Boolean) col.getProperty( "hidden" ) : false;
      if ( !isHidden ) {
        cat.getBusinessColumns().add( createColumn( m, col ) );
      }
    }
    return cat;
  }

  private Column createColumn( BusinessModel m, org.pentaho.pms.schema.BusinessColumn c ) {
    Column col = new Column();
    col.setName( c.getName( getLocale() ) );
    col.setId( c.getId() );

    int type = c.getPhysicalColumn().getDataType().getType();
    ColumnType ourType = null;
    switch ( type ) {
      case DataTypeSettings.DATA_TYPE_BOOLEAN:
        ourType = ColumnType.BOOLEAN;
        break;
      case DataTypeSettings.DATA_TYPE_STRING:
        ourType = ColumnType.TEXT;
        break;
      case DataTypeSettings.DATA_TYPE_NUMERIC:
        ourType = ColumnType.FLOAT;
        break;
      case DataTypeSettings.DATA_TYPE_DATE:
        ourType = ColumnType.DATE;
        break;
    }
    col.setType( ourType );
    List<AggregationSettings> possibleAggs = c.getAggregationList();

    for ( AggregationSettings agg : possibleAggs ) {
      col.getAggTypes().add( getAggType( agg.getType() ) );
    }

    // There might be a default agg, but no agg list. If so, add it to the list.
    AggType defaultAggType = getAggType( c.getAggregationType().getType() );
    if ( col.getAggTypes().contains( defaultAggType ) == false ) {
      col.getAggTypes().add( defaultAggType );
    }

    col.setDefaultAggType( defaultAggType );
    col.setSelectedAggType( defaultAggType );

    return col;
  }

  private Column createColumn( LogicalModel m, LogicalColumn c ) {
    Column col = new Column();
    col.setName( c.getName( getLocale() ) );
    col.setId( c.getId() );

    ColumnType ourType = null;
    if ( c.getDataType() != null ) {
      int type = c.getDataType().getType();
      switch ( type ) {
        case DataTypeSettings.DATA_TYPE_BOOLEAN:
          ourType = ColumnType.BOOLEAN;
          break;
        case DataTypeSettings.DATA_TYPE_STRING:
          ourType = ColumnType.TEXT;
          break;
        case DataTypeSettings.DATA_TYPE_NUMERIC:
          ourType = ColumnType.FLOAT;
          break;
        case DataTypeSettings.DATA_TYPE_DATE:
          ourType = ColumnType.DATE;
          break;
      }
    }
    col.setType( ourType );
    List<AggregationType> possibleAggs = c.getAggregationList();
    if ( possibleAggs != null ) {
      for ( AggregationType agg : possibleAggs ) {
        col.getAggTypes().add( getAggType( agg.ordinal() ) );
      }
    }

    // There might be a default agg, but no agg list. If so, add it to the list.

    AggType defaultAggType = null;
    if ( c.getAggregationType() != null ) {
      defaultAggType = getAggType( c.getAggregationType().ordinal() );
    } else {
      defaultAggType = AggType.NONE;
    }
    if ( col.getAggTypes().contains( defaultAggType ) == false ) {
      col.getAggTypes().add( defaultAggType );
    }
    col.setDefaultAggType( defaultAggType );
    col.setSelectedAggType( defaultAggType );

    return col;
  }

  private AggType getAggType( int type ) {
    switch ( type ) {
      case AggregationSettings.TYPE_AGGREGATION_COUNT:
        return AggType.COUNT;
      case AggregationSettings.TYPE_AGGREGATION_COUNT_DISTINCT:
        return AggType.COUNT_DISTINCT;
      case AggregationSettings.TYPE_AGGREGATION_AVERAGE:
        return AggType.AVERAGE;
      case AggregationSettings.TYPE_AGGREGATION_MAXIMUM:
        return AggType.MAX;
      case AggregationSettings.TYPE_AGGREGATION_MINIMUM:
        return AggType.MIN;
      case AggregationSettings.TYPE_AGGREGATION_SUM:
        return AggType.SUM;
      default:
        return AggType.NONE;
    }
  }

  private AggregationType getAggregationType( AggType type ) {
    if ( type == null ) {
      return null;
    }
    switch ( type ) {
      case COUNT:
        return AggregationType.COUNT;
      case COUNT_DISTINCT:
        return AggregationType.COUNT_DISTINCT;
      case AVERAGE:
        return AggregationType.AVERAGE;
      case MAX:
        return AggregationType.MAXIMUM;
      case MIN:
        return AggregationType.MINIMUM;
      case SUM:
        return AggregationType.SUM;
      default:
        return AggregationType.NONE;
    }
  }

  private AggregationSettings getAggregationSettings( AggType type ) {
    if ( type == null ) {
      return AggregationSettings.NONE;
    }
    switch ( type ) {
      case COUNT:
        return AggregationSettings.COUNT;
      case COUNT_DISTINCT:
        return AggregationSettings.COUNT_DISTINCT;
      case AVERAGE:
        return AggregationSettings.AVERAGE;
      case MAX:
        return AggregationSettings.MAXIMUM;
      case MIN:
        return AggregationSettings.MINIMUM;
      case SUM:
        return AggregationSettings.SUM;
      default:
        return AggregationSettings.NONE;
    }
  }

  public List<MqlDomain> getMetadataDomains() {
    return new ArrayList<>( domains.values() );
  }

  public MqlDomain getDomainByName( String name ) {

    // refresh the metadata domains for every request,
    // we should not be caching this unless its flushable
    // and per user (security issues, etc)

    refreshMetadataDomains();

    if ( !name.endsWith( ".xmi" ) ) {
      return matchLegacyDomainName( name );
    }
    return domains.get( name );
  }

  private MqlDomain matchLegacyDomainName( String name ) {
    for ( Map.Entry<String, MqlDomain> entry : domains.entrySet() ) {
      if ( entry.getKey().startsWith( name ) ) {
        return entry.getValue();
      }
    }
    return null;
  }

  private org.pentaho.pms.schema.BusinessColumn[] getColumns( BusinessModel model, List<? extends MqlColumn> thincols ) {
    org.pentaho.pms.schema.BusinessColumn[] cols = new org.pentaho.pms.schema.BusinessColumn[thincols.size()];

    int i = 0;
    for ( MqlColumn thincol : thincols ) {
      UniqueList list = model.getAllBusinessColumns();
      for ( Object col : list.getList() ) {
        if ( ( (BusinessColumn) col ).getId().equals( thincol.getId() ) ) {
          cols[i] = (org.pentaho.pms.schema.BusinessColumn) col;
          cols[i].setAggregationType( getAggregationSettings( thincol.getSelectedAggType() ) );
          i++;
        }
      }
    }
    return cols;
  }

  private org.pentaho.pms.schema.BusinessColumn getColumn( BusinessModel model, MqlColumn thinCol ) {
    UniqueList list = model.getAllBusinessColumns();
    for ( Object col : list.getList() ) {
      org.pentaho.pms.schema.BusinessColumn bCol = (org.pentaho.pms.schema.BusinessColumn) col;
      if ( bCol.getId().equals( thinCol.getId() ) ) {

        AggType aggType = thinCol.getSelectedAggType();
        if ( aggType == null && thinCol.getDefaultAggType() != null ) {
          aggType = thinCol.getDefaultAggType();
        }
        bCol.setAggregationType( getAggregationSettings( aggType ) );
        return (org.pentaho.pms.schema.BusinessColumn) bCol;
      }
    }
    return null;
  }

  private MQLWhereConditionModel[] getConditions( BusinessModel model, List<? extends MqlCondition> thinConditions ) {
    MQLWhereConditionModel[] conditions = new MQLWhereConditionModel[thinConditions.size()];
    int i = 0;
    for ( MqlCondition thinCondition : thinConditions ) {
      org.pentaho.pms.schema.BusinessColumn col = getColumn( model, thinCondition.getColumn() );
      MQLWhereConditionModel where =
          new MQLWhereConditionModel( thinCondition.getCombinationType() == null
              ? "" : thinCondition.getCombinationType().toString(), //$NON-NLS-1$
              col, conditionFormatter.getCondition( thinCondition, "[" + col.toString() + "]" ) );
      conditions[i++] = where;
    }
    return conditions;
  }

  private List<OrderBy> getOrders( BusinessModel model, List<? extends MqlOrder> thinOrders ) {
    List<OrderBy> ord = new ArrayList<OrderBy>();

    for ( MqlOrder thinOrder : thinOrders ) {
      Selection selection = new Selection( getColumn( model, thinOrder.getColumn() ) );
      ord.add( new OrderBy( selection, ( thinOrder.getOrderType() == MqlOrder.Type.ASC ) ) );
    }
    return ord;
  }

  public String saveQuery( MqlQuery query ) {
    if ( query.getColumns().isEmpty() ) {
      // UI allowed user to create a query without columns
      throw new RuntimeException( "query is not valid without columns" ); //$NON-NLS-1$
    }

    if ( domainRepository != null ) {
      org.pentaho.metadata.model.Domain thinDomain = domainRepository.getDomain( query.getDomain().getName() );
      if ( thinDomain != null ) {
        org.pentaho.metadata.query.model.Query queryModel = convertQueryModel( thinDomain, query );
        return new QueryXmlHelper().toXML( queryModel );
      }
    }

    MQLQuery fatQ = convertModel( query );
    if ( fatQ != null ) {
      return fatQ.getXML();
    } else {
      return "";
    }

  }

  private org.pentaho.metadata.model.Category findCategory( LogicalModel model, MqlColumn col ) {
    for ( org.pentaho.metadata.model.Category category : model.getCategories() ) {
      for ( LogicalColumn lcol : category.getLogicalColumns() ) {
        if ( lcol.getId().equals( col.getId() ) ) {
          return category;
        }
      }
    }
    return null;
  }

  /*
   * a call to this method assumes that we are using the new thin metadata model.
   */
  public org.pentaho.metadata.query.model.Query convertQueryModel( MqlQuery query ) {
    org.pentaho.metadata.model.Domain thinDomain = domainRepository.getDomain( query.getDomain().getName() );
    return convertQueryModel( thinDomain, query );
  }

  private DataType getDataType( ColumnType type ) {
    switch ( type ) {
      case BOOLEAN:
        return DataType.BOOLEAN;
      case DATE:
        // As we are crafting an open formula function to handle dates, the parameter data type needs to be a String.
        // This will eventually be handled by the Metadata layer.
        return DataType.STRING;
        // return DataType.DATE;
      case FLOAT:
      case NUMERIC:
        return DataType.NUMERIC;
      case TEXT:
        return DataType.STRING;
      default:
        return DataType.UNKNOWN;

    }
  }

  private org.pentaho.metadata.query.model.Query convertQueryModel( org.pentaho.metadata.model.Domain thinDomain,
      MqlQuery query ) {
    LogicalModel model = thinDomain.findLogicalModel( query.getModel().getId() );
    if ( model != null ) {
      org.pentaho.metadata.query.model.Query queryObject =
          new org.pentaho.metadata.query.model.Query( thinDomain, model );
      try {
        if ( query.getColumns().size() > 0 ) {
          for ( MqlColumn col : query.getColumns() ) {

            org.pentaho.metadata.model.Category view = findCategory( model, col );

            if ( view == null ) {
              // log an error
              System.err.println( "could not find category for column " + col.getId() );
              return null;
            }
            LogicalColumn column = view.findLogicalColumn( col.getId() );
            if ( column == null ) {
              System.err.println( "could not find column : " + col.getId() );
              return null;
            }
            queryObject.getSelections().add(
                new org.pentaho.metadata.query.model.Selection( view, column, getAggregationType( col
                    .getSelectedAggType() ) ) );
          }

          for ( MqlCondition condition : query.getConditions() ) {
            org.pentaho.metadata.model.Category view = findCategory( model, condition.getColumn() );
            AggregationType type = getAggregationType( condition.getSelectedAggType() );
            String field = "[";
            field += view.getId() + "." + condition.getColumn().getId();
            if ( type != null ) {
              field += "." + type.toString();
            }
            field += "]";

            if ( condition.isParameterized() ) {
              queryObject.getParameters().add(
                  new Parameter( condition.getValue().replaceAll( "[\\{\\}]", "" ), getDataType( condition.getColumn()
                      .getType() ), condition.getDefaultValue() ) );
            }
            queryObject.getConstraints().add(
                new Constraint( getComboType( condition.getCombinationType() ), conditionFormatter.getCondition(
                    condition, field ) ) );
          }

          for ( MqlOrder order : query.getOrders() ) {
            org.pentaho.metadata.model.Category view = findCategory( model, order.getColumn() );
            LogicalColumn column = view.findLogicalColumn( order.getColumn().getId() );
            if ( view == null || column == null ) {
              // log an error
              return null;
            }
            queryObject.getOrders().add(
                new org.pentaho.metadata.query.model.Order( new org.pentaho.metadata.query.model.Selection( view,
                    column, getAggregationType( order.getSelectedAggType() ) ), getOrderType( order.getOrderType() ) ) );
          }

          queryObject.setLimit( query.getLimit() );

          return queryObject;
        }
      } catch ( Throwable e ) { // PMSFormulaException e) {
        e.printStackTrace();
      }

    }
    return null;
  }

  private org.pentaho.metadata.query.model.Order.Type getOrderType( MqlOrder.Type type ) {
    return org.pentaho.metadata.query.model.Order.Type.values()[type.ordinal()];
  }

  private org.pentaho.metadata.query.model.CombinationType getComboType( CombinationType type ) {
    if ( type == null ) {
      return null;
    }
    return org.pentaho.metadata.query.model.CombinationType.values()[type.ordinal()];
  }

  public MqlQuery convertModelToThin( MQLQuery fatQ ) {
    Query query = new Query();

    // currently only called by the PME-editor in which case there's only one domain.
    BusinessModel model = null;
    for ( BusinessModel m : modelIdToSchemaMetaMap.get( fatQ.getModel().getId() ).getBusinessModels() ) {
      if ( m.getId() == fatQ.getModel().getId() ) {
        model = m;
      }
    }

    for ( MqlDomain domain : domains.values() ) {
      for ( MqlModel m : domain.getModels() ) {
        if ( m.getId().equals( fatQ.getModel().getId() ) ) {
          query.setModel( (Model) m );
          query.setDomain( (Domain) domain );
        }
      }
    }

    List<Column> cols = new ArrayList<Column>();
    for ( Selection sel : fatQ.getSelections() ) {
      Column col = createColumn( model, sel.getBusinessColumn() );
      cols.add( col );
    }
    query.setColumns( cols );

    List<Condition> conditions = new ArrayList<Condition>();
    for ( WhereCondition w : fatQ.getConstraints() ) {
      Pattern p = Pattern.compile( "\\[([^\\]]*)\\.([^\\]]*)\\] (.*)" ); //$NON-NLS-1$
      Matcher m = p.matcher( w.getCondition() );
      if ( m.find() ) {
        String cat = m.group( 1 );
        String col = m.group( 2 );

        UniqueList list = model.getAllBusinessColumns();
        BusinessColumn fatcol = null;
        for ( Object c : list.getList() ) {
          if ( ( (BusinessColumn) c ).getId().equals( col ) ) {
            fatcol = (org.pentaho.pms.schema.BusinessColumn) c;

          }
        }
        Column c = createColumn( model, fatcol );
        String condition = m.group( 3 );
        String operator = w.getOperator();

        Condition cond = new Condition();
        cond.setColumn( c );
        cond.setValue( condition );
        cond.setOperator( Operator.parse( operator ) );
        conditions.add( cond );
      }
    }

    query.setConditions( conditions );

    List<Order> orders = new ArrayList<Order>();
    for ( OrderBy ord : fatQ.getOrder() ) {
      Column col = createColumn( model, ord.getSelection().getBusinessColumn() );
      if ( ord.getSelection().getAggregationType() != null ) {
        col.setDefaultAggType( this.getAggType( ord.getSelection().getAggregationType().getType() ) );
      }
      Order o = new Order();
      o.setColumn( col );
      o.setOrderType( ord.isAscending() ? MqlOrder.Type.ASC : MqlOrder.Type.DESC );
      orders.add( o );
    }

    query.setOrders( orders );

    query.setLimit( fatQ.getLimit() );

    return query;
  }

  public MQLQuery convertModel( MqlQuery query ) {
    SchemaMeta meta = modelIdToSchemaMetaMap.get( query.getModel().getId() );

    // TODO: Name isn't guaranteed to be unique, ID is... This should use ID.
    UniqueList<BusinessModel> models = meta.getBusinessModels();
    BusinessModel realModel = null;
    for ( BusinessModel m : models ) {
      if ( m.getId().equals( query.getModel().getId() ) ) {
        realModel = m;
        break;
      }
    }
    if ( realModel != null ) {

      MQLQuery mqlQuery = null;
      try {

        org.pentaho.pms.schema.BusinessColumn[] businessColumns = getColumns( realModel, query.getColumns() );
        if ( businessColumns.length > 0 ) {
          BusinessModel businessModel = realModel;
          mqlQuery = new MQLQueryImpl( meta, businessModel, null, meta.getActiveLocale() );
          List<Selection> selections = new ArrayList<Selection>();
          for ( int i = 0; i < businessColumns.length; i++ ) {
            selections.add( new Selection( businessColumns[i], businessColumns[i].getAggregationType() ) );
          }

          mqlQuery.setSelections( selections );
          MQLWhereConditionModel[] wherelist = getConditions( realModel, query.getConditions() );
          ArrayList<WhereCondition> constraints = new ArrayList<WhereCondition>();
          BusinessCategory rootCat = businessModel.getRootCategory();
          // mqlQuery.setDisableDistinct(!this.distinctSelections.getSelection());
          for ( int i = 0; i < wherelist.length; i++ ) {
            BusinessCategory businessCategory = rootCat.findBusinessCategoryForBusinessColumn( wherelist[i].getField() );

            constraints
                .add( new WhereCondition( businessModel, wherelist[i].getOperator(), wherelist[i].getCondition() ) ); //$NON-NLS-1$
          }
          mqlQuery.setConstraints( constraints );
          mqlQuery.setOrder( getOrders( realModel, query.getOrders() ) );
          mqlQuery.setLimit( query.getLimit() );

          return mqlQuery;
        }
      } catch ( Throwable e ) { // PMSFormulaException e) {
        e.printStackTrace();
      }

    }
    return null;
  }

  public String serializeModel( MqlQuery uiQuery ) {

    Query query = ModelUtil.convertUIModelToBean( uiQuery );

    return ModelSerializer.serialize( query );
  }

  public MqlQuery deserializeModel( String serializedQuery ) {
    return ModelSerializer.deSerialize( serializedQuery );
  }

  private class MQLWhereConditionModel {

    private String operator; // AND

    private org.pentaho.pms.schema.BusinessColumn field; // customer_name

    private String condition; // = 'Casters'

    public MQLWhereConditionModel( String operator, org.pentaho.pms.schema.BusinessColumn field, String condition ) {
      this.operator = operator;
      this.field = field;
      this.condition = condition;
    }

    public String getOperator() {
      return operator;
    }

    public org.pentaho.pms.schema.BusinessColumn getField() {
      return field;
    }

    public String getCondition() {
      return condition;
    }

    public void setCondition( String condition ) {
      this.condition = condition;
    }

    public void setOperator( String operator ) {
      this.operator = operator;
    }

  }

  public MqlQuery convertModelToThin( org.pentaho.metadata.query.model.Query query ) {
    Query q = new Query();

    String domainId = query.getDomain().getId();
    String modelId = query.getLogicalModel().getId();

    MqlDomain selectedDomain = null;
    MqlModel selectedModel = null;
    MqlDomain storedDomain = domains.get( domainId );
    if ( storedDomain != null ) {
      selectedDomain = storedDomain;
    }
    if ( selectedDomain == null ) {
      throw new IllegalStateException( "Could not find domain: " + domainId );
    }
    q.setDomain( (Domain) selectedDomain );
    for ( MqlModel m : selectedDomain.getModels() ) {
      if ( m.getId().equals( modelId ) ) {
        selectedModel = m;
        break;
      }
    }
    if ( selectedModel == null ) {
      throw new IllegalStateException( "Could not find model: " + modelId );
    }
    q.setModel( (Model) selectedModel );
    List<Column> cols = q.getColumns();
    for ( org.pentaho.metadata.query.model.Selection sel : query.getSelections() ) {
      Column c = (Column) convertNewThinColumn( selectedModel, sel.getLogicalColumn().getId() );
      c.setSelectedAggType( convertNewThinAggregationType( sel.getAggregationType() ) );
      cols.add( c );
    }

    for ( org.pentaho.metadata.query.model.Constraint constraint : query.getConstraints() ) {
      FormulaParser fp = new FormulaParser( constraint.getFormula() );

      Condition cond = fp.getCondition();
      Outter: for ( MqlCategory cat : selectedModel.getCategories() ) {
        for ( MqlColumn col : cat.getBusinessColumns() ) {
          if ( col.getId().equals( fp.getColID() ) ) {
            cond.setColumn( (Column) col );
            break Outter;
          }
        }
      }

      // PRD-3710
      if ( fp.getAggType() != null ) {
        cond.setSelectedAggType( convertNewThinAggregationType( AggregationType.valueOf( fp.getAggType() ) ) );
      }

      cond.setCombinationType( CombinationType.valueOf( constraint.getCombinationType().name().toUpperCase() ) );
      String val = cond.getValue();

      // check to see if it was parameterized, if so resolve default and setup the condition properly
      if ( val.indexOf( "[param:" ) == 0 ) {
        String paramKey = val.substring( 7, val.length() - 1 );
        cond.setValue( "{" + paramKey + "}" );
        cond.setParameterized( true );
        for ( Parameter p : query.getParameters() ) {
          if ( p.getName().equals( paramKey ) ) {
            // convert arrays back to vertical bar (pipe) delimited string to support multi-valued defaults
            cond.setDefaultValue( getDisplayableDefaultValue( p ) );
            break;
          }
        }
      }
      q.getConditions().add( cond );
    }

    for ( org.pentaho.metadata.query.model.Order ord : query.getOrders() ) {
      Order o = new Order();
      o.setColumn( (Column) convertNewThinColumn( selectedModel, ord.getSelection().getLogicalColumn().getId() ) );
      o.setOrderType( MqlOrder.Type.valueOf( ord.getType().toString().toUpperCase() ) );
      q.getOrders().add( o );
    }

    q.setLimit( query.getLimit() );
    return q;
  }

  protected AggType convertNewThinAggregationType( AggregationType aggregationType ) {
    if ( aggregationType == null ) {
      return AggType.NONE;
    }

    switch ( aggregationType ) {
      case COUNT:
        return AggType.COUNT;
      case COUNT_DISTINCT:
        return AggType.COUNT_DISTINCT;
      case AVERAGE:
        return AggType.AVERAGE;
      case MINIMUM:
        return AggType.MIN;
      case MAXIMUM:
        return AggType.MAX;
      case SUM:
        return AggType.SUM;
      case NONE:
      default:
        return AggType.NONE;
    }
  }

  private MqlColumn convertNewThinColumn( MqlModel selectedModel, String colId ) {

    for ( MqlCategory c : selectedModel.getCategories() ) {
      for ( MqlColumn col : c.getBusinessColumns() ) {
        if ( col.getId().equals( colId ) ) {
          // PRD-3542 - clone the columns to allow for more than one field with different aggregations.
          if ( col instanceof Column ) {
            return ( (Column) col ).clone();
          } else {
            return col;
          }
        }
      }
    }

    return null;
  }

  private String getDisplayableDefaultValue( Parameter p ) {
    if ( p == null || p.getDefaultValue() == null ) {
      return null;
    } else if ( p.getDefaultValue() instanceof Object[] ) {
      Object[] values = (Object[]) p.getDefaultValue();
      StringBuilder sb = new StringBuilder();
      for ( Object value : values ) {
        if ( sb.length() > 0 ) {
          sb.append( "|" );
        }
        if ( value.toString().contains( "|" ) ) {
          sb.append( "\"" ).append( value ).append( "\"" );
        } else {
          sb.append( value );
        }
      }
      return sb.toString();
    } else {
      return p.getDefaultValue().toString();
    }
  }

}