// Copyright (C) 2013 The Android Open Source Project
//
// 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.

package com.googlesource.gerrit.plugins.rabbitmq.session.impl;

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

import com.googlesource.gerrit.plugins.rabbitmq.config.Properties;
import com.googlesource.gerrit.plugins.rabbitmq.config.section.AMQP;
import com.googlesource.gerrit.plugins.rabbitmq.config.section.Exchange;
import com.googlesource.gerrit.plugins.rabbitmq.config.section.Message;
import com.googlesource.gerrit.plugins.rabbitmq.config.section.Monitor;
import com.googlesource.gerrit.plugins.rabbitmq.session.Session;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;

import org.apache.commons.codec.CharEncoding;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URISyntaxException;

public final class AMQPSession implements Session, ShutdownListener {

  private static final Logger LOGGER = LoggerFactory.getLogger(AMQPSession.class);
  private final Properties properties;
  private volatile Connection connection;
  private volatile Channel channel;
  private volatile int failureCount = 0;

  @Inject
  public AMQPSession(@Assisted Properties properties) {
    this.properties = properties;
  }

  private String MSG(String msg) {
    return String.format("[%s] %s", properties.getName(), msg);
  }

  @Override
  public boolean isOpen() {
    if (connection != null) {
      return true;
    }
    return false;
  }

  private Channel getChannel() {
    Channel ch = null;
    if (connection == null) {
      connect();
    } else {
      try {
        ch = connection.createChannel();
        ch.addShutdownListener(this);
        failureCount = 0;
        LOGGER.info(MSG("Publish channel opened."));
      } catch (Exception ex) {
        LOGGER.warn(MSG("Failed to open publish channel."));
        failureCount++;
      }
      if (failureCount > properties.getSection(Monitor.class).failureCount) {
        LOGGER.warn("Connection has something wrong. So will be disconnected.");
        disconnect();
      }
    }
    return ch;
  }

  @Override
  public void connect() {
    if (connection != null && connection.isOpen()) {
      LOGGER.info(MSG("Already connected."));
      return;
    }
    AMQP amqp = properties.getSection(AMQP.class);
    LOGGER.info(MSG("Connect to {}..."), amqp.uri);
    ConnectionFactory factory = new ConnectionFactory();
    try {
      if (StringUtils.isNotEmpty(amqp.uri)) {
        factory.setUri(amqp.uri);
        if (StringUtils.isNotEmpty(amqp.username)) {
          factory.setUsername(amqp.username);
        }
        if (StringUtils.isNotEmpty(amqp.password)) {
          factory.setPassword(amqp.password);
        }
        connection = factory.newConnection();
        connection.addShutdownListener(this);
        LOGGER.info(MSG("Connection established."));
      }
    } catch (URISyntaxException ex) {
      LOGGER.error(MSG("URI syntax error: {}"), amqp.uri);
    } catch (IOException ex) {
      LOGGER.error(MSG("Connection cannot be opened."));
    } catch (Exception ex) {
      LOGGER.warn(MSG("Connection has something error. it will be disposed."), ex);
    }
  }

  @Override
  public void disconnect() {
    LOGGER.info(MSG("Disconnecting..."));
    try {
      if (connection != null) {
        connection.close();
      }
    } catch (Exception ex) {
      LOGGER.warn(MSG("Error when close connection.") , ex);
    } finally {
      connection = null;
      channel = null;
    }
  }

  @Override
  public void publish(String messageBody) {
    if (channel == null || !channel.isOpen()) {
      channel = getChannel();
    }
    if (channel != null && channel.isOpen()) {
      Message message = properties.getSection(Message.class);
      Exchange exchange = properties.getSection(Exchange.class);
      try {
        LOGGER.debug(MSG("Send message."));
        channel.basicPublish(exchange.name, message.routingKey,
            properties.getAMQProperties().getBasicProperties(),
            messageBody.getBytes(CharEncoding.UTF_8));
      } catch (Exception ex) {
        LOGGER.warn(MSG("Error when sending meessage."), ex);
      }
    }
  }

  @Override
  public void shutdownCompleted(ShutdownSignalException exception) {
    Object obj = exception.getReference();

    if (obj instanceof Channel) {
      Channel ch = (Channel) obj;
      if (ch.equals(channel)) {
        LOGGER.info(MSG("Publish channel closed."));
        channel = null;
      }
    } else if (obj instanceof Connection) {
      LOGGER.info(MSG("Connection disconnected."));
      connection = null;
    }
  }
}

@@ -4031,24 +4031,190 @@
 ting...%22));%0A
+    try %7B%0A      if (channel != null) %7B%0A        channel.close();%0A      %7D%0A    %7D catch (Exception ex) %7B%0A      LOGGER.warn(MSG(%22Error when close channel.%22) , ex);%0A    %7D%0A%0A
     try %7B%0A  
@@ -4373,71 +4373,8 @@
 x);%0A
-    %7D finally %7B%0A      connection = null;%0A      channel = null;%0A
     
@@ -5395,24 +5395,105 @@
 nnection) %7B%0A
+      Connection conn = (Connection) obj;%0A      if (conn.equals(connection)) %7B%0A  
       LOGGER
@@ -5534,24 +5534,26 @@
 .%22));%0A      
+  
 connection =
@@ -5555,24 +5555,32 @@
 ion = null;%0A
+      %7D%0A
     %7D%0A  %7D%0A%7D%0A

Fix shutdown session