/*
 * 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.activemq.artemis.ra.inflow;

import javax.jms.Session;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.InvalidPropertyException;
import javax.resource.spi.ResourceAdapter;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.Hashtable;
import java.util.Objects;

import org.apache.activemq.artemis.ra.ActiveMQRALogger;
import org.apache.activemq.artemis.ra.ActiveMQRaUtils;
import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter;
import org.apache.activemq.artemis.ra.ConnectionFactoryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * These properties are set on the MDB ActivationProperties
 */
public class ActiveMQActivationSpec extends ConnectionFactoryProperties implements ActivationSpec, Serializable {

   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

   private static final long serialVersionUID = -7997041053897964654L;

   private static final int DEFAULT_MAX_SESSION = 15;

   private static final boolean DEFAULT_SINGLE_CONNECTION = false;

   public String strConnectorClassName;

   public String strConnectionParameters;
   protected Boolean allowLocalTransactions;


   private ActiveMQResourceAdapter ra;

   private String connectionFactoryLookup;

   private String destination;

   private String destinationType;

   private String messageSelector;

   private Integer acknowledgeMode;

   private Boolean subscriptionDurability;

   private String subscriptionName;

   /**
    * If this is true, a durable subscription could be shared by multiple MDB instances
    */
   private Boolean shareSubscriptions = false;

   private String user;

   private String password;

   private Integer maxSession;

   private Boolean singleConnection = false;

   @Deprecated(forRemoval = true)
   private Integer transactionTimeout;

   private Boolean useJNDI = true;

   private String jndiParams = null;

   private Hashtable<String, String> parsedJndiParams;

   /**
    *  use local tx instead of XA
    */
   private Boolean localTx;

   /**
    * undefined by default, default is specified at the RA level in ActiveMQRAProperties
    */
   private Integer setupAttempts;

   /**
    * undefined by default, default is specified at the RA level in ActiveMQRAProperties
    */
   private Long setupInterval;

   private Boolean rebalanceConnections = false;

   /**
    * Enables backwards compatibility of the pre 2.x addressing model
    */
   private String topicPrefix;

   private String queuePrefix;

   public ActiveMQActivationSpec() {
      logger.trace("constructor()");

      // we create an Adapter here but only for Application Servers that do introspection on loading to avoid an NPE
      ra = new ActiveMQResourceAdapter();
      destination = null;
      destinationType = null;
      messageSelector = null;
      acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
      subscriptionDurability = false;
      subscriptionName = null;
      user = null;
      password = null;
      maxSession = DEFAULT_MAX_SESSION;
      transactionTimeout = 0;
   }

   @Override
   public ResourceAdapter getResourceAdapter() {
      logger.trace("getResourceAdapter()");

      return ra;
   }

   public Boolean isUseJNDI() {
      if (useJNDI == null) {
         return ra.isUseJNDI();
      }
      return useJNDI;
   }

   public void setUseJNDI(final Boolean value) {
      useJNDI = value;
   }

   public String getJndiParams() {
      if (jndiParams == null) {
         return ra.getJndiParams();
      }
      return jndiParams;
   }

   public void setJndiParams(String jndiParams) {
      this.jndiParams = jndiParams;
      parsedJndiParams = ActiveMQRaUtils.parseHashtableConfig(jndiParams);
   }

   public Hashtable<?, ?> getParsedJndiParams() {
      if (parsedJndiParams == null) {
         return ra.getParsedJndiParams();
      }
      return parsedJndiParams;
   }

   @Override
   public void setResourceAdapter(final ResourceAdapter ra) throws ResourceException {
      logger.trace("setResourceAdapter({})", ra);

      if (ra == null || !(ra instanceof ActiveMQResourceAdapter)) {
         throw new ResourceException("Resource adapter is " + ra);
      }

      this.ra = (ActiveMQResourceAdapter) ra;
   }

   public String getConnectionFactoryLookup() {
      logger.trace("getConnectionFactoryLookup() ->{}", connectionFactoryLookup);

      return connectionFactoryLookup;
   }

