/**
 * diqube: Distributed Query Base.
 *
 * Copyright (C) 2015 Bastian Gloeckle
 *
 * This file is part of diqube.
 *
 * diqube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.diqube.executionenv;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.diqube.data.column.ColumnShard;
import org.diqube.data.column.ColumnType;
import org.diqube.data.column.ConstantColumnShard;
import org.diqube.data.column.StandardColumnShard;
import org.diqube.data.types.dbl.DoubleColumnShard;
import org.diqube.data.types.lng.LongColumnShard;
import org.diqube.data.types.str.StringColumnShard;
import org.diqube.executionenv.querystats.AbstractQueryableColumnShardFacade;
import org.diqube.executionenv.querystats.QueryableColumnShard;
import org.diqube.executionenv.querystats.QueryableDoubleColumnShard;
import org.diqube.executionenv.querystats.QueryableDoubleColumnShardFacade;
import org.diqube.executionenv.querystats.QueryableLongColumnShard;
import org.diqube.executionenv.querystats.QueryableLongColumnShardFacade;
import org.diqube.executionenv.querystats.QueryableStringColumnShard;
import org.diqube.executionenv.querystats.QueryableStringColumnShardFacade;
import org.diqube.queries.QueryRegistry;

import com.google.common.collect.Iterables;

/**
 * Abstract implementation of an {@link ExecutionEnvironment} containing a separate set of Long, String and Double
 * columns.
 *
 * @author Bastian Gloeckle
 */
public abstract class AbstractExecutionEnvironment implements ExecutionEnvironment {
  private Map<String, QueryableLongColumnShardFacade> tempLongColumns = new ConcurrentHashMap<>();
  private Map<String, QueryableStringColumnShardFacade> tempStringColumns = new ConcurrentHashMap<>();
  private Map<String, QueryableDoubleColumnShardFacade> tempDoubleColumns = new ConcurrentHashMap<>();
  protected QueryRegistry queryRegistry;

  public AbstractExecutionEnvironment(QueryRegistry queryRegistry) {
    this.queryRegistry = queryRegistry;
  }

  @Override
  public QueryableLongColumnShard getLongColumnShard(String name) {
    if (tempLongColumns.containsKey(name))
      return tempLongColumns.get(name);
    return delegateGetLongColumnShard(name);
  }

  /**
   * @return <code>null</code> if not available.
   */
  abstract protected QueryableLongColumnShard delegateGetLongColumnShard(String name);

  @Override
  public QueryableStringColumnShard getStringColumnShard(String name) {
    if (tempStringColumns.containsKey(name))
      return tempStringColumns.get(name);
    return delegateGetStringColumnShard(name);
  }

  /**
   * @return <code>null</code> if not available.
   */
  abstract protected QueryableStringColumnShard delegateGetStringColumnShard(String name);

  @Override
  public QueryableDoubleColumnShard getDoubleColumnShard(String name) {
    if (tempDoubleColumns.containsKey(name))
      return tempDoubleColumns.get(name);
    return delegateGetDoubleColumnShard(name);
  }

  /**
   * @return <code>null</code> if not available.
   */
  abstract protected QueryableDoubleColumnShard delegateGetDoubleColumnShard(String name);

  @Override
  public ColumnType getColumnType(String colName) {
    if (getLongColumnShard(colName) != null)
      return ColumnType.LONG;
    if (getStringColumnShard(colName) != null)
      return ColumnType.STRING;
    if (getDoubleColumnShard(colName) != null)
      return ColumnType.DOUBLE;
    return null;
  }

  @Override
  public QueryableColumnShard getColumnShard(String name) {
    QueryableColumnShard res = getLongColumnShard(name);
    if (res != null)
      return res;

    res = getStringColumnShard(name);
    if (res != null)
      return res;

    return getDoubleColumnShard(name);
  }

