/*
 * 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.orm.entities;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.view.ViewSubResourceDefinition;
import org.apache.ambari.server.view.configuration.ParameterConfig;
import org.apache.ambari.server.view.configuration.ResourceConfig;
import org.apache.ambari.server.view.configuration.ViewConfig;
import org.apache.ambari.view.View;
import org.apache.ambari.view.ViewDefinition;
import org.apache.ambari.view.validation.Validator;

/**
 * Entity representing a View.
 */
@Table(name = "viewmain")
@NamedQuery(name = "allViews",
    query = "SELECT view FROM ViewEntity view")
@Entity
public class ViewEntity implements ViewDefinition {

  public static final String AMBARI_ONLY = "AMBARI-ONLY";

  /**
   * The unique view name.
   */
  @Id
  @Column(name = "view_name", nullable = false, insertable = true,
      updatable = false, unique = true, length = 100)
  private String name;

  /**
   * The public view name.
   */
  @Column
  @Basic
  private String label;

  /**
   * The view description.
   */
  @Column
  @Basic
  private String description;

  /**
   * The icon path.
   */
  @Column
  @Basic
  private String icon;

  /**
   * The big icon path.
   */
  @Column
  @Basic
  private String icon64;

  /**
   * The view version.
   */
  @Column
  @Basic
  private String version;

  /**
   * The view build number.
   */
  @Column
  @Basic
  private String build;

  /**
   * The view archive.
   */
  @Column
  @Basic
  private String archive;

  /**
   * The masker class for parameters.
   */
  @Column
  @Basic
  private String mask;

  /**
   * Indicates whether or not this is a system view.
   */
  @Column(name = "system_view")
  @Basic
  private Integer system;