   public void setConnectionFactoryLookup(final String value) {
      logger.trace("setConnectionFactoryLookup({})", value);

      connectionFactoryLookup = value;
   }

   public String getDestination() {
      logger.trace("getDestination()");

      return destination;
   }

   public void setDestination(final String value) {
      logger.trace("setDestination({})", value);

      destination = value;
   }

   public String getDestinationLookup() {
      return getDestination();
   }

   public void setDestinationLookup(final String value) {
      setDestination(value);
      setUseJNDI(true);
   }

   public String getDestinationType() {
      logger.trace("getDestinationType()");

      return destinationType;
   }

   public void setDestinationType(final String value) {
      logger.trace("setDestinationType({})", value);

      destinationType = value;
   }

   public String getMessageSelector() {
      logger.trace("getMessageSelector()");

      return messageSelector;
   }

   public void setMessageSelector(final String value) {
      logger.trace("setMessageSelector({})", value);

      messageSelector = value;
   }

   public String getAcknowledgeMode() {
      logger.trace("getAcknowledgeMode()");

      if (Session.DUPS_OK_ACKNOWLEDGE == acknowledgeMode) {
         return "Dups-ok-acknowledge";
      } else {
         return "Auto-acknowledge";
      }
   }

   public void setQueuePrefix(String prefix) {
      this.queuePrefix = prefix;
   }

   public String getQueuePrefix() {
      return queuePrefix;
   }

   public void setTopicPrefix(String prefix) {
      this.topicPrefix = prefix;
   }

   public String getTopicPrefix() {
      return topicPrefix;
   }

   public void setAcknowledgeMode(final String value) {
      logger.trace("setAcknowledgeMode({})", value);

      try {
         this.acknowledgeMode = ActiveMQActivationValidationUtils.validateAcknowledgeMode(value);
      } catch (IllegalArgumentException e) {
         ActiveMQRALogger.LOGGER.invalidAcknowledgementMode(value);
         throw e;
      }
   }

   public Integer getAcknowledgeModeInt() {
      logger.trace("getAcknowledgeMode()");

      return acknowledgeMode;
   }

   public String getSubscriptionDurability() {
      logger.trace("getSubscriptionDurability()");

      if (subscriptionDurability) {
         return "Durable";
      } else {
         return "NonDurable";
      }
   }

   public void setSubscriptionDurability(final String value) {
      logger.trace("setSubscriptionDurability({})", value);

      subscriptionDurability = "Durable".equals(value);
   }

   public Boolean isSubscriptionDurable() {
      logger.trace("isSubscriptionDurable()");

      return subscriptionDurability;
   }

   public String getSubscriptionName() {
      logger.trace("getSubscriptionName()");

      return subscriptionName;
   }

   public void setSubscriptionName(final String value) {
      logger.trace("setSubscriptionName({})", value);

      subscriptionName = value;
   }

   public Boolean isShareSubscriptions() {
      logger.trace("isShareSubscriptions() = {}", shareSubscriptions);

      return shareSubscriptions;
   }

   public void setShareSubscriptions(final Boolean shareSubscriptions) {
      logger.trace("setShareSubscriptions({})", shareSubscriptions);

      this.shareSubscriptions = shareSubscriptions;
   }

   public String getUser() {
      logger.trace("getUser()");

      if (user == null) {
         return ra.getUserName();
      } else {
         return user;
      }
   }

   public void setUser(final String value) {
      logger.trace("setUser()", value);

      user = value;
   }

   public String getUserName() {
      if (logger.isTraceEnabled()) {
         logger.trace("getUserName()");
      }

      if (user == null) {
         return ra.getUserName();
      } else {
         return user;
      }
   }

   public void setUserName(final String value) {
      if (logger.isTraceEnabled()) {
         logger.trace("setUserName(" + value + ")");
      }

      user = value;
   }

   public String getPassword() {
      logger.trace("getPassword()");

      if (password == null) {
         return ra.getPassword();
      } else {
         return password;
      }
   }

