/*
 * 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.olingo.server.core.serializer.utils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceAction;
import org.apache.olingo.server.api.uri.UriResourceComplexProperty;
import org.apache.olingo.server.api.uri.UriResourceCount;
import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourceProperty;
import org.apache.olingo.server.api.uri.UriResourceRef;
import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;

public abstract class ExpandSelectHelper {

  public static boolean hasSelect(final SelectOption select) {
    return select != null && select.getSelectItems() != null && !select.getSelectItems().isEmpty();
  }

  public static boolean isAll(final SelectOption select) {
    if (hasSelect(select)) {
      for (final SelectItem item : select.getSelectItems()) {
        if (item.isStar()) {
          return true;
        }
      }
      return false;
    } else {
      return true;
    }
  }

  public static Set<String> getSelectedPropertyNames(final List<SelectItem> selectItems) {
    Set<String> selected = new HashSet<>();
    for (final SelectItem item : selectItems) {
      final UriResource resource = item.getResourcePath().getUriResourceParts().get(0);
      if (resource instanceof UriResourceProperty) {
        selected.add(((UriResourceProperty) resource).getProperty().getName());
      } else if (resource instanceof UriResourceNavigation) {
        selected.add(((UriResourceNavigation) resource).getProperty().getName());
      } else if (resource instanceof UriResourceAction) {
        selected.add(((UriResourceAction) resource).getAction().getName());
      } else if (resource instanceof UriResourceFunction) {
        selected.add(((UriResourceFunction) resource).getFunction().getName());
      }
    }
    return selected;
  }
  
  /**
   * This method creates selectedPath list checking if the resource has entity type filter,
   * complex type filter, or if resource is navigation property and if it has type filter
   * @param selectItems items in the select clause
   * @param propertyName propertyName 
   * @return Set<List<String>> return a list of selected paths
   */
  public static Set<List<String>> getSelectedPathsWithTypeCasts(
      final List<SelectItem> selectItems, final String propertyName) {
    Set<List<String>> selectedPaths = new HashSet<>();
    for (final SelectItem item : selectItems) {
      final List<UriResource> parts = item.getResourcePath().getUriResourceParts();
      final UriResource resource = parts.get(0);
      if (resource instanceof UriResourceProperty
          && propertyName.equals(((UriResourceProperty) resource).getProperty().getName())) {
        List<String> path = new ArrayList<>();
        if (item.getStartTypeFilter() != null) {
          path.add(item.getStartTypeFilter().getFullQualifiedName().getFullQualifiedNameAsString());
        }
        if (resource instanceof UriResourceComplexProperty && 
            ((UriResourceComplexProperty) resource).getComplexTypeFilter() != null) {
          path.add(((UriResourceComplexProperty) resource).getComplexTypeFilter().
              getFullQualifiedName().getFullQualifiedNameAsString());
        } else if (resource instanceof UriResourceEntitySet && 
            ((UriResourceEntitySet) resource).getTypeFilterOnCollection() != null) {
          path.add(((UriResourceEntitySet) resource).getTypeFilterOnCollection().
              getFullQualifiedName().getFullQualifiedNameAsString());
        }
        extractPathsFromResourceParts(selectedPaths, parts, path);
      } else if (resource instanceof UriResourceNavigation
          && propertyName.equals(((UriResourceNavigation) resource).getProperty().getName()) ) {
        List<String> path = new ArrayList<>();
        if (item.getStartTypeFilter() != null) {
          path.add(item.getStartTypeFilter().getFullQualifiedName().getFullQualifiedNameAsString());
        }
        extractPathsFromResourceParts(selectedPaths, parts, path);
      }
    }
    return selectedPaths.isEmpty() ? null : selectedPaths;
  }

  /**
   * @param selectedPaths
   * @param parts
   * @param path
   */
  private static Set<List<String>> extractPathsFromResourceParts(
      Set<List<String>> selectedPaths, final List<UriResource> parts,
      List<String> path) {
    if (parts.size() > 1) {
      for (final UriResource part : parts.subList(1, parts.size())) {
        if (part instanceof UriResourceProperty) {
          path.add(((UriResourceProperty) part).getProperty().getName());
        } else if (part instanceof UriResourceNavigation) {
          path.add(((UriResourceNavigation) part).getProperty().getName());
        }
        if (part instanceof UriResourceComplexProperty &&
            ((UriResourceComplexProperty) part).getComplexTypeFilter() != null) {
          path.add(((UriResourceComplexProperty) part).getComplexTypeFilter().
              getFullQualifiedName().getFullQualifiedNameAsString());
        }
      }
      selectedPaths.add(path);
    } else if (!path.isEmpty()) {
      selectedPaths.add(path);
    } else {
      return null;
    }
    return selectedPaths.isEmpty() ? null : selectedPaths;
  }

  public static Set<List<String>> getSelectedPaths(final List<SelectItem> selectItems, final String propertyName) {
    Set<List<String>> selectedPaths = new HashSet<>();
    for (final SelectItem item : selectItems) {
      final List<UriResource> parts = item.getResourcePath().getUriResourceParts();
      final UriResource resource = parts.get(0);
      if (resource instanceof UriResourceProperty
          && propertyName.equals(((UriResourceProperty) resource).getProperty().getName())) {
        if (parts.size() > 1) {
          List<String> path = new ArrayList<>();
          for (final UriResource part : parts.subList(1, parts.size())) {
            if (part instanceof UriResourceProperty) {
              path.add(((UriResourceProperty) part).getProperty().getName());
            } else if (part instanceof UriResourceNavigation) {
              path.add(((UriResourceNavigation) part).getProperty().getName());
            }
          }
          selectedPaths.add(path);
        } else {
          return null;
        }
      }
    }
    return selectedPaths.isEmpty() ? null : selectedPaths;
  }

  public static Set<List<String>> getSelectedPaths(final List<SelectItem> selectItems) {
    Set<List<String>> selectedPaths = new HashSet<>();
    for (final SelectItem item : selectItems) {
      final List<UriResource> parts = item.getResourcePath().getUriResourceParts();
      final UriResource resource = parts.get(0);
      if (resource instanceof UriResourceProperty) {
        if (!parts.isEmpty()) {
          List<String> path = new ArrayList<>();
          for (final UriResource part : parts.subList(0, parts.size())) {
            if (part instanceof UriResourceProperty) {
              path.add(((UriResourceProperty) part).getProperty().getName());
            }
          }
          selectedPaths.add(path);
        } else {
          return null;
        }
      }
    }
    return selectedPaths.isEmpty() ? null : selectedPaths;
  }
  
  public static boolean isSelected(final Set<List<String>> selectedPaths, final String propertyName) {
    for (final List<String> path : selectedPaths) {
      if (propertyName.equals(path.get(0))) {
        return true;
      }
    }
    return false;
  }

  public static Set<List<String>> getReducedSelectedPaths(final Set<List<String>> selectedPaths,
      final String propertyName) {
    Set<List<String>> reducedPaths = new HashSet<>();
    for (final List<String> path : selectedPaths) {
      if (propertyName.equals(path.get(0))) {
        if (path.size() > 1) {
          reducedPaths.add(path.subList(1, path.size()));
        } else {
          return null;
        }
      }
    }
    return reducedPaths.isEmpty() ? null : reducedPaths;
  }

  public static boolean hasExpand(final ExpandOption expand) {
    return expand != null && expand.getExpandItems() != null && !expand.getExpandItems().isEmpty();
  }

  public static ExpandItem getExpandAll(final ExpandOption expand) {
      for (final ExpandItem item : expand.getExpandItems()) {
        if (item.isStar()) {
          return item;
        } 
      }
      return null;
    }
  
  public static Set<String> getExpandedPropertyNames(final List<ExpandItem> expandItems)
      throws SerializerException {
    Set<String> expanded = new HashSet<>();
    for (final ExpandItem item : expandItems) {
      final List<UriResource> resourceParts = item.getResourcePath().getUriResourceParts();
      final UriResource resource = resourceParts.get(0);
      if (resource instanceof UriResourceNavigation) {
        expanded.add(((UriResourceNavigation) resource).getProperty().getName());
      }
    }
    return expanded;
  }

  public static ExpandItem getExpandItem(final List<ExpandItem> expandItems, final String propertyName) {
    for (final ExpandItem item : expandItems) {
      if (item.isStar()) {
          continue;
      }
      final List<UriResource> resourceParts = item.getResourcePath().getUriResourceParts();
      UriResource resource = null;
      if (resourceParts.get(resourceParts.size() - 1) instanceof UriResourceRef ||
          resourceParts.get(resourceParts.size() - 1) instanceof UriResourceCount) {
        resource = resourceParts.get(resourceParts.size() - 2);
      } else {
        resource = resourceParts.get(resourceParts.size() - 1);
      }
      if ((resource instanceof UriResourceNavigation
          && propertyName.equals(((UriResourceNavigation) resource).getProperty().getName())) ||
          resource instanceof UriResourceProperty
          && propertyName.equals(((UriResourceProperty) resource).getProperty().getName())) {
        return item;
      }
    }
    return null;
  }

  public static Set<List<String>> getExpandedItemsPath(ExpandOption expand) {
    Set<List<String>> expandPaths = new HashSet<>();
    if (expand != null) {
      List<ExpandItem> expandItems = expand.getExpandItems();
      for (ExpandItem item : expandItems) {
        if (item.isStar()) {
          continue;
        }
        List<UriResource> resourceParts = item.getResourcePath().getUriResourceParts();
        if (resourceParts.get(0) instanceof UriResourceComplexProperty) {
          List<String> path = new ArrayList<>();
          for (UriResource resource : resourceParts) {
            if (resource instanceof UriResourceNavigation) {
              path.add(((UriResourceNavigation) resource).getProperty().getName());
            } else if (resource instanceof UriResourceProperty) {
              path.add(((UriResourceProperty) resource).getProperty().getName());
            }
          }
          expandPaths.add(path); 
        }
      }
    }
    return expandPaths;
  }
  
  public static Set<List<String>> getReducedExpandItemsPaths(final Set<List<String>> expandItemsPaths,
      final String propertyName) {
    Set<List<String>> reducedPaths = new HashSet<>();
    for (final List<String> path : expandItemsPaths) {
      if (propertyName.equals(path.get(0))) {
        if (path.size() > 1) {
          reducedPaths.add(path.subList(1, path.size()));
        }
      } else {
        reducedPaths.add(path);
      }
    }
    return reducedPaths.isEmpty() ? null : reducedPaths;
  }
  
  /**
   * Fetches the expand Item depending upon the type
   * @param expandItems
   * @param propertyName
   * @param type
   * @param resourceName
   * @return
   */
  public static ExpandItem getExpandItemBasedOnType(final List<ExpandItem> expandItems, 
      final String propertyName, final EdmStructuredType type, String resourceName) {
    ExpandItem expandItem = null;
    for (final ExpandItem item : expandItems) {
      boolean matched = false;
      if (item.isStar()) {
          continue;
      }
      final List<UriResource> resourceParts = item.getResourcePath().getUriResourceParts();
      UriResource resource = null;
      if (resourceParts.size() == 1) {
        resource = resourceParts.get(0);
        matched = true;
        expandItem = getMatchedExpandItem(propertyName, item, matched, resource);
      } else if (resourceParts.get(resourceParts.size() - 1) instanceof UriResourceRef ||
          resourceParts.get(resourceParts.size() - 1) instanceof UriResourceCount) {
        if (resourceParts.size() == 2) {
          resource = resourceParts.get(0);
          matched = true;
          expandItem = getMatchedExpandItem(propertyName, item, matched, resource);
        } else {
          resource = resourceParts.get(resourceParts.size() - 3);
          matched = resource.getSegmentValue().equalsIgnoreCase(resourceName) ? 
              isFoundExpandItem(type, matched, resource) : false;
          expandItem = getMatchedExpandItem(propertyName, item, matched, resourceParts.get(resourceParts.size() - 2));
        }
      } else {
        resource = resourceParts.get(resourceParts.size() - 2);
        matched = resource.getSegmentValue().equalsIgnoreCase(resourceName) ? 
            isFoundExpandItem(type, matched, resource) : false;
        expandItem = getMatchedExpandItem(propertyName, item, matched, resourceParts.get(resourceParts.size() - 1));
      }
      if (expandItem != null) {
        return expandItem;
      }
    }
    return expandItem;
  }

  /**
   * @param propertyName
   * @param item
   * @param matched
   * @param resource
   */
  private static ExpandItem getMatchedExpandItem(final String propertyName, final ExpandItem item, boolean matched,
      UriResource resource) {
    if (matched && ((resource instanceof UriResourceNavigation
        && propertyName.equals(((UriResourceNavigation) resource).getProperty().getName())) ||
        resource instanceof UriResourceProperty
        && propertyName.equals(((UriResourceProperty) resource).getProperty().getName()))) {
      return item;
    }
    return null;
  }

  /**
   * @param type
   * @param matched
   * @param resource
   * @return
   */
  private static boolean isFoundExpandItem(final EdmStructuredType type, 
      boolean matched, UriResource resource) {
    if (!matched) {
      if ((resource instanceof UriResourceProperty && 
              type.compatibleTo(((UriResourceProperty) resource).getType())) ||
          (resource instanceof UriResourceNavigation && 
              type.compatibleTo(((UriResourceNavigation) resource).getType()))) {
        matched = true;
      }
    }
    return matched;
  }
}