/*
 * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software;Designed and Developed mainly by many Chinese 
 * opensource volunteers. you can redistribute it and/or modify it under the 
 * terms of the GNU General Public License version 2 only, as published by the
 * Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Any questions about this component can be directed to it's project Web address 
 * https://code.google.com/p/opencloudb/.
 *
 */
package com.talent.nio.communicate.send;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.talent.nio.api.Packet;
import com.talent.nio.communicate.ChannelContext;
import com.talent.nio.communicate.monitor.vo.PacketVo;
import com.talent.nio.communicate.monitor.vo.StatVo;
import com.talent.nio.debug.DebugUtils;
import com.talent.nio.utils.SystemTimer;
import com.talent.platform.threadpool.AbstractQueueRunnable;

/**
 * 
 * @author 谭耀武
 * @date 2012-08-09
 * 
 */
public class SendRunnable extends AbstractQueueRunnable<Packet>
{
    /**
     * 
     */
    @SuppressWarnings("unused")
    private static final long serialVersionUID = 4228217817295225588L;

    private static final Logger log = LoggerFactory.getLogger(SendRunnable.class);

    /**
     * @param args
     */
    public static void main(String[] args)
    {
    }

    private ConcurrentLinkedQueue<PacketVo> sendFailQueue = null;

    private ChannelContext channelContext = null;

    /**
     * 
     * @param socketChannelId
     */
    public SendRunnable(ChannelContext channelContext)
    {
        this.channelContext = channelContext;
        super.setRunnableName(SendRunnable.class.getSimpleName() + " [" + channelContext.getId() + "]");
    }

    /**
     * 添加要处理的消息
     * 
     * @param packet
     */
    public void addMsg(Packet packet)
    {
        getMsgQueue().add(packet);
    }

    /**
     * 清空消息队列
     */
    public void clearMsgQueue()
    {
        getMsgQueue().clear();
    }

    @Override
    public String getCurrentProcessor()
    {
        return this.getClass().getName();
    }

    /**
     * 
     * @param isForceCreate
     *            true:有条件地强制创建对象,false:直接返回
     * @return
     */
    public ConcurrentLinkedQueue<PacketVo> getSendFailQueue(boolean isForceCreate)
    {
        if (!isForceCreate)
        {
            return sendFailQueue;
        }

        if (!this.channelContext.isNeedRecordSendFailMsg())
        {
            return null;
        } else if (sendFailQueue == null)
        {
            sendFailQueue = new ConcurrentLinkedQueue<PacketVo>();
        }
        return sendFailQueue;
    }

    public void recordFailMsg(Packet packet, String failReason)
    {
        if (channelContext.isNeedRecordSendFailMsg())
        {
            ConcurrentLinkedQueue<PacketVo> sendFailQueue = getSendFailQueue(true);
            if (sendFailQueue != null)
            {
                PacketVo packetPojo = PacketVo.createPacketVo(packet, SystemTimer.currentTimeMillis(), failReason);
                if (sendFailQueue.size() >= channelContext.getCountOfRecordSendFail())
                {
                    sendFailQueue.poll();
                }
                sendFailQueue.add(packetPojo);
            }
        }

    }

    /**
     * 
     */
    @Override
    public void run()
    {
        Packet packet = null;
        try
        {
            while ((packet = getMsgQueue().poll()) != null)
            {
                sendPacket(packet);
            }
        } catch (IOException e)
        {
            recordFailMsg(packet, e.getMessage());
            channelContext.getWriteIOErrorHandler().handle(channelContext.getSocketChannel(), e, channelContext,
                    "IOException occured when writing");
        }
    }

    private int send(SocketChannel socketChannel, ByteBuffer dataBuffer) throws IOException
    {
        int sendSize = socketChannel.write(dataBuffer);

        if (sendSize > 0)
        {
            channelContext.getStatVo().setCurrentSendTime(SystemTimer.currentTimeMillis());
            channelContext.getStatVo().setSentBytes(sendSize + channelContext.getStatVo().getSentBytes());
            StatVo.getAllSentBytes().addAndGet(sendSize);

            getProcessedMsgByteCount().addAndGet(sendSize);
        }

        return sendSize;
    }

    /**
     * 
     * @param data
     *            要发送的数据
     * @param socketChannel
     *            发往的通道
     * @return 数据发送成功:true;失败:false
     * @throws IOException
     */
    private boolean sendData(byte[] data, SocketChannel socketChannel) throws IOException
    {
        try
        {
            ByteBuffer dataBuffer = ByteBuffer.wrap(data);

            long sendSize = send(socketChannel, dataBuffer);
            long allSendSize = sendSize;
            long starttime = SystemTimer.currentTimeMillis();

            while (dataBuffer.hasRemaining() && socketChannel.isOpen()) // 还有数据没写干净,一般是网络不好导致,也可能是发送和接收量太大。
            {
                sendSize = send(socketChannel, dataBuffer);

                if (sendSize == 0)
                {
                    Thread.sleep(5);
                } else
                {
                    allSendSize += sendSize;
                }

                if (DebugUtils.isNeedDebug(channelContext))
                {
                    try
                    {
                        long endtime = SystemTimer.currentTimeMillis();
                        long costtime = (endtime - starttime);
                        double speed = -1;
                        if (costtime > 0)
                        {
                            speed = allSendSize / costtime;
                        }

                        log.error("{} bytes was sent, {}/{}({}%), cost time: {} ms, {}bytes/ms,{}", sendSize, allSendSize, data.length,
                                (allSendSize * 100) / data.length, costtime, speed, channelContext);
                    } catch (Exception e)
                    {
                        log.error(e.getLocalizedMessage(), e);
                    }
                }
            }
            StatVo.getAllSentMsgCount().incrementAndGet();
            this.getProcessedMsgCount().incrementAndGet();
            long endtime = SystemTimer.currentTimeMillis();

            if (DebugUtils.isNeedDebug(channelContext))
            {
                try
                {
                    long costtime = (endtime - starttime);
                    double speed = -1;
                    if (costtime > 0)
                    {
                        speed = allSendSize / costtime;
                    }
                    log.error("cost time: {} ms, {}bytes/ms, {}", costtime, speed, channelContext);

                    log.error("ok sent to " + channelContext + ",total num[" + StatVo.getAllSentMsgCount().get() + "],num to this ["
                            + getProcessedMsgCount().get() + "],waiting for send to this [" + getMsgQueue().size() + "]");
                } catch (Exception e)
                {
                    log.error(e.getLocalizedMessage(), e);
                }
            }

            return true;
        } catch (InterruptedException e)
        {
            return false;
        }
    }

    public void sendPacket(Packet packet) throws IOException
    {
        if (packet == null)
        {
            log.error("Packet is null,please check synchronize");
            return;
        }

        if (channelContext.getId() == null)
        {
            log.error("target socketChannelId is null!");
            return;
        }

        byte[] bytes = null;
        try
        {
            bytes = channelContext.getPacketHandler().onSend(packet, channelContext);
        } catch (Exception e)
        {
            log.error(e.getMessage(), e);
            return;
        }

        if (log.isDebugEnabled())
        {
            log.debug("send:{}{}", Arrays.toString(bytes), packet);
        }

        if (bytes != null)
        {
            sendData(bytes, channelContext.getSocketChannel());

        } else
        {
            log.error("bytes is null");
        }
    }

    public void setRouteInfo(ChannelContext channelContext)
    {
        this.channelContext = channelContext;
    }

    public void setSendFailQueue(ConcurrentLinkedQueue<PacketVo> sendFailQueue)
    {
        this.sendFailQueue = sendFailQueue;
    }
}