/*
 * Copyright (C) 2000 - 2024 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of the GPL, you may
 * redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS")
 * applications as described in Silverpeas's FLOSS exception. You should have received a copy of the
 * text describing the FLOSS exception, and it is also available here:
 * "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see <https://www.gnu.org/licenses/>.
 */

package org.silverpeas.components.mydb.model;

import org.silverpeas.components.mydb.service.MyDBConnectionInfoService;
import org.silverpeas.components.mydb.service.MyDBException;
import org.silverpeas.core.persistence.datasource.model.identifier.UniqueIntegerIdentifier;
import org.silverpeas.core.persistence.datasource.model.jpa.BasicJpaEntity;
import org.silverpeas.kernel.util.StringUtil;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * Information about a connexion to a data source. Such information is the name of the
 * data source and the credentials required to access that data source.
 * @author mmoquillon
 */
@Entity
@NamedQuery(name = "MyDBConnectionInfo.findByInstanceId",
    query = "select ds from MyDBConnectionInfo ds where ds.instanceId = :instanceId")
@NamedQuery(name = "MyDBConnectionInfo.deleteByInstanceId",
    query = "delete from MyDBConnectionInfo d where d.instanceId = :instanceId")
@Table(name = "sc_mydb_connectinfo")
public class MyDBConnectionInfo
    extends BasicJpaEntity<MyDBConnectionInfo, UniqueIntegerIdentifier> {

  @Column(length = 250, nullable = false)
  @NotNull
  private String dataSource;
  @Column(length = 250)
  private String login;
  @Column(length = 250)
  private String password;
  @Column(length = 100)
  private String tableName;
  private int rowLimit = 0;
  @Column(length = 50, nullable = false)
  @NotNull
  private String instanceId;

  public static List<MyDBConnectionInfo> getFromComponentInstance(String instanceId) {
    return MyDBConnectionInfoService.get().getConnectionInfoList(instanceId);
  }

  public static void removeFromComponentInstance(String instanceId) {
    MyDBConnectionInfoService.get().removeConnectionInfoOfComponentInstance(instanceId);
  }

  protected MyDBConnectionInfo() {
  }

  public MyDBConnectionInfo(String dataSource, String instanceId) {
    this.dataSource = dataSource;
    this.instanceId = instanceId;
  }

  /**
   * Is this connection information defined? Information about a connection to a data source is
   * defined if both it is related to a myDB application instance and the name of the
   * data source is defined.
   * @return true if the connection is defined. False otherwise.
   */
  public boolean isDefined() {
    return StringUtil.isDefined(this.dataSource) && StringUtil.isDefined(this.instanceId);
  }

  /**
   * Sets the default table to defaultTable from the database with this connection information
   * and returns
   * the later.
   * @param tableName the name of the table to load by default.
   * @return itself.
   */
  public MyDBConnectionInfo withDefaultTableName(String tableName) {
    setDefaultTableName(tableName.toLowerCase());
    return this;
  }

  public MyDBConnectionInfo withoutAnyDefaultTable() {
    setDefaultTableName(null);
    return this;
  }

  /**
   * Sets the maximum number of data to select when requesting the data source and returns this
   * connection information.
   * @param maxNumber the maximum number of data to return. 0 means all.
   * @return itself.
   */
  public MyDBConnectionInfo withDataMaxNumber(int maxNumber) {
    setDataMaxNumber(maxNumber);
    return this;
  }

  public MyDBConnectionInfo withLoginAndPassword(String login, String password) {
    setLoginAndPassword(login, password);
    return this;
  }

  /**
   * Sets a new data source by its JNDI name to this connection info.
   * @param dataSourceName the JNDI name of the data source to connect to.
   * @return itself.
   */
  public MyDBConnectionInfo withDataSourceName(String dataSourceName) {
    setDataSourceName(dataSourceName);
    return this;
  }

  /**
   * Gets the JNDI name of the data source targeted by this connection information.
   * @return the JNDI name of the data source.
   */
  public String getDataSourceName() {
    return dataSource;
  }

  /**
   * Gets the user identifier used in the data source authentication.
   * @return the login.
   */
  public String getLogin() {
    return (login == null ? "" : login);
  }

  /**
   * Gets the password associated with the login to connect the data source.
   * @return the password.
   */
  public String getPassword() {
    return (password == null ? "" : password);
  }

  /**
   * Gets the name of the table to load with this connexion information.
   * @return the name of the default table.
   */
  public String getDefaultTableName() {
    return tableName;
  }

  /**
   * Gets the maximum number of data to return when requesting the data source.
   * @return the maximum number of data to consider. 0 means all.
   */
  public int getDataMaxNumber() {
    return rowLimit;
  }

  /**
   * Is the name of the default table to load is defined?
   * @return true if the name of a default table is set, false otherwise.
   */
  public boolean isDefaultTableNameDefined() {
    return StringUtil.isDefined(tableName);
  }

  /**
   * Sets a new data source by its JNDI name to this connection info.
   * @param dataSourceName the JNDI name of the data source to connect to.
   */
  public void setDataSourceName(String dataSourceName) {
    this.dataSource = dataSourceName;
  }

  /**
   * Sets the login and the password required to open a connection to the targeted data source.
   * @param login a login.
   * @param password the password associated with the login.
   */
  public void setLoginAndPassword(String login, String password) {
    this.login = login;
    this.password = password;
  }

  /**
   * Sets the name of the default table to load with this connection information.
   * @param tableName the name of a table in the database.
   */
  public void setDefaultTableName(String tableName) {
    this.tableName = tableName;
  }

  /**
   * Sets the maximum number of data to select when requesting the data source.
   * @param maxNumber the maximum number of data to return. 0 means all.
   */
  public void setDataMaxNumber(int maxNumber) {
    this.rowLimit = Math.max(maxNumber, 0);
  }

  /**
   * Saves or updates this connection information into the persistence context in order to be able
   * to retrieve it later.
   */
  public void save() {
    MyDBConnectionInfoService.get().saveConnectionInfo(this);
  }

  /**
   * Opens a connection to the data source targeted by this connection information.
   * @return a connection against the data source referred by this object.
   */
  public Connection openConnection() throws MyDBException {
    try {
      DataSource ds = InitialContext.doLookup(getDataSourceName());
      return ds.getConnection(getLogin(), getPassword());
    } catch (NamingException | SQLException ex) {
      throw new MyDBException(ex.getMessage());
    }
  }

  @Override
  public boolean equals(final Object o) {
    return super.equals(o);
  }

  @Override
  public int hashCode() {
    return super.hashCode();
  }

  /**
   * Checks the connection with the remote data source referred in this
   * {@link MyDBConnectionInfo} instance.
   * <p>
   * If the connection cannot be established with the data source, a
   * {@link org.silverpeas.components.mydb.service.MyDBException}
   * exception is thrown. This occurs when:
   * </p>
   * <ul>
   * <li>no connection information is set for the underlying component instance,</li>
   * <li>the connection information isn't correct,</li>
   * <li>the connection information is correct but the connection with the data source fails at
   * this time.</li>
   * </ul>
   * @throws MyDBException if the connection cannot be established with the data source
   * referred by this {@link MyDBConnectionInfo} instance.
   */
  public void checkConnection() throws MyDBException {
    try (Connection connection = openConnection()) {
      if (!connection.isValid(0)) {
        throw new MyDBException("No valid connexion with the data source " + getDataSourceName());
      }
    } catch (SQLException e) {
      throw new MyDBException(e);
    }
  }
}
