/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.materialize;

import org.apache.calcite.util.Util;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import org.pentaho.aggdes.algorithm.Algorithm;
import org.pentaho.aggdes.algorithm.Progress;
import org.pentaho.aggdes.algorithm.Result;
import org.pentaho.aggdes.algorithm.impl.MonteCarloAlgorithm;
import org.pentaho.aggdes.algorithm.util.ArgumentUtils;
import org.pentaho.aggdes.model.Aggregate;
import org.pentaho.aggdes.model.Attribute;
import org.pentaho.aggdes.model.Dialect;
import org.pentaho.aggdes.model.Dimension;
import org.pentaho.aggdes.model.Measure;
import org.pentaho.aggdes.model.Parameter;
import org.pentaho.aggdes.model.Schema;
import org.pentaho.aggdes.model.StatisticsProvider;
import org.pentaho.aggdes.model.Table;

import java.io.PrintWriter;
import java.util.List;

/**
 * Algorithm that suggests a set of initial tiles (materialized aggregate views)
 * for a given lattice.
 */
public class TileSuggester {
  private static final Function<Attribute, Lattice.Column> TO_COLUMN =
      new Function<Attribute, Lattice.Column>() {
        public Lattice.Column apply(Attribute input) {
          return ((AttributeImpl) input).column;
        }
      };

  private final Lattice lattice;

  public TileSuggester(Lattice lattice) {
    this.lattice = lattice;
  }

  public Iterable<? extends Lattice.Tile> tiles() {
    final Algorithm algorithm = new MonteCarloAlgorithm();
    final PrintWriter pw = Util.printWriter(System.out);
    final Progress progress = new ArgumentUtils.TextProgress(pw);
    final StatisticsProvider statisticsProvider =
        new StatisticsProviderImpl(lattice);
    final double f = statisticsProvider.getFactRowCount();
    final ImmutableMap.Builder<Parameter, Object> map = ImmutableMap.builder();
    if (lattice.algorithmMaxMillis >= 0) {
      map.put(Algorithm.ParameterEnum.timeLimitSeconds,
          Math.max(1, (int) (lattice.algorithmMaxMillis / 1000L)));
    }
    map.put(Algorithm.ParameterEnum.aggregateLimit, 3);
    map.put(Algorithm.ParameterEnum.costLimit, f * 5d);
    final SchemaImpl schema = new SchemaImpl(lattice, statisticsProvider);
    final Result result = algorithm.run(schema, map.build(), progress);
    final ImmutableList.Builder<Lattice.Tile> tiles = ImmutableList.builder();
    for (Aggregate aggregate : result.getAggregates()) {
      tiles.add(toTile(aggregate));
    }
    return tiles.build();
  }

  private Lattice.Tile toTile(Aggregate aggregate) {
    final Lattice.TileBuilder tileBuilder = new Lattice.TileBuilder();
    for (Lattice.Measure measure : lattice.defaultMeasures) {
      tileBuilder.addMeasure(measure);
    }
    for (Attribute attribute : aggregate.getAttributes()) {
      tileBuilder.addDimension(((AttributeImpl) attribute).column);
    }
    return tileBuilder.build();
  }

  /** Implementation of {@link Schema} based on a {@link Lattice}. */
  private static class SchemaImpl implements Schema {
    private final StatisticsProvider statisticsProvider;
    private final TableImpl table;
    private final ImmutableList<AttributeImpl> attributes;

    SchemaImpl(Lattice lattice, StatisticsProvider statisticsProvider) {
      this.statisticsProvider = statisticsProvider;
      this.table = new TableImpl();
      final ImmutableList.Builder<AttributeImpl> attributeBuilder =
          ImmutableList.builder();
      for (Lattice.Column column : lattice.columns) {
        attributeBuilder.add(new AttributeImpl(column, table));
      }
      this.attributes = attributeBuilder.build();
    }

    public List<? extends Table> getTables() {
      return ImmutableList.of(table);
    }

    public List<Measure> getMeasures() {
      throw new UnsupportedOperationException();
    }

    public List<? extends Dimension> getDimensions() {
      throw new UnsupportedOperationException();
    }

    public List<? extends Attribute> getAttributes() {
      return attributes;
    }

    public StatisticsProvider getStatisticsProvider() {
      return statisticsProvider;
    }

    public Dialect getDialect() {
      throw new UnsupportedOperationException();
    }

    public String generateAggregateSql(Aggregate aggregate,
        List<String> columnNameList) {
      throw new UnsupportedOperationException();
    }
  }

  /** Implementation of {@link Table} based on a {@link Lattice}.
   * There is only one table (in this sense of table) in a lattice.
   * The algorithm does not really care about tables. */
  private static class TableImpl implements Table {
    public String getLabel() {
      return "TABLE";
    }

    public Table getParent() {
      return null;
    }
  }

  /** Implementation of {@link Attribute} based on a {@link Lattice.Column}. */
  private static class AttributeImpl implements Attribute {
    private final Lattice.Column column;
    private final TableImpl table;

    private AttributeImpl(Lattice.Column column, TableImpl table) {
      this.column = column;
      this.table = table;
    }

    @Override public String toString() {
      return getLabel();
    }

    public String getLabel() {
      return column.alias;
    }

    public Table getTable() {
      return table;
    }

    public double estimateSpace() {
      return 0;
    }

    public String getCandidateColumnName() {
      return null;
    }

    public String getDatatype(Dialect dialect) {
      return null;
    }

    public List<Attribute> getAncestorAttributes() {
      return ImmutableList.of();
    }
  }

  /** Implementation of {@link org.pentaho.aggdes.model.StatisticsProvider}
   * that asks the lattice. */
  private static class StatisticsProviderImpl implements StatisticsProvider {
    private final Lattice lattice;

    StatisticsProviderImpl(Lattice lattice) {
      this.lattice = lattice;
    }

    public double getFactRowCount() {
      return lattice.getFactRowCount();
    }

    public double getRowCount(List<Attribute> attributes) {
      return lattice.getRowCount(Lists.transform(attributes, TO_COLUMN));
    }

    public double getSpace(List<Attribute> attributes) {
      return attributes.size();
    }

    public double getLoadTime(List<Attribute> attributes) {
      return getSpace(attributes) * getRowCount(attributes);
    }
  }
}

// End TileSuggester.java