  /**
  * The list of view parameters.
  */
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "view")
  private Collection<ViewParameterEntity> parameters = new HashSet<>();

  /**
   * The list of view resources.
   */
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "view")
  private Collection<ViewResourceEntity> resources = new HashSet<>();

   /**
   * The list of view instances.
   */
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "view")
  private Collection<ViewInstanceEntity> instances = new HashSet<>();

  /**
   * The list of view permissions.
   */
  @OneToMany(cascade = CascadeType.ALL)
  @JoinColumns({
      @JoinColumn(name = "resource_type_id", referencedColumnName = "resource_type_id", nullable = false)
  })
  private Collection<PermissionEntity> permissions = new HashSet<>();

  /**
   * The resource type.
   */
  @ManyToOne(cascade = CascadeType.ALL)
  @JoinColumns({
      @JoinColumn(name = "resource_type_id", referencedColumnName = "resource_type_id", nullable = false)
  })
  private ResourceTypeEntity resourceType;

  // ----- Transient data ----------------------------------------------------

  /**
   * The associated view configuration.
   */
  @Transient
  private ViewConfig configuration;

  /**
   * The Ambari configuration properties.
   */
  @Transient
  private final Configuration ambariConfiguration;

  /**
   * The external resource type for the view.
   */
  @Transient
  private final Resource.Type externalResourceType;

  /**
   * The classloader used to load the view.
   */
  @Transient
  private ClassLoader classLoader = null;

  /**
   * The mapping of resource type to resource provider.
   */
  @Transient
  private final Map<Resource.Type, ResourceProvider> resourceProviders = new HashMap<>();

  /**
   * The mapping of resource type to resource definition.
   */
  @Transient
  private final Map<Resource.Type, ViewSubResourceDefinition> resourceDefinitions = new HashMap<>();

  /**
   * The mapping of resource type to resource configuration.
   */
  @Transient
  private final Map<Resource.Type, ResourceConfig> resourceConfigurations = new HashMap<>();

  /**
   * The name of the view shared across versions.
   */
  @Transient
  private String commonName = null;

  /**
   * The view.
   */
  @Transient
  private View view = null;

  /**
   * The view validator.
   */
  @Transient
  private Validator validator = null;

  /**
   * The view status.
   */
  @Transient
  private ViewStatus status = ViewStatus.PENDING;

  /**
   * The view status detail.
   */
  @Transient
  private String statusDetail;

  /**
   * Indicates whether or not this view is configurable through cluster association.
   */
  @Transient
  private boolean clusterConfigurable;


  // ----- Constructors ------------------------------------------------------

  /**
   * Construct a view entity.
   */
  public ViewEntity() {
    this.configuration        = null;
    this.ambariConfiguration  = null;
    this.archive              = null;
    this.externalResourceType = null;
    this.system               = 0;
    this.clusterConfigurable  = false;
  }

  /**
   * Construct a view entity from the given configuration.
   *
   * @param configuration        the view configuration
   * @param ambariConfiguration  the Ambari configuration
   * @param archivePath          the path of the view archive
   */
  public ViewEntity(ViewConfig configuration, Configuration ambariConfiguration,
                    String archivePath) {
    setConfiguration(configuration);

    this.ambariConfiguration = ambariConfiguration;
    this.archive             = archivePath;

    String version = configuration.getVersion();

    this.name        = getViewName(configuration.getName(), version);
    this.label       = configuration.getLabel();
    this.description = configuration.getDescription();
    this.version     = version;
    this.build       = configuration.getBuild();

    this.mask        = configuration.getMasker();
    this.icon        = configuration.getIcon();
    this.icon64      = configuration.getIcon64();
    this.system      = configuration.isSystem() ? 1 : 0;

    this.externalResourceType =
        new Resource.Type(getQualifiedResourceTypeName(ResourceConfig.EXTERNAL_RESOURCE_PLURAL_NAME));
  }


  // ----- ViewDefinition ----------------------------------------------------

  @Override
  public String getViewName() {
    return getCommonName();
  }

  @Override
  public String getLabel() {
    return label;
  }

  @Override
  public String getDescription() {
    return description;
  }

  @Override
  public String getVersion() {
    return version;
  }

  @Override
  public String getBuild() {
    return build;
  }

  @Override
  public ViewStatus getStatus() {
    return status;
  }

  @Override
  public String getStatusDetail() {
    return statusDetail;
  }

  @Override
  public String getMask() {
    return mask;
  }


  // ----- ViewEntity --------------------------------------------------------

  /**
   * Get the view name.
   *
   * @return the view name
   */
  public String getName() {
    return name;
  }

  /**
   * Set the view name.
   *
   * @param name the view name
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Get the common name of the view.
   * This name is shared across versions of the view.
   *
   * @return the common name
   */
  public synchronized String getCommonName() {
    if (commonName == null) {
      // Strip version from the internal name
      commonName = name.replaceAll("\\{(.+)\\}", "");
    }
    return commonName;
  }

  /**
   * Set the view label (display name).
   *
   * @param label the view label
   */
  public void setLabel(String label) {
    this.label = label;
  }

  /**
   * Set the view description.
   *
   * @param description the view description
   */
  public void setDescription(String description) {
    this.description = description;
  }

  /**
   * Set the view version.
   *
   * @param version the version
   */
  public void setVersion(String version) {
    this.version = version;
  }

  /**
   * Set the view build number.
   *
   * @param build the build
   */
  public void setBuild(String build) {
    this.build = build;
  }

  /**
   * Get the icon path.
   *
   * @return the icon path
   */
  public String getIcon() {
    return icon;
  }

  /**
   * Set the icon path.
   *
   * @param icon  the icon path
   */
  public void setIcon(String icon) {
    this.icon = icon;
  }

  /**
   * Get the big icon path.
   *
   * @return the big icon path
   */
  public String getIcon64() {
    return icon64;
  }

  /**
   * Set the big icon path.
   *
   * @param icon64  the big icon path
   */
  public void setIcon64(String icon64) {
    this.icon64 = icon64;
  }

  /**
   * Get the view parameters.
   *
   * @return the view parameters
   */
  public Collection<ViewParameterEntity> getParameters() {
    return parameters;
  }

  /**
   * Set the view parameters.
   *
   * @param parameters  the view parameters
   */
  public void setParameters(Collection<ViewParameterEntity> parameters) {
    this.parameters = parameters;
  }

  /**
   * Get the view custom permissions.
   *
   * @return the view permissions
   */
  public Collection<PermissionEntity> getPermissions() {
    return permissions;
  }

  /**
   * Set the custom view permissions.
   *
   * @param permissions  the permissions
   */
  public void setPermissions(Collection<PermissionEntity> permissions) {
    this.permissions = permissions;
  }

  /**
   * Get the permission entity for the given permission name.
   *
   * @param permissionName  the permission name
   *
   * @return the matching permission entity or null
   */
  public PermissionEntity getPermission(String permissionName) {

    for (PermissionEntity permissionEntity : permissions) {
      if (permissionEntity.getPermissionName().equals(permissionName)) {
        return permissionEntity;
      }
    }
    return null;
  }

  /**
   * Get the view resources.
   *
   * @return the view resources
   */
  public Collection<ViewResourceEntity> getResources() {
    return resources;
  }

  /**
   * Set the view resources.
   *
   * @param resources  the view resources
   */
  public void setResources(Collection<ViewResourceEntity> resources) {
    this.resources = resources;
  }

  /**
   * Get the view instances.
   *
   * @return the view instances
   */
  public Collection<ViewInstanceEntity> getInstances() {
    return instances;
  }

  /**
   * Set the view instances.
   *
   * @param instances  the instances
   */
  public void setInstances(Collection<ViewInstanceEntity> instances) {
    this.instances = instances;
  }

  /**
   * Add an instance definition.
   *
   * @param viewInstanceDefinition  the instance definition
   */
  public void addInstanceDefinition(ViewInstanceEntity viewInstanceDefinition) {
    removeInstanceDefinition(viewInstanceDefinition.getName());
    instances.add(viewInstanceDefinition);
  }

  /**
   * Remove an instance definition.
   *
   * @param instanceName  the instance name
   */
  public void removeInstanceDefinition(String instanceName) {
    ViewInstanceEntity entity = getInstanceDefinition(instanceName);
    if (entity != null) {
      instances.remove(entity);
    }
  }

  /**
   * Get an instance definition for the given name.
   *
   * @param instanceName  the instance name
   *
   * @return the instance definition
   */
  public ViewInstanceEntity getInstanceDefinition(String instanceName) {
    for (ViewInstanceEntity viewInstanceEntity : instances) {
      if (viewInstanceEntity.getName().equals(instanceName)) {
        return viewInstanceEntity;
      }
    }
    return null;
  }

  /**
   * Get the path of the view archive.
   *
   * @return  the path of the view archive
   */
  public String getArchive() {
    return archive;
  }

  /**
   * Set the view archive path.
   *
   * @param archive  the view archive path
   */
  public void setArchive(String archive) {
    this.archive = archive;
  }

  /**
   * Get a property for the given key from the ambari configuration.
   *
   * @param key  the property key
   *
   * @return the property value; null indicates that the configuration contains no mapping for the key
   */
  public String getAmbariProperty(String key) {
    return ambariConfiguration.getProperty(key);
  }

  /**
   * Get the Ambari configuration.
   *
   * @return the Ambari configuration
   */
  public Configuration getAmbariConfiguration() {
    return ambariConfiguration;
  }

  /**
   * Get a resource name qualified by the associated view name.
   *
   * @param resourceTypeName  the resource type name
   *
   * @return the qualified resource name
   */
  public String getQualifiedResourceTypeName(String resourceTypeName) {
    return getName() + "/" + resourceTypeName;
  }

  /**
   * Get the external resource type for the view.
   *
   * @return the external resource type
   */
  public Resource.Type getExternalResourceType() {
    return externalResourceType;
  }

  /**
   * Get the class loader used to load the view classes.
   *
   * @return the class loader
   */
  public ClassLoader getClassLoader() {
    return classLoader;
  }

  /**
   * Set the class loader.
   *
   * @param classLoader  the class loader
   */
  public void setClassLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
  }

  /**
   * Add a resource provider for the given type.
   *
   * @param type      the resource type
   * @param provider  the resource provider
   */
  public void addResourceProvider(Resource.Type type, ResourceProvider provider) {
    resourceProviders.put(type, provider);
  }

  /**
   * Get the resource provider for the given type.
   *
   * @param type  the resource type
   *
   * @return the resource provider associated with the given type
   */
  public ResourceProvider getResourceProvider(Resource.Type type) {
    return resourceProviders.get(type);
  }

  /**
   * Add a resource definition.
   *
   * @param definition  the resource definition
   */
  public void addResourceDefinition(ViewSubResourceDefinition definition) {
    resourceDefinitions.put(definition.getType(), definition);
  }

  /**
   * Get the resource definition for the given type.
   *
   * @param type  the resource type
   *
   * @return the resource definition associated with the given type
   */
  public ViewSubResourceDefinition getResourceDefinition(Resource.Type type) {
    return resourceDefinitions.get(type);
  }

  /**
   * Get the mapping of resource type to resource definitions.
   *
   * @return the mapping of resource type to resource definitions
   */
  public Map<Resource.Type, ViewSubResourceDefinition> getResourceDefinitions() {
    return resourceDefinitions;
  }

  /**
   * Add a resource configuration for the given type.
   *
   * @param type    the resource type
   * @param config  the configuration
   */
  public void addResourceConfiguration(Resource.Type type, ResourceConfig config) {
    resourceConfigurations.put(type, config);
  }

  /**
   * Get the mapping of resource type to resource configurations.
   *
   * @return the mapping of resource types to resource configurations
   */
  public Map<Resource.Type, ResourceConfig> getResourceConfigurations() {
    return resourceConfigurations;
  }

  /**
   * Get the set of resource types for this view.
   *
   * @return the set of resource type
   */
  public Set<Resource.Type> getViewResourceTypes() {
    return resourceProviders.keySet();
  }

  /**
   * Set the view configuration.
   *
   * @param configuration  the view configuration
   */
  public void setConfiguration(ViewConfig configuration) {
    this.configuration       = configuration;
    this.clusterConfigurable = false;

    if(configuration.getClusterConfigOptions() != null &&
       configuration.getClusterConfigOptions().equals(AMBARI_ONLY)){
      this.clusterConfigurable = true;
      return;
    }

    // if any of the parameters contain a cluster config element then the view is cluster configurable
    for (ParameterConfig parameterConfig : configuration.getParameters()) {
      String clusterConfig = parameterConfig.getClusterConfig();
      if (clusterConfig != null && !clusterConfig.isEmpty()) {
        this.clusterConfigurable = true;
        return;
      }
    }
  }

  /**
   * Get the associated view configuration.
   *
   * @return the configuration
   */
  public ViewConfig getConfiguration() {
    return configuration;
  }

  /**
   * Set the view.
   *
   * @param view  the view
   */
  public void setView(View view) {
    this.view = view;
  }

  /**
   * Get the associated view.
   *
   * @return the view
   */
  public View getView() {
    return view;
  }

  /**
   * Set the view validator.
   *
   * @param validator  the view validator
   */
  public void setValidator(Validator validator) {
    this.validator = validator;
  }

  /**
   * Get the associated view validator.
   *
   * @return the view validator
   */
  public Validator getValidator() {
    return validator;
  }

  /**
   * Determine whether or not a validator has been specified for this view.
   *
   * @return true if this view has a validator
   */
  public boolean hasValidator() {
    return validator != null;
  }

  /**
   * Set the mask class name.
   *
   * @param mask the mask class name
   */
  public void setMask(String mask) {
    this.mask = mask;
  }

  /**
   * Determine whether or not the view is a system view.
   *
   * @return true if the view is a system view
   */
  public boolean isSystem() {
    return system == 1;
  }

  /**
   * Set the flag which indicates whether or not the view is a system view.
   *
   * @param required  the system flag; true if the view is a system view
   */
  public void setSystem(boolean required) {
    this.system = required ? 1 : 0;
  }

  /**
   * Get the admin resource type entity.
   *
   * @return the resource type entity
   */
  public ResourceTypeEntity getResourceType() {
    return resourceType;
  }

  /**
   * Set the admin resource type entity.
   *
   * @param resourceType  the resource type entity
   */
  public void setResourceType(ResourceTypeEntity resourceType) {
    this.resourceType = resourceType;
  }

  /**
   * Set the status of the view.
   *
   * @param status  the view status
   */
  public void setStatus(ViewStatus status) {
    this.status = status;
  }

  /**
   * Set the status detail for the view.
   *
   * @param statusDetail  the status detail
   */
  public void setStatusDetail(String statusDetail) {
    this.statusDetail = statusDetail;
  }

  /**
   * Determine whether or not this view is configurable through a cluster association.
   *
   * @return true if this view is cluster configurable
   */
  public boolean isClusterConfigurable() {
    return clusterConfigurable;
  }

  /**
   * Determine whether or not the entity is deployed.
   *
   * @return true if the entity is deployed
   */
  public boolean isDeployed() {
    return status.equals(ViewStatus.DEPLOYED);
  }

  /**
   * Get the internal view name from the given common name and version.
   *
   * @param name     the view common name
   * @param version  the version
   *
   * @return the view name
   */
  public static String getViewName(String name, String version) {
    return name + "{" + version + "}";
  }

  @Override
  public String toString() {
    return "ViewEntity{" +
        "name='" + name + '\'' +
        ", label='" + label + '\'' +
        ", description='" + description + '\'' +
        '}';
  }
}