  @Override
  public void storeTemporaryLongColumnShard(LongColumnShard column) {
    queryRegistry.getOrCreateCurrentStatsManager().incNumberOfTemporaryColumnShardsCreated();
    internalStoreTemporaryLongColumnShard(column);
  }

  protected void internalStoreTemporaryLongColumnShard(LongColumnShard column) {
    tempLongColumns.put(column.getName(), new QueryableLongColumnShardFacade(column, true, queryRegistry));
  }

  @Override
  public void storeTemporaryStringColumnShard(StringColumnShard column) {
    queryRegistry.getOrCreateCurrentStatsManager().incNumberOfTemporaryColumnShardsCreated();
    internalStoreTemporaryStringColumnShard(column);
  }

  protected void internalStoreTemporaryStringColumnShard(StringColumnShard column) {
    tempStringColumns.put(column.getName(), new QueryableStringColumnShardFacade(column, true, queryRegistry));
  }

  @Override
  public void storeTemporaryDoubleColumnShard(DoubleColumnShard column) {
    queryRegistry.getOrCreateCurrentStatsManager().incNumberOfTemporaryColumnShardsCreated();
    internalStoreTemporaryDoubleColumnShard(column);
  }

  protected void internalStoreTemporaryDoubleColumnShard(DoubleColumnShard column) {
    tempDoubleColumns.put(column.getName(), new QueryableDoubleColumnShardFacade(column, true, queryRegistry));
  }

  @Override
  public StandardColumnShard getPureStandardColumnShard(String name) {
    ColumnShard colShard = getColumnShard(name);

    if (colShard == null)
      return null;

    if (colShard instanceof QueryableColumnShard)
      colShard = ((QueryableColumnShard) colShard).getDelegate();

    if (colShard instanceof StandardColumnShard)
      return (StandardColumnShard) colShard;

    return null;
  }

  @Override
  public ConstantColumnShard getPureConstantColumnShard(String name) {
    ColumnShard colShard = getColumnShard(name);

    if (colShard == null)
      return null;

    if (colShard instanceof QueryableColumnShard)
      colShard = ((QueryableColumnShard) colShard).getDelegate();

    if (colShard instanceof ConstantColumnShard)
      return (ConstantColumnShard) colShard;

    return null;
  }

  protected Set<String> getAllColumnNamesDefinedInThisEnv() {
    Set<String> res = new HashSet<>();
    res.addAll(tempDoubleColumns.keySet());
    res.addAll(tempLongColumns.keySet());
    res.addAll(tempStringColumns.keySet());
    return res;
  }

  @Override
  public boolean isTemporaryColumn(String colName) {
    return tempDoubleColumns.containsKey(colName) || tempLongColumns.containsKey(colName)
        || tempStringColumns.containsKey(colName) || delegateIsTemporaryColumns(colName);
  }

  protected abstract boolean delegateIsTemporaryColumns(String colName);

  @Override
  public Map<String, List<QueryableColumnShard>> getAllTemporaryColumnShards() {
    Map<String, List<QueryableColumnShard>> res = delegateGetAllTemporaryColumnShards();
    for (Entry<String, ? extends AbstractQueryableColumnShardFacade> tempEntry : Iterables
        .concat(tempDoubleColumns.entrySet(), tempLongColumns.entrySet(), tempStringColumns.entrySet())) {
      if (!res.containsKey(tempEntry.getKey()))
        res.put(tempEntry.getKey(), new ArrayList<>());
      res.get(tempEntry.getKey()).add(tempEntry.getValue());
    }
    return res;
  }

  protected abstract Map<String, List<QueryableColumnShard>> delegateGetAllTemporaryColumnShards();

  @Override
  public Map<String, QueryableColumnShard> getAllNonTemporaryColumnShards() {
    return delegateGetAllNonTemporaryColumnShards();
  }

  protected abstract Map<String, QueryableColumnShard> delegateGetAllNonTemporaryColumnShards();

}
