/*
 * Druid - a distributed column store.
 * Copyright 2012 - 2015 Metamarkets Group Inc.
 *
 * Licensed 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 io.druid.data.input.impl;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.metamx.common.parsers.ParserUtils;

import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class DimensionsSpec
{
  private final List<DimensionSchema> dimensions;
  private final Set<String> dimensionExclusions;
  private final Map<String, DimensionSchema> dimensionSchemaMap;

  public static List<DimensionSchema> getDefaultSchemas(List<String> dimNames)
  {
    return Lists.transform(
        dimNames,
        new Function<String, DimensionSchema>()
        {
          @Override
          public DimensionSchema apply(String input)
          {
            return new StringDimensionSchema(input);
          }
        }
    );
  }

  public static DimensionSchema convertSpatialSchema(SpatialDimensionSchema spatialSchema)
  {
    return new NewSpatialDimensionSchema(spatialSchema.getDimName(), spatialSchema.getDims());
  }

  @JsonCreator
  public DimensionsSpec(
      @JsonProperty("dimensions") List<DimensionSchema> dimensions,
      @JsonProperty("dimensionExclusions") List<String> dimensionExclusions,
      @Deprecated @JsonProperty("spatialDimensions") List<SpatialDimensionSchema> spatialDimensions
  )
  {
    this.dimensions = dimensions == null
                      ? Lists.<DimensionSchema>newArrayList()
                      : Lists.newArrayList(dimensions);

    this.dimensionExclusions = (dimensionExclusions == null)
                               ? Sets.<String>newHashSet()
                               : Sets.newHashSet(dimensionExclusions);

    List<SpatialDimensionSchema> spatialDims = (spatialDimensions == null)
                                               ? Lists.<SpatialDimensionSchema>newArrayList()
                                               : spatialDimensions;

    verify(spatialDims);

    // Map for easy dimension name-based schema lookup
    this.dimensionSchemaMap = new HashMap<>();
    for (DimensionSchema schema : this.dimensions) {
      dimensionSchemaMap.put(schema.getName(), schema);
    }

    for(SpatialDimensionSchema spatialSchema : spatialDims) {
      DimensionSchema newSchema = DimensionsSpec.convertSpatialSchema(spatialSchema);
      this.dimensions.add(newSchema);
      dimensionSchemaMap.put(newSchema.getName(), newSchema);
    }
  }


  @JsonProperty
  public List<DimensionSchema> getDimensions()
  {
    return dimensions;
  }

  @JsonProperty
  public Set<String> getDimensionExclusions()
  {
    return dimensionExclusions;
  }

  @Deprecated @JsonIgnore
  public List<SpatialDimensionSchema> getSpatialDimensions()
  {
    Iterable<NewSpatialDimensionSchema> filteredList = Iterables.filter(
        dimensions, NewSpatialDimensionSchema.class
    );

    Iterable<SpatialDimensionSchema> transformedList = Iterables.transform(
        filteredList,
        new Function<NewSpatialDimensionSchema, SpatialDimensionSchema>()
        {
          @Nullable
          @Override
          public SpatialDimensionSchema apply(NewSpatialDimensionSchema input)
          {
            return new SpatialDimensionSchema(input.getName(), input.getDims());
          }
        }
    );

    return Lists.newArrayList(transformedList);
  }


  @JsonIgnore
  public List<String> getDimensionNames()
  {
    return Lists.transform(
        dimensions,
        new Function<DimensionSchema, String>()
        {
          @Override
          public String apply(DimensionSchema input)
          {
            return input.getName();
          }
        }
    );
  }

  public DimensionSchema getSchema(String dimension)
  {
    return dimensionSchemaMap.get(dimension);
  }

  public boolean hasCustomDimensions()
  {
    return !(dimensions == null || dimensions.isEmpty());
  }

  public DimensionsSpec withDimensions(List<DimensionSchema> dims)
  {
    return new DimensionsSpec(dims, ImmutableList.copyOf(dimensionExclusions), null);
  }

  public DimensionsSpec withDimensionExclusions(Set<String> dimExs)
  {
    return new DimensionsSpec(
        dimensions,
        ImmutableList.copyOf(Sets.union(dimensionExclusions, dimExs)),
        null
    );
  }

  @Deprecated
  public DimensionsSpec withSpatialDimensions(List<SpatialDimensionSchema> spatials)
  {
    return new DimensionsSpec(dimensions, ImmutableList.copyOf(dimensionExclusions), spatials);
  }

  private void verify(List<SpatialDimensionSchema> spatialDimensions)
  {
    List<String> dimNames = getDimensionNames();
    Preconditions.checkArgument(
        Sets.intersection(this.dimensionExclusions, Sets.newHashSet(dimNames)).isEmpty(),
        "dimensions and dimensions exclusions cannot overlap"
    );

    ParserUtils.validateFields(dimNames);
    ParserUtils.validateFields(dimensionExclusions);

    List<String> spatialDimNames = Lists.transform(
        spatialDimensions,
        new Function<SpatialDimensionSchema, String>()
        {
          @Override
          public String apply(SpatialDimensionSchema input)
          {
            return input.getDimName();
          }
        }
    );

    // Don't allow duplicates between main list and deprecated spatial list
    ParserUtils.validateFields(Iterables.concat(dimNames, spatialDimNames));
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    DimensionsSpec that = (DimensionsSpec) o;

    if (!dimensions.equals(that.dimensions)) {
      return false;
    }

    return dimensionExclusions.equals(that.dimensionExclusions);
  }

  @Override
  public int hashCode()
  {
    int result = dimensions.hashCode();
    result = 31 * result + dimensionExclusions.hashCode();
    return result;
  }
}