package org.holer.server.handler;

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

import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
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.HolerPort;
import org.holer.server.util.ServerMgr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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


    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        HolerUtil.close(ctx);
    }

    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
        Channel extraChannel = ctx.channel();
        Channel holerChannel = (Channel) extraChannel.attr(HolerConst.HOLER_CHANNEL).get();

        if (!HolerUtil.isActive(holerChannel)) {

            extraChannel.close();

            return;
        }
        byte[] data = new byte[buf.readableBytes()];
        buf.readBytes(data);

        HolerMsg msg = new HolerMsg();
        msg.setType((byte) 5);
        msg.setUri((String) extraChannel.attr(HolerConst.HOLER_URI).get());
        msg.setData(data);
        holerChannel.writeAndFlush(msg);
    }

    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel extraChannel = ctx.channel();
        Integer portNum = ServerMgr.port(extraChannel);
        HolerChannel hc = ServerMgr.holerChannel(portNum);
        Channel holerChannel = ServerMgr.getHolerChannel(hc);

        if (!HolerUtil.isActive(holerChannel)) {
            extraChannel.close();
            super.channelActive(ctx);
            return;
        }

        Map<String, Channel> extraClients = (Map) holerChannel.attr(ServerConst.EXTRA_CLIENTS).get();
        if (MapUtils.isNotEmpty(extraClients) && extraClients.size() >= 256) {

            Map.Entry<String, Channel> chEntry = (Map.Entry) CollectionUtils.get(extraClients, 0);
            Channel oldChannel = (Channel) extraClients.remove(chEntry.getKey());
            log.warn("Exceeds the maximum number of connections, close channel " + oldChannel);
            HolerUtil.close(oldChannel);
        }


        String extraId = ServerMgr.extraClientId();
        extraChannel.config().setOption(ChannelOption.AUTO_READ, Boolean.valueOf(false));
        extraChannel.attr(HolerConst.HOLER_URI).set(extraId);


        extraClients.put(extraId, extraChannel);

        HolerPort port = (HolerPort) hc.getPorts().get(portNum);
        HolerMsg msg = new HolerMsg();
        msg.setType((byte) 3);
        msg.setUri(extraId);
        msg.setData(port.getServer().getBytes());
        holerChannel.writeAndFlush(msg);

        super.channelActive(ctx);
    }


    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel extraChannel = ctx.channel();
        Integer portNum = ServerMgr.port(extraChannel);
        HolerChannel hc = ServerMgr.holerChannel(portNum);
        Channel hchannel = ServerMgr.getHolerChannel(hc);


        if (!HolerUtil.isActive(hchannel)) {

            extraChannel.close();
            super.channelInactive(ctx);

            return;
        }

        String extraId = (String) extraChannel.attr(HolerConst.HOLER_URI).get();
        ServerMgr.removeExtraChannel(hchannel, extraId);
        Channel holerChannel = (Channel) extraChannel.attr(HolerConst.HOLER_CHANNEL).get();
        if (HolerUtil.isActive(holerChannel)) {

            HolerUtil.close((Channel) holerChannel.attr(HolerConst.HOLER_CHANNEL).get());
            holerChannel.attr(HolerConst.HOLER_URI).set(null);
            holerChannel.attr(HolerConst.HOLER_KEY).set(null);
            holerChannel.attr(HolerConst.HOLER_CHANNEL).set(null);
            holerChannel.config().setOption(ChannelOption.AUTO_READ, Boolean.valueOf(true));


            HolerMsg msg = new HolerMsg();
            msg.setType((byte) 4);
            msg.setUri(extraId);
            holerChannel.writeAndFlush(msg);
        }
        super.channelInactive(ctx);
    }


    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        Channel extraChannel = ctx.channel();
        Integer portNum = ServerMgr.port(extraChannel);
        HolerChannel hc = ServerMgr.holerChannel(portNum);
        Channel hchannel = ServerMgr.getHolerChannel(hc);

        if (!HolerUtil.isActive(hchannel)) {
            extraChannel.close();
            super.channelWritabilityChanged(ctx);
            return;
        }
        Channel holerChannel = (Channel) extraChannel.attr(HolerConst.HOLER_CHANNEL).get();
        if (null != holerChannel) {
            holerChannel.config().setOption(ChannelOption.AUTO_READ, Boolean.valueOf(extraChannel.isWritable()));
        }
        super.channelWritabilityChanged(ctx);
    }
}
