/**
 * This file is part of Eclipse Steady.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.backend.repo;

import java.util.List;

import org.eclipse.steady.backend.model.Application;
import org.eclipse.steady.backend.model.Dependency;
import org.eclipse.steady.backend.model.DependencyIntersection;
import org.eclipse.steady.backend.model.Space;
import org.eclipse.steady.backend.util.ResultSetFilter;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

// TODO: Make read-only, as construct ids should only be created by other APIs
/**
 * <p>DependencyRepository interface.</p>
 */
@RepositoryRestResource(collectionResourceRel = "dependencies", path = "dependencies")
public interface DependencyRepository
    extends PagingAndSortingRepository<Dependency, Long>, DependencyRepositoryCustom {

  /** Constant <code>FILTER</code> */
  public static final ResultSetFilter<Dependency> FILTER = new ResultSetFilter<Dependency>();

  /**
   * <p>findByDigest.</p>
   *
   * @param lib a {@link java.lang.String} object.
   * @return a {@link java.util.List} object.
   */
  @Query("SELECT dep FROM Dependency dep JOIN FETCH dep.lib l WHERE l.digest = :lib")
  List<Dependency> findByDigest(@Param("lib") String lib);

  /**
   * <p>findByAppAndLib.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @param digest a {@link java.lang.String} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "SELECT dep FROM Dependency dep JOIN FETCH dep.lib l WHERE dep.app = :app AND dep.lib.digest"
          + " = :digest")
  List<Dependency> findByAppAndLib(@Param("app") Application app, @Param("digest") String digest);

  /**
   * <p>findByAppAndLibAndParentAndRelPath.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @param digest a {@link java.lang.String} object.
   * @param dep a {@link org.eclipse.steady.backend.model.Dependency} object.
   * @param relPath a {@link java.lang.String} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "SELECT dep FROM Dependency dep JOIN FETCH dep.lib l WHERE (dep.app = :app AND"
          + " dep.lib.digest = :digest AND dep.parent = :parent AND dep.relativePath = :relPath)"
          + " OR (dep.app = :app AND dep.lib.digest = :digest AND dep.parent is null AND"
          + " dep.relativePath = :relPath) OR (dep.app = :app AND dep.lib.digest = :digest AND"
          + " dep.parent is null AND dep.relativePath is null) OR (dep.app = :app AND"
          + " dep.lib.digest = :digest AND dep.parent = :parent AND dep.relativePath is null ) ")
  List<Dependency> findByAppAndLibAndParentAndRelPath(
      @Param("app") Application app,
      @Param("digest") String digest,
      @Param("parent") Dependency dep,
      @Param("relPath") String relPath);

  /**
   * <p>findByApp.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @return a {@link java.util.List} object.
   */
  @Query("SELECT dep FROM Dependency dep JOIN FETCH dep.lib l WHERE dep.app = :app")
  List<Dependency> findByApp(@Param("app") Application app);

  /**
   * <p>findByGAV.</p>
   *
   * @param group a {@link java.lang.String} object.
   * @param artifact a {@link java.lang.String} object.
   * @param version a {@link java.lang.String} object.
   * @param space a {@link org.eclipse.steady.backend.model.Space} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "    SELECT dep FROM  Dependency dep  JOIN FETCH dep.lib l LEFT OUTER JOIN FETCH l.libraryId"
          + "  JOIN dep.app WHERE dep.app.mvnGroup = :mvnGroup AND dep.app.artifact = :artifact"
          + " AND dep.app.version = :version AND dep.app.space = :space")
  List<Dependency> findByGAV(
      @Param("mvnGroup") String group,
      @Param("artifact") String artifact,
      @Param("version") String version,
      @Param("space") Space space);

  /**
   * Finds all {@link DependencyIntersection}s for a given {@link Application}.
   *
   * @param group a {@link java.lang.String} object.
   * @param artifact a {@link java.lang.String} object.
   * @param version a {@link java.lang.String} object.
   * @param space a {@link org.eclipse.steady.backend.model.Space} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "SELECT   new org.eclipse.steady.backend.model.DependencyIntersection(d1, d2, COUNT(lc1))"
          + " FROM 	    Dependency d1      JOIN      d1.app a1 	    JOIN      d1.lib l1     JOIN   "
          + "   l1.constructs lc1, 	    Dependency d2      JOIN      d2.app a2 	    JOIN     "
          + " d2.lib l2     JOIN      l2.constructs lc2 	  WHERE a1.mvnGroup ="
          + " :mvnGroup " // Given application
          + "     AND a1.artifact = :artifact "
          + "     AND a1.version  = :version "
          + "     AND a1.space  = :space "
          + "     AND a1.mvnGroup = a2.mvnGroup " // Same application
          + "     AND a1.artifact = a2.artifact "
          + "     AND a1.version  = a2.version "
          + "     AND a1.space  = a2.space "
          + "     AND lc1.type = 'CLAS'" // Only classes matter
          + "     AND lc2.type = 'CLAS'"
          + "     AND lc1.qname = lc2.qname " // Same qname
          + "     AND l1.libraryId <> l2.libraryId " // From different libs
  )
  List<DependencyIntersection> findDepIntersections(
      @Param("mvnGroup") String group,
      @Param("artifact") String artifact,
      @Param("version") String version,
      @Param("space") Space space);

  /**
   * <p>findWithBundledByApp.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "SELECT distinct dep FROM Dependency dep JOIN FETCH dep.lib l JOIN FETCH l.bundledLibraryIds"
          + "  lb WHERE dep.app = :app AND lb IS NOT NULL ")
  List<Dependency> findWithBundledByApp(@Param("app") Application app);

  /**
   * <p>findWithDifferentBundledLibByApp.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @return a {@link java.util.List} object.
   */
  @Query(
      "SELECT distinct dep FROM Dependency dep   JOIN FETCH dep.lib l   JOIN FETCH"
          + " l.bundledLibraryIds lb   WHERE dep.app = :app AND lb IS NOT NULL AND NOT"
          + " (SIZE(l.bundledLibraryIds)=1 AND l.libraryId MEMBER OF l.bundledLibraryIds) ")
  List<Dependency> findWithDifferentBundledLibByApp(@Param("app") Application app);
}
