package org.holer.server.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Date;
import java.util.LinkedHashMap;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.holer.common.constant.HolerConst;
import org.holer.common.model.HolerMsg;
import org.holer.common.util.HolerUtil;
import org.holer.server.constant.ServerConst;
import org.holer.server.model.HolerChannel;
import org.holer.server.model.HolerClient;
import org.holer.server.util.ServerMgr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class ServerHandler extends SimpleChannelInboundHandler<HolerMsg> {
    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("Caught holer client exception {} {}", ctx.channel(), cause.getMessage());
        super.exceptionCaught(ctx, cause);
    }

    protected void channelRead0(ChannelHandlerContext ctx, HolerMsg msg) throws Exception {
        Channel holerChannel = ctx.channel();
        log.debug("Received message from holer client {}, message type is {}", holerChannel, Byte.valueOf(msg.getType()));
        switch (msg.getType()) {
            case 7:
                handleHeartbeatMsg(holerChannel, msg);
                break;
            case 1:
                handleAuthMsg(holerChannel, msg);
                break;
            case 3:
                handleConnectMsg(holerChannel, msg);
                break;
            case 4:
                handleDisconnectMsg(holerChannel, msg);
                break;
            case 5:
                handleTransferMsg(ctx, msg);
                break;
        }
    }

    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        Channel holerChannel = ctx.channel();
        Channel extraChannel = (Channel) holerChannel.attr(HolerConst.HOLER_CHANNEL).get();
        if (null != extraChannel) {
            extraChannel.config().setOption(ChannelOption.AUTO_READ, Boolean.valueOf(holerChannel.isWritable()));
        }
        super.channelWritabilityChanged(ctx);
    }

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel holerChannel = ctx.channel();
        Channel extraChannel = (Channel) holerChannel.attr(HolerConst.HOLER_CHANNEL).get();
        if (HolerUtil.isActive(extraChannel)) {
            String key = (String) holerChannel.attr(HolerConst.HOLER_KEY).get();
            String extraId = (String) holerChannel.attr(HolerConst.HOLER_URI).get();
            ServerMgr.removeExtraChannel(key, extraId);
            extraChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            extraChannel.close();
        } else {

            ServerMgr.removeHolerChannel(holerChannel);
        }
        super.channelInactive(ctx);
    }


    private void handleHeartbeatMsg(Channel hc, HolerMsg msg) {
        ServerMgr.notify(hc, (byte) 7, Long.valueOf(msg.getSerialNumber()));
        log.debug("Response heart beat message {}", hc);
    }


    private void handleAuthMsg(Channel hc, HolerMsg msg) {
        String accessKey = msg.getUri();
        HolerChannel hchannel = ServerMgr.holerChannel(accessKey);
        if (null == hchannel || null == hchannel.getClient()) {

            log.info("Invalid access key {}", accessKey);
            ServerMgr.notify(hc, (byte) 16, Long.valueOf(msg.getSerialNumber()));

            return;
        }
        HolerClient client = hchannel.getClient();
        if (!client.getEnabled().booleanValue()) {
            log.info("The holer client with access key {} has been disabled.", accessKey);
            ServerMgr.notify(hc, (byte) 8, Long.valueOf(msg.getSerialNumber()));
            return;
        }
        if (MapUtils.isEmpty(hchannel.getPorts())) {
            log.info("There are no available ports for the access key {}, {}", accessKey, hc);
            ServerMgr.notify(hc, (byte) 2, msg.getSerialNumber());
            return;
        }

        Channel holerChannel = client.getChannel();
        if (null != holerChannel) {
            log.info("The access key is in use by other holer client {}, {}", accessKey, holerChannel);
            ServerMgr.notify(holerChannel, (byte) 6, msg.getSerialNumber());
            ServerMgr.removeHolerChannel(accessKey);
        }
        log.warn("Holer access key [{}] with ports {} is in use by holer client {}", new Object[]{accessKey, hchannel.getPorts().keySet(), hc});
        hc.attr(ServerConst.EXTRA_CLIENTS).set(new LinkedHashMap());
        client.setChannel(hc);
        client.setOnlineAt(new Date());
    }


    private void handleConnectMsg(Channel hc, HolerMsg msg) {
        String uriStr = msg.getUri();
        if (StringUtils.isEmpty(uriStr)) {

            log.warn("The uri is empty.");
            hc.close();

            return;
        }
        String[] tokens = uriStr.split("@");
        if (tokens.length != 2) {

            log.warn("The uri is not valid.");
            hc.close();

            return;
        }
        String extraId = tokens[0];
        String accessKey = tokens[1];

        Channel hchannel = ServerMgr.getHolerChannel(accessKey);
        if (null == hchannel) {

            log.warn("There is no holer channel with access key {}", accessKey);
            hc.close();

            return;
        }
        Channel extraChannel = ServerMgr.getExtraChannel(hchannel, extraId);
        if (HolerUtil.isActive(extraChannel)) {

            hc.attr(HolerConst.HOLER_URI).set(extraId);
            hc.attr(HolerConst.HOLER_KEY).set(accessKey);
            hc.attr(HolerConst.HOLER_CHANNEL).set(extraChannel);


            extraChannel.attr(HolerConst.HOLER_CHANNEL).set(hc);
            extraChannel.config().setOption(ChannelOption.AUTO_READ, Boolean.TRUE);
        }
    }


    private void handleDisconnectMsg(Channel hc, HolerMsg msg) {
        String extraId = msg.getUri();
        String accessKey = (String) hc.attr(HolerConst.HOLER_KEY).get();
        if (StringUtils.isEmpty(accessKey)) {


            Channel extraChannel = ServerMgr.removeExtraChannel(hc, extraId);
            if (HolerUtil.isActive(extraChannel)) {
                extraChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            }

            return;
        }

        Channel extraChannel = ServerMgr.removeExtraChannel(accessKey, extraId);
        if (HolerUtil.isActive(extraChannel)) {

            extraChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            HolerUtil.close((Channel) hc.attr(HolerConst.HOLER_CHANNEL).get());
            hc.attr(HolerConst.HOLER_URI).set(null);
            hc.attr(HolerConst.HOLER_KEY).set(null);
            hc.attr(HolerConst.HOLER_CHANNEL).set(null);
        }
    }


    private void handleTransferMsg(ChannelHandlerContext ctx, HolerMsg msg) throws Exception {
        Channel hc = ctx.channel();
        Channel extraChannel = (Channel) hc.attr(HolerConst.HOLER_CHANNEL).get();
        if (HolerUtil.isActive(extraChannel)) {

            ByteBuf data = ctx.alloc().buffer(msg.getData().length);
            data.writeBytes(msg.getData());
            extraChannel.writeAndFlush(data);
        }
    }
}
