// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.codec.http.websocketx;

import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.buffer.ByteBuf;
import java.nio.ByteOrder;
import io.netty.handler.codec.TooLongFrameException;
import java.util.List;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.handler.codec.MessageToMessageEncoder;

public class WebSocket08FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder
{
    private static final InternalLogger logger;
    private static final byte OPCODE_CONT = 0;
    private static final byte OPCODE_TEXT = 1;
    private static final byte OPCODE_BINARY = 2;
    private static final byte OPCODE_CLOSE = 8;
    private static final byte OPCODE_PING = 9;
    private static final byte OPCODE_PONG = 10;
    private static final int GATHERING_WRITE_THRESHOLD = 1024;
    private final WebSocketFrameMaskGenerator maskGenerator;
    
    public WebSocket08FrameEncoder(final boolean maskPayload) {
        this(maskPayload ? RandomWebSocketFrameMaskGenerator.INSTANCE : null);
    }
    
    public WebSocket08FrameEncoder(final WebSocketFrameMaskGenerator maskGenerator) {
        super(WebSocketFrame.class);
        this.maskGenerator = maskGenerator;
    }
    
    @Override
    protected void encode(final ChannelHandlerContext ctx, final WebSocketFrame msg, final List<Object> out) throws Exception {
        final ByteBuf data = msg.content();
        final byte opcode = getOpCode(msg);
        final int length = data.readableBytes();
        if (WebSocket08FrameEncoder.logger.isTraceEnabled()) {
            WebSocket08FrameEncoder.logger.trace("Encoding WebSocket Frame opCode={} length={}", (Object)opcode, length);
        }
        int b0 = 0;
        if (msg.isFinalFragment()) {
            b0 |= 0x80;
        }
        b0 |= (msg.rsv() & 0x7) << 4;
        b0 |= (opcode & 0x7F);
        if (opcode == 9 && length > 125) {
            throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length);
        }
        boolean release = true;
        ByteBuf buf = null;
        try {
            final int maskLength = (this.maskGenerator != null) ? 4 : 0;
            if (length <= 125) {
                final int size = 2 + maskLength + length;
                buf = ctx.alloc().buffer(size);
                buf.writeByte(b0);
                final byte b2 = (byte)((this.maskGenerator != null) ? (0x80 | length) : length);
                buf.writeByte(b2);
            }
            else if (length <= 65535) {
                int size = 4 + maskLength;
                if (this.maskGenerator != null || length <= 1024) {
                    size += length;
                }
                buf = ctx.alloc().buffer(size);
                buf.writeByte(b0);
                buf.writeByte((this.maskGenerator != null) ? 254 : 126);
                buf.writeByte(length >>> 8 & 0xFF);
                buf.writeByte(length & 0xFF);
            }
            else {
                int size = 10 + maskLength;
                if (this.maskGenerator != null) {
                    size += length;
                }
                buf = ctx.alloc().buffer(size);
                buf.writeByte(b0);
                buf.writeByte((this.maskGenerator != null) ? 255 : 127);
                buf.writeLong(length);
            }
            if (this.maskGenerator != null) {
                final int mask = this.maskGenerator.nextMask();
                buf.writeInt(mask);
                if (mask != 0) {
                    if (length > 0) {
                        final ByteOrder srcOrder = data.order();
                        final ByteOrder dstOrder = buf.order();
                        int i = data.readerIndex();
                        final int end = data.writerIndex();
                        if (srcOrder == dstOrder) {
                            long longMask = (long)mask & 0xFFFFFFFFL;
                            longMask |= longMask << 32;
                            if (srcOrder == ByteOrder.LITTLE_ENDIAN) {
                                longMask = Long.reverseBytes(longMask);
                            }
                            for (int lim = end - 7; i < lim; i += 8) {
                                buf.writeLong(data.getLong(i) ^ longMask);
                            }
                            if (i < end - 3) {
                                buf.writeInt(data.getInt(i) ^ (int)longMask);
                                i += 4;
                            }
                        }
                        int maskOffset = 0;
                        while (i < end) {
                            final byte byteData = data.getByte(i);
                            buf.writeByte(byteData ^ WebSocketUtil.byteAtIndex(mask, maskOffset++ & 0x3));
                            ++i;
                        }
                    }
                    out.add(buf);
                }
                else {
                    addBuffers(buf, data, out);
                }
            }
            else {
                addBuffers(buf, data, out);
            }
            release = false;
        }
        finally {
            if (release && buf != null) {
                buf.release();
            }
        }
    }
    
    private static byte getOpCode(final WebSocketFrame msg) {
        if (msg instanceof TextWebSocketFrame) {
            return 1;
        }
        if (msg instanceof BinaryWebSocketFrame) {
            return 2;
        }
        if (msg instanceof PingWebSocketFrame) {
            return 9;
        }
        if (msg instanceof PongWebSocketFrame) {
            return 10;
        }
        if (msg instanceof CloseWebSocketFrame) {
            return 8;
        }
        if (msg instanceof ContinuationWebSocketFrame) {
            return 0;
        }
        throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName());
    }
    
    private static void addBuffers(final ByteBuf buf, final ByteBuf data, final List<Object> out) {
        final int readableBytes = data.readableBytes();
        if (buf.writableBytes() >= readableBytes) {
            buf.writeBytes(data);
            out.add(buf);
        }
        else {
            out.add(buf);
            if (readableBytes > 0) {
                out.add(data.retain());
            }
        }
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
    }
}