   public String getOwnPassword() {
      return password;
   }

   public void setPassword(final String value) throws Exception {
      logger.trace("setPassword(****)");

      password = value;
   }

   public Integer getMaxSession() {
      logger.trace("getMaxSession()");

      if (maxSession == null) {
         return DEFAULT_MAX_SESSION;
      }

      return maxSession;
   }

   public void setMaxSession(final Integer value) {
      logger.trace("setMaxSession({})", value);

      if (value < 1) {
         maxSession = 1;
         ActiveMQRALogger.LOGGER.invalidNumberOfMaxSession(value, maxSession);
      } else
         maxSession = value;
   }

   public Boolean isSingleConnection() {
      logger.trace("getSingleConnection()");

      if (singleConnection == null) {
         return DEFAULT_SINGLE_CONNECTION;
      }

      return singleConnection;
   }

   public void setSingleConnection(final Boolean value) {
      logger.trace("setSingleConnection({})", value);

      singleConnection = value;
   }

   @Deprecated(forRemoval = true)
   public Integer getTransactionTimeout() {
      logger.trace("getTransactionTimeout()");

      return transactionTimeout;
   }

   @Deprecated(forRemoval = true)
   public void setTransactionTimeout(final Integer value) {
      logger.trace("setTransactionTimeout({})", value);

      transactionTimeout = value;
   }

   public Boolean isUseLocalTx() {
      if (localTx == null) {
         return ra.getUseLocalTx();
      } else {
         return localTx;
      }
   }

   public void setUseLocalTx(final Boolean localTx) {
      this.localTx = localTx;
   }

   public Boolean getRebalanceConnections() {
      return rebalanceConnections;
   }

   public Boolean isRebalanceConnections() {
      return rebalanceConnections;
   }

   public void setRebalanceConnections(final Boolean rebalanceConnections) {
      this.rebalanceConnections = rebalanceConnections;
   }

   public Integer getSetupAttempts() {
      logger.trace("getSetupAttempts()");

      if (setupAttempts == null) {
         return ra.getSetupAttempts();
      } else {
         return setupAttempts;
      }
   }

   public void setSetupAttempts(final Integer setupAttempts) {
      logger.trace("setSetupAttempts({})", setupAttempts);

      this.setupAttempts = setupAttempts;
   }

   public Long getSetupInterval() {
      logger.trace("getSetupInterval()");

      if (setupInterval == null) {
         return ra.getSetupInterval();
      } else {
         return setupInterval;
      }
   }

   public void setSetupInterval(final Long setupInterval) {
      logger.trace("setSetupInterval({})", setupInterval);

      this.setupInterval = setupInterval;
   }

   // ARTEMIS-399 - support both "clientId" and "clientID" activation config properties
   public void setClientId(String clientId) {
      setClientID(clientId);
   }

   /**
    * Validate
    *
    * @throws InvalidPropertyException Thrown if a validation exception occurs
    */
   @Override
   public void validate() throws InvalidPropertyException {
      logger.trace("validate()");

      ActiveMQActivationValidationUtils.validate(destination, destinationType, isSubscriptionDurable(), subscriptionName);
   }

   public String getConnectorClassName() {
      return strConnectorClassName;
   }

   public void setConnectorClassName(final String connectorClassName) {
      logger.trace("setConnectorClassName({})", connectorClassName);

      strConnectorClassName = connectorClassName;

      setParsedConnectorClassNames(ActiveMQRaUtils.parseConnectorConnectorConfig(connectorClassName));
   }

   public String getConnectionParameters() {
      return strConnectionParameters;
   }

   public void setConnectionParameters(final String configuration) {
      strConnectionParameters = configuration;
      setParsedConnectionParameters(ActiveMQRaUtils.parseConfig(configuration));
   }

