/**
 * 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.ambari.server.agent;

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

import org.apache.ambari.annotations.Experimental;
import org.apache.ambari.annotations.ExperimentalFeature;
import org.apache.ambari.server.orm.entities.RepoDefinitionEntity;
import org.apache.ambari.server.state.RepositoryInfo;
import org.apache.ambari.server.state.stack.RepoTag;
import org.apache.ambari.server.utils.URLCredentialsHider;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.annotations.SerializedName;

/**
 * Wraps the information required to create repositories from a command.  This was added
 * as a top level command object.
 */
public class CommandRepository {

  @SerializedName("repositories")
  @JsonProperty("repositories")
  private List<Repository> m_repositories = new ArrayList<>();

  @SerializedName("repoVersion")
  @JsonProperty("repoVersion")
  private String m_repoVersion;

  @SerializedName("repoVersionId")
  @JsonProperty("repoVersionId")
  private long m_repoVersionId;

  @SerializedName("stackName")
  @JsonProperty("stackName")
  private String m_stackName;

  @SerializedName("repoFileName")
  @JsonProperty("repoFileName")
  private String m_repoFileName;

  @SerializedName("feature")
  @JsonProperty("feature")
  private final CommandRepositoryFeature feature = new CommandRepositoryFeature();

  /**
   * Provides {@link CommandRepository} feature
   *
   * @return {@link CommandRepositoryFeature}
   */
  public CommandRepositoryFeature getFeature(){
    return feature;
  }

  /**
   * {@code true} if Ambari believes that this repository has reported back it's
   * version after distribution.
   */
  @SerializedName("resolved")
  @JsonProperty("resolved")
  private boolean m_resolved;

  /**
   * @param version the repo version
   */
  public void setRepoVersion(String version) {
    m_repoVersion = version;
  }


  public String getRepoVersion(){
    return m_repoVersion;
  }

  /**
   * @param id the repository id
   */
  public void setRepositoryVersionId(long id) {
    m_repoVersionId = id;
  }

  /**
   * @param name the stack name
   */
  public void setStackName(String name) {
    m_stackName = name;
  }

  /**
   * @param repositories the repositories if sourced from the stack instead of the repo_version.
   */
  public void setRepositories(Collection<RepositoryInfo> repositories) {
    m_repositories = new ArrayList<>();

    for (RepositoryInfo info : repositories) {
      m_repositories.add(new Repository(info));
    }
  }

  /**
   * @param osType        the OS type for the repositories
   * @param repositories  the repository entities that should be processed into a file
   */
  public void setRepositories(String osType, Collection<RepoDefinitionEntity> repositories) {
    m_repositories = new ArrayList<>();

    for (RepoDefinitionEntity entity : repositories) {
      m_repositories.add(new Repository(osType, entity));
    }
  }

  /**
   * @return the repositories that the command should process into a file.
   */
  public Collection<Repository> getRepositories() {
    return m_repositories;
  }

  /**
   * Sets a uniqueness on the repo ids.
   *
   * @param suffix  the repo id suffix
   */
  public void setUniqueSuffix(String suffix) {
    for (Repository repo : m_repositories) {
      repo.m_repoId = repo.m_repoId + suffix;
    }
  }

  /**
   * Sets fields for non-managed
   */
  public void setNonManaged() {
    for (Repository repo : m_repositories) {
      repo.m_baseUrl = null;
      repo.m_mirrorsList = null;
      repo.m_ambariManaged = false;
    }
  }

  public long getRepoVersionId() {
    return m_repoVersionId;
  }

  /**
   * Gets whether this repository has had its version resolved.
   *
   * @param resolved
   *          {@code true} to mark this repository as being resolved.
   */
  public void setResolved(boolean resolved) {
    m_resolved = resolved;
  }

  /**
   * Update repository id to be consistent with old format
   *
   * @param repoVersion
   */
  @Deprecated
  @Experimental(feature= ExperimentalFeature.PATCH_UPGRADES)
  public void setLegacyRepoId(String repoVersion){
    for (Repository repo : m_repositories) {
      repo.m_repoId = String.format("%s-%s", repo.getRepoName(), repoVersion);
    }
  }

  /**
   * Sets filename for the repo
   *
   * @param stackName  name of the stack
   * @param repoVersion repository version
   */
  @Deprecated
  @Experimental(feature= ExperimentalFeature.PATCH_UPGRADES)
  public void setLegacyRepoFileName(String stackName, String repoVersion) {
    this.m_repoFileName = String.format("%s-%s", stackName, repoVersion);
  }

  /**
   * Sets filename for the repo
   *
   * @param stackName  name of the stack
   * @param repoVersionId repository version id
   */
  public void setRepoFileName(String stackName, Long repoVersionId) {
    this.m_repoFileName = String.format("ambari-%s-%s", stackName.toLowerCase(), repoVersionId.toString());
  }

