/*
 * Copyright 2015-present Open Networking Foundation
 *
 * 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 org.onosproject.bgp.controller.impl;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.onosproject.bgp.controller.BgpCfg;
import org.onosproject.bgp.controller.BgpController;
import org.onosproject.bgp.controller.BgpPeerCfg;
import org.onosproject.bgp.controller.BgpConnectPeer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implements connection initiation to peer on peer configuration and manage channel using netty channel handler.
 */
public class BgpConnectPeerImpl implements BgpConnectPeer {
    private static final Logger log = LoggerFactory.getLogger(BgpConnectPeerImpl.class);

    private ScheduledExecutorService connectExecutor = null;
    private final String peerHost;
    private static final int RETRY_INTERVAL = 4;
    private final int peerPort;
    private int connectRetryCounter = 0;
    private int connectRetryTime;
    private ChannelPipelineFactory pfact;
    private ClientBootstrap peerBootstrap;
    public String getPeerHost() {
        return peerHost;
    }
    public static int getRetryInterval() {
        return RETRY_INTERVAL;
    }

    @Override
    public int getPeerPort() {
        return peerPort;
    }
    @Override
    public int getConnectRetryCounter() {
        return connectRetryCounter;
    }

    public void setConnectRetryCounter(int connectRetryCounter) {
        this.connectRetryCounter = connectRetryCounter;
    }

    public void setConnectRetryTime(int connectRetryTime) {
        this.connectRetryTime = connectRetryTime;
    }

    public BgpCfg getBgpconfig() {
        return bgpconfig;
    }

    public void setBgpconfig(BgpCfg bgpconfig) {
        this.bgpconfig = bgpconfig;
    }

    private BgpCfg bgpconfig;

    /**
     * Initialize timer and initiate pipeline factory.
     *
     * @param bgpController parent BGP controller
     * @param remoteHost remote host to connect
     * @param remotePort remote port to connect
     */
    public BgpConnectPeerImpl(BgpController bgpController, String remoteHost, int remotePort) {

        this.bgpconfig = bgpController.getConfig();
        this.pfact = new BgpPipelineFactory(bgpController, false);
        this.peerBootstrap = Controller.peerBootstrap();
        this.peerBootstrap.setPipelineFactory(pfact);
        this.peerHost = remoteHost;
        this.peerPort = remotePort;
        this.connectRetryTime = 0;
    }

    @Override
    public void disconnectPeer() {
        if (connectExecutor != null) {
            connectExecutor.shutdown();
            connectExecutor = null;
        }
    }

    @Override
    public void connectPeer() {
        scheduleConnectionRetry(this.connectRetryTime);
    }

    /**
     * Retry connection with exponential back-off mechanism.
     *
     * @param retryDelay retry delay
     */
    private void scheduleConnectionRetry(long retryDelay) {
        if (this.connectExecutor == null) {
            this.connectExecutor = Executors.newSingleThreadScheduledExecutor();
        }
        this.connectExecutor.schedule(new ConnectionRetry(), retryDelay, TimeUnit.MINUTES);
    }

    /**
     * Implements BGP connection and manages connection to peer with back-off mechanism in case of failure.
     */
    class ConnectionRetry implements Runnable {
        @Override
        public void run() {
            log.debug("Connect to peer {}", peerHost);

            InetSocketAddress connectToSocket = new InetSocketAddress(peerHost, peerPort);

            try {
                bgpconfig.setPeerConnState(peerHost, BgpPeerCfg.State.CONNECT);
                peerBootstrap.connect(connectToSocket).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            bgpconfig.setPeerConnState(peerHost, BgpPeerCfg.State.ACTIVE);
                            connectRetryCounter++;
                            log.error("Connection failed, ConnectRetryCounter {} remote host {}", connectRetryCounter,
                                      peerHost);
                            /*
                             * Reconnect to peer on failure is exponential till 4 mins, later on retry after every 4
                             * mins.
                             */
                            if (connectRetryTime < RETRY_INTERVAL) {
                                connectRetryTime = (connectRetryTime != 0) ? connectRetryTime * 2 : 1;
                            }
                            scheduleConnectionRetry(connectRetryTime);
                        } else {

                            connectRetryCounter++;
                            log.debug("Connected to remote host {}, Connect Counter {}", peerHost, connectRetryCounter);
                            disconnectPeer();
                            return;
                        }
                    }
                });
            } catch (Exception e) {
                log.debug("Connect peer exception : " + e.toString());
                disconnectPeer();
            }
        }
    }
}