   @Override
   public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(ActiveMQActivationSpec.class.getName()).append('(');
      sb.append("ra=").append(ra);
      if (connectionFactoryLookup != null) {
         sb.append(" connectionFactoryLookup=").append(connectionFactoryLookup);
      }
      sb.append(" destination=").append(destination);
      sb.append(" destinationType=").append(destinationType);
      if (messageSelector != null) {
         sb.append(" selector=").append(messageSelector);
      }
      sb.append(" ack=").append(getAcknowledgeMode());
      sb.append(" durable=").append(subscriptionDurability);
      sb.append(" clientID=").append(getClientID());
      if (subscriptionName != null) {
         sb.append(" subscription=").append(subscriptionName);
      }
      sb.append(" user=").append(user);
      if (password != null) {
         sb.append(" password=").append("****");
      }
      sb.append(" maxSession=").append(maxSession);
      sb.append(')');
      return sb.toString();
   }

   // here for backwards compatibility
   public void setUseDLQ(final Boolean b) {
   }

   public void setDLQJNDIName(final String name) {
   }

   public void setDLQHandler(final String handler) {
   }

   public void setDLQMaxResent(final Integer maxResent) {
   }

   public void setProviderAdapterJNDI(final String jndi) {
   }

   public void setKeepAlive(Boolean keepAlive) {
   }

   public void setKeepAliveMillis(Long keepAliveMillis) {
   }

   public void setReconnectInterval(Long interval) {
   }

   public void setMinSession(final Integer value) {
   }

   public void setMaxMessages(final Integer value) {
   }

   public Boolean isAllowLocalTransactions() {
      return allowLocalTransactions;
   }

   public void setAllowLocalTransactions(final Boolean allowLocalTransactions) {
      this.allowLocalTransactions = allowLocalTransactions;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj) {
         return true;
      }
      if (!(obj instanceof ActiveMQActivationSpec that)) {
         return false;
      }
      if (!super.equals(obj)) {
         return false;
      }

      return Objects.equals(acknowledgeMode, that.acknowledgeMode) &&
             Objects.equals(subscriptionDurability, that.subscriptionDurability) &&
             Objects.equals(shareSubscriptions, that.shareSubscriptions) &&
             Objects.equals(strConnectorClassName, that.strConnectorClassName) &&
             Objects.equals(strConnectionParameters, that.strConnectionParameters) &&
             Objects.equals(ra, that.ra) &&
             Objects.equals(connectionFactoryLookup, that.connectionFactoryLookup) &&
             Objects.equals(destination, that.destination) &&
             Objects.equals(destinationType, that.destinationType) &&
             Objects.equals(messageSelector, that.messageSelector) &&
             Objects.equals(subscriptionName, that.subscriptionName) &&
             Objects.equals(user, that.user) &&
             Objects.equals(password, that.password) &&
             Objects.equals(maxSession, that.maxSession) &&
             Objects.equals(useJNDI, that.useJNDI) &&
             Objects.equals(transactionTimeout, that.transactionTimeout) &&
             Objects.equals(singleConnection, that.singleConnection) &&
             Objects.equals(jndiParams, that.jndiParams) &&
             Objects.equals(parsedJndiParams, that.parsedJndiParams) &&
             Objects.equals(localTx, that.localTx) &&
             Objects.equals(rebalanceConnections, that.rebalanceConnections) &&
             Objects.equals(setupAttempts, that.setupAttempts) &&
             Objects.equals(queuePrefix, that.queuePrefix) &&
             Objects.equals(topicPrefix, that.topicPrefix) &&
             Objects.equals(setupInterval, that.setupInterval);
   }

   @Override
   public int hashCode() {
      return Objects.hash(super.hashCode(), strConnectorClassName, strConnectionParameters, ra, connectionFactoryLookup,
                          destination, destinationType, messageSelector, acknowledgeMode, subscriptionDurability,
                          subscriptionName, shareSubscriptions, user, password, maxSession, singleConnection,
                          transactionTimeout, useJNDI, jndiParams, parsedJndiParams, localTx, rebalanceConnections,
                          setupAttempts, setupInterval, queuePrefix, topicPrefix);
   }
}