  /**
   * Minimal information about repository feature
   */
  public static class CommandRepositoryFeature {

    /**
     * Repository is pre-installed on the host
     */
    @SerializedName("preInstalled")
    @JsonProperty("preInstalled")
    private Boolean m_isPreInstalled = false;

    /**
     * Indicates if any operation with the packages should be scoped to this repository only.
     *
     * Currently affecting: getting available packages from the repository
     */
    @SerializedName("scoped")
    @JsonProperty("scoped")
    private boolean m_isScoped = true;

    public void setIsScoped(boolean isScoped){
      this.m_isScoped = isScoped;
    }

    public void setPreInstalled(String isPreInstalled) {
      this.m_isPreInstalled = isPreInstalled.equalsIgnoreCase("true");
    }
  }

  /**
   * Minimal information required to generate repo files on the agent.  These are copies
   * of the repository objects from repo versions that can be changed for URL overrides, etc.
   */
  public static class Repository {

    @SerializedName("baseUrl")
    @JsonProperty("baseUrl")
    private String m_baseUrl;

    @SerializedName("repoId")
    @JsonProperty("repoId")
    private String m_repoId;

    @SerializedName("ambariManaged")
    @JsonProperty("ambariManaged")
    private boolean m_ambariManaged = true;

    @SerializedName("repoName")
    @JsonProperty("repoName")
    private final String m_repoName;

    @SerializedName("distribution")
    @JsonProperty("distribution")
    private final String m_distribution;

    @SerializedName("components")
    @JsonProperty("components")
    private final String m_components;

    @SerializedName("mirrorsList")
    @JsonProperty("mirrorsList")
    private String m_mirrorsList;

    @SerializedName("applicableServices")
    @Experimental(feature = ExperimentalFeature.CUSTOM_SERVICE_REPOS,
      comment = "Remove logic for handling custom service repos after enabling multi-mpack cluster deployment")
    private List<String> m_applicableServices;

    @SerializedName("tags")
    @JsonProperty("tags")
    private Set<RepoTag> m_tags;


    private transient String m_osType;

    private Repository(RepositoryInfo info) {
      m_baseUrl = info.getBaseUrl();
      m_osType = info.getOsType();
      m_repoId = info.getRepoId();
      m_repoName = info.getRepoName();
      m_distribution = info.getDistribution();
      m_components = info.getComponents();
      m_mirrorsList = info.getMirrorsList();
      m_applicableServices = info.getApplicableServices();
      m_tags = info.getTags();
    }

    private Repository(String osType, RepoDefinitionEntity entity) {
      m_baseUrl = entity.getBaseUrl();
      m_repoId = entity.getRepoID();
      m_repoName = entity.getRepoName();
      m_distribution = entity.getDistribution();
      m_components = entity.getComponents();
      m_mirrorsList = entity.getMirrors();
      m_applicableServices = entity.getApplicableServices();
      m_osType = osType;
      m_tags = entity.getTags();
    }

    public void setRepoId(String repoId){
      m_repoId = repoId;
    }

    public void setBaseUrl(String url) {
      m_baseUrl = url;
    }

    public String getRepoName() {
      return m_repoName;
    }

    public String getRepoId() {
      return m_repoId;
    }

    public String getBaseUrl() {
      return m_baseUrl;
    }

    public boolean isAmbariManaged() {
      return m_ambariManaged;
    }

    @Experimental(feature = ExperimentalFeature.CUSTOM_SERVICE_REPOS,
      comment = "Remove logic for handling custom service repos after enabling multi-mpack cluster deployment")
    public void setApplicableServices(List<String> applicableServices) {
      m_applicableServices = applicableServices;
    }

    @Experimental(feature = ExperimentalFeature.CUSTOM_SERVICE_REPOS,
      comment = "Remove logic for handling custom service repos after enabling multi-mpack cluster deployment")
    public List<String> getApplicableServices() {
      return m_applicableServices;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
      return new ToStringBuilder(null)
          .append("os", m_osType)
          .append("name", m_repoName)
          .append("distribution", m_distribution)
          .append("components", m_components)
          .append("id", m_repoId)
          .append("baseUrl", URLCredentialsHider.hideCredentials(m_baseUrl))
          .append("applicableServices", (m_applicableServices != null? StringUtils.join(m_applicableServices, ",") : ""))
          .toString();
    }
  }

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

    CommandRepository that = (CommandRepository) o;

    return m_repoVersionId == that.m_repoVersionId;
  }

  @Override
  public int hashCode() {
    return (int) (m_repoVersionId ^ (m_repoVersionId >>> 32));
  }
}
