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

package io.netty.channel.socket.nio;

import io.netty.channel.MessageSizeEstimator;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.buffer.ByteBufAllocator;
import java.net.StandardSocketOptions;
import java.util.List;
import java.util.ArrayList;
import io.netty.channel.ChannelOption;
import java.util.Map;
import io.netty.channel.socket.DuplexChannelConfig;
import io.netty.channel.DefaultChannelConfig;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.nio.AbstractNioChannel;
import java.nio.ByteBuffer;
import io.netty.channel.ChannelOutboundBuffer;
import java.nio.channels.WritableByteChannel;
import io.netty.channel.FileRegion;
import java.nio.channels.GatheringByteChannel;
import io.netty.channel.RecvByteBufAllocator;
import java.nio.channels.ScatteringByteChannel;
import io.netty.buffer.ByteBuf;
import io.netty.util.internal.SocketUtils;
import java.net.SocketAddress;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Future;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ServerChannel;
import java.nio.channels.SelectableChannel;
import io.netty.channel.Channel;
import java.io.IOException;
import io.netty.channel.ChannelException;
import io.netty.util.internal.PlatformDependent;
import java.nio.channels.SocketChannel;
import io.netty.channel.ChannelConfig;
import java.lang.reflect.Method;
import java.nio.channels.spi.SelectorProvider;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.socket.DuplexChannel;
import io.netty.channel.nio.AbstractNioByteChannel;

public final class NioDomainSocketChannel extends AbstractNioByteChannel implements DuplexChannel
{
    private static final InternalLogger logger;
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER;
    private static final Method OPEN_SOCKET_CHANNEL_WITH_FAMILY;
    private final ChannelConfig config;
    private volatile boolean isInputShutdown;
    private volatile boolean isOutputShutdown;
    
    static SocketChannel newChannel(final SelectorProvider provider) {
        if (PlatformDependent.javaVersion() < 16) {
            throw new UnsupportedOperationException("Only supported on java 16+");
        }
        try {
            final SocketChannel channel = SelectorProviderUtil.newDomainSocketChannel(NioDomainSocketChannel.OPEN_SOCKET_CHANNEL_WITH_FAMILY, provider);
            if (channel == null) {
                throw new ChannelException("Failed to open a socket.");
            }
            return channel;
        }
        catch (final IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
    }
    
    public NioDomainSocketChannel() {
        this(NioDomainSocketChannel.DEFAULT_SELECTOR_PROVIDER);
    }
    
    public NioDomainSocketChannel(final SelectorProvider provider) {
        this(newChannel(provider));
    }
    
    public NioDomainSocketChannel(final SocketChannel socket) {
        this(null, socket);
    }
    
    public NioDomainSocketChannel(final Channel parent, final SocketChannel socket) {
        super(parent, socket);
        if (PlatformDependent.javaVersion() < 16) {
            throw new UnsupportedOperationException("Only supported on java 16+");
        }
        this.config = new NioDomainSocketChannelConfig(this, socket);
    }
    
    @Override
    public ServerChannel parent() {
        return (ServerChannel)super.parent();
    }
    
    @Override
    public ChannelConfig config() {
        return this.config;
    }
    
    @Override
    protected SocketChannel javaChannel() {
        return (SocketChannel)super.javaChannel();
    }
    
    @Override
    public boolean isActive() {
        final SocketChannel ch = this.javaChannel();
        return ch.isOpen() && ch.isConnected();
    }
    
    @Override
    public boolean isOutputShutdown() {
        return this.isOutputShutdown || !this.isActive();
    }
    
    @Override
    public boolean isInputShutdown() {
        return this.isInputShutdown || !this.isActive();
    }
    
    @Override
    public boolean isShutdown() {
        return (this.isInputShutdown() && this.isOutputShutdown()) || !this.isActive();
    }
    
    @Override
    protected void doShutdownOutput() throws Exception {
        this.javaChannel().shutdownOutput();
        this.isOutputShutdown = true;
    }
    
    @Override
    public ChannelFuture shutdownOutput() {
        return this.shutdownOutput(this.newPromise());
    }
    
    @Override
    public ChannelFuture shutdownOutput(final ChannelPromise promise) {
        final EventLoop loop = this.eventLoop();
        if (loop.inEventLoop()) {
            ((AbstractUnsafe)this.unsafe()).shutdownOutput(promise);
        }
        else {
            loop.execute(new Runnable() {
                @Override
                public void run() {
                    ((AbstractUnsafe)NioDomainSocketChannel.this.unsafe()).shutdownOutput(promise);
                }
            });
        }
        return promise;
    }
    
    @Override
    public ChannelFuture shutdownInput() {
        return this.shutdownInput(this.newPromise());
    }
    
    @Override
    protected boolean isInputShutdown0() {
        return this.isInputShutdown();
    }
    
    @Override
    public ChannelFuture shutdownInput(final ChannelPromise promise) {
        final EventLoop loop = this.eventLoop();
        if (loop.inEventLoop()) {
            this.shutdownInput0(promise);
        }
        else {
            loop.execute(new Runnable() {
                @Override
                public void run() {
                    NioDomainSocketChannel.this.shutdownInput0(promise);
                }
            });
        }
        return promise;
    }
    
    @Override
    public ChannelFuture shutdown() {
        return this.shutdown(this.newPromise());
    }
    
    @Override
    public ChannelFuture shutdown(final ChannelPromise promise) {
        final ChannelFuture shutdownOutputFuture = this.shutdownOutput();
        if (shutdownOutputFuture.isDone()) {
            this.shutdownOutputDone(shutdownOutputFuture, promise);
        }
        else {
            shutdownOutputFuture.addListener((GenericFutureListener<? extends Future<? super Void>>)new ChannelFutureListener() {
                @Override
                public void operationComplete(final ChannelFuture shutdownOutputFuture) throws Exception {
                    NioDomainSocketChannel.this.shutdownOutputDone(shutdownOutputFuture, promise);
                }
            });
        }
        return promise;
    }
    
    private void shutdownOutputDone(final ChannelFuture shutdownOutputFuture, final ChannelPromise promise) {
        final ChannelFuture shutdownInputFuture = this.shutdownInput();
        if (shutdownInputFuture.isDone()) {
            shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise);
        }
        else {
            shutdownInputFuture.addListener((GenericFutureListener<? extends Future<? super Void>>)new ChannelFutureListener() {
                @Override
                public void operationComplete(final ChannelFuture shutdownInputFuture) throws Exception {
                    shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise);
                }
            });
        }
    }
    
    private static void shutdownDone(final ChannelFuture shutdownOutputFuture, final ChannelFuture shutdownInputFuture, final ChannelPromise promise) {
        final Throwable shutdownOutputCause = shutdownOutputFuture.cause();
        final Throwable shutdownInputCause = shutdownInputFuture.cause();
        if (shutdownOutputCause != null) {
            if (shutdownInputCause != null) {
                NioDomainSocketChannel.logger.debug("Exception suppressed because a previous exception occurred.", shutdownInputCause);
            }
            promise.setFailure(shutdownOutputCause);
        }
        else if (shutdownInputCause != null) {
            promise.setFailure(shutdownInputCause);
        }
        else {
            promise.setSuccess();
        }
    }
    
    private void shutdownInput0(final ChannelPromise promise) {
        try {
            this.shutdownInput0();
            promise.setSuccess();
        }
        catch (final Throwable t) {
            promise.setFailure(t);
        }
    }
    
    private void shutdownInput0() throws Exception {
        this.javaChannel().shutdownInput();
        this.isInputShutdown = true;
    }
    
    @Override
    protected SocketAddress localAddress0() {
        try {
            return this.javaChannel().getLocalAddress();
        }
        catch (final Exception ex) {
            return null;
        }
    }
    
    @Override
    protected SocketAddress remoteAddress0() {
        try {
            return this.javaChannel().getRemoteAddress();
        }
        catch (final Exception ex) {
            return null;
        }
    }
    
    @Override
    protected void doBind(final SocketAddress localAddress) throws Exception {
        SocketUtils.bind(this.javaChannel(), localAddress);
    }
    
    @Override
    protected boolean doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            this.doBind(localAddress);
        }
        boolean success = false;
        try {
            final boolean connected = SocketUtils.connect(this.javaChannel(), remoteAddress);
            if (!connected) {
                this.selectionKey().interestOps(8);
            }
            success = true;
            return connected;
        }
        finally {
            if (!success) {
                this.doClose();
            }
        }
    }
    
    @Override
    protected void doFinishConnect() throws Exception {
        if (!this.javaChannel().finishConnect()) {
            throw new UnsupportedOperationException("finishConnect is not supported for " + this.getClass().getName());
        }
    }
    
    @Override
    protected void doDisconnect() throws Exception {
        this.doClose();
    }
    
    @Override
    protected void doClose() throws Exception {
        try {
            super.doClose();
        }
        finally {
            this.javaChannel().close();
        }
    }
    
    @Override
    protected int doReadBytes(final ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = this.unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        return byteBuf.writeBytes(this.javaChannel(), allocHandle.attemptedBytesRead());
    }
    
    @Override
    protected int doWriteBytes(final ByteBuf buf) throws Exception {
        final int expectedWrittenBytes = buf.readableBytes();
        return buf.readBytes(this.javaChannel(), expectedWrittenBytes);
    }
    
    @Override
    protected long doWriteFileRegion(final FileRegion region) throws Exception {
        final long position = region.transferred();
        return region.transferTo(this.javaChannel(), position);
    }
    
    private void adjustMaxBytesPerGatheringWrite(final int attempted, final int written, final int oldMaxBytesPerGatheringWrite) {
        if (attempted == written) {
            if (attempted << 1 > oldMaxBytesPerGatheringWrite) {
                ((NioDomainSocketChannelConfig)this.config).setMaxBytesPerGatheringWrite(attempted << 1);
            }
        }
        else if (attempted > 4096 && written < attempted >>> 1) {
            ((NioDomainSocketChannelConfig)this.config).setMaxBytesPerGatheringWrite(attempted >>> 1);
        }
    }
    
    @Override
    protected void doWrite(final ChannelOutboundBuffer in) throws Exception {
        final SocketChannel ch = this.javaChannel();
        int writeSpinCount = this.config().getWriteSpinCount();
        while (!in.isEmpty()) {
            final int maxBytesPerGatheringWrite = ((NioDomainSocketChannelConfig)this.config).getMaxBytesPerGatheringWrite();
            final ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
            final int nioBufferCnt = in.nioBufferCount();
            switch (nioBufferCnt) {
                case 0: {
                    writeSpinCount -= this.doWrite0(in);
                    break;
                }
                case 1: {
                    final ByteBuffer buffer = nioBuffers[0];
                    final int attemptedBytes = buffer.remaining();
                    final int localWrittenBytes = ch.write(buffer);
                    if (localWrittenBytes <= 0) {
                        this.incompleteWrite(true);
                        return;
                    }
                    this.adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes);
                    --writeSpinCount;
                    break;
                }
                default: {
                    final long attemptedBytes2 = in.nioBufferSize();
                    final long localWrittenBytes2 = ch.write(nioBuffers, 0, nioBufferCnt);
                    if (localWrittenBytes2 <= 0L) {
                        this.incompleteWrite(true);
                        return;
                    }
                    this.adjustMaxBytesPerGatheringWrite((int)attemptedBytes2, (int)localWrittenBytes2, maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes2);
                    --writeSpinCount;
                    break;
                }
            }
            if (writeSpinCount <= 0) {
                this.incompleteWrite(writeSpinCount < 0);
                return;
            }
        }
        this.clearOpWrite();
    }
    
    @Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(NioDomainSocketChannel.class);
        DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
        OPEN_SOCKET_CHANNEL_WITH_FAMILY = SelectorProviderUtil.findOpenMethod("openSocketChannel");
    }
    
    private final class NioSocketChannelUnsafe extends NioByteUnsafe
    {
    }
    
    private final class NioDomainSocketChannelConfig extends DefaultChannelConfig implements DuplexChannelConfig
    {
        private volatile boolean allowHalfClosure;
        private volatile int maxBytesPerGatheringWrite;
        private final SocketChannel javaChannel;
        
        private NioDomainSocketChannelConfig(final NioDomainSocketChannel channel, final SocketChannel javaChannel) {
            super(channel);
            this.maxBytesPerGatheringWrite = Integer.MAX_VALUE;
            this.javaChannel = javaChannel;
            this.calculateMaxBytesPerGatheringWrite();
        }
        
        @Override
        public boolean isAllowHalfClosure() {
            return this.allowHalfClosure;
        }
        
        @Override
        public NioDomainSocketChannelConfig setAllowHalfClosure(final boolean allowHalfClosure) {
            this.allowHalfClosure = allowHalfClosure;
            return this;
        }
        
        @Override
        public Map<ChannelOption<?>, Object> getOptions() {
            final List<ChannelOption<?>> options = new ArrayList<ChannelOption<?>>();
            options.add(ChannelOption.SO_RCVBUF);
            options.add(ChannelOption.SO_SNDBUF);
            for (final ChannelOption<?> opt : NioChannelOption.getOptions(this.jdkChannel())) {
                options.add(opt);
            }
            return this.getOptions(super.getOptions(), (ChannelOption<?>[])options.toArray(new ChannelOption[0]));
        }
        
        @Override
        public <T> T getOption(final ChannelOption<T> option) {
            if (option == ChannelOption.SO_RCVBUF) {
                return (T)Integer.valueOf(this.getReceiveBufferSize());
            }
            if (option == ChannelOption.SO_SNDBUF) {
                return (T)Integer.valueOf(this.getSendBufferSize());
            }
            if (option instanceof NioChannelOption) {
                return NioChannelOption.getOption(this.jdkChannel(), (NioChannelOption<T>)(NioChannelOption)option);
            }
            return super.getOption(option);
        }
        
        @Override
        public <T> boolean setOption(final ChannelOption<T> option, final T value) {
            if (option == ChannelOption.SO_RCVBUF) {
                this.validate(option, value);
                this.setReceiveBufferSize((int)value);
            }
            else if (option == ChannelOption.SO_SNDBUF) {
                this.validate(option, value);
                this.setSendBufferSize((int)value);
            }
            else {
                if (option instanceof NioChannelOption) {
                    return NioChannelOption.setOption(this.jdkChannel(), (NioChannelOption)option, value);
                }
                return super.setOption(option, value);
            }
            return true;
        }
        
        private int getReceiveBufferSize() {
            try {
                return this.javaChannel.getOption(StandardSocketOptions.SO_RCVBUF);
            }
            catch (final IOException e) {
                throw new ChannelException(e);
            }
        }
        
        private NioDomainSocketChannelConfig setReceiveBufferSize(final int receiveBufferSize) {
            try {
                this.javaChannel.setOption(StandardSocketOptions.SO_RCVBUF, receiveBufferSize);
            }
            catch (final IOException e) {
                throw new ChannelException(e);
            }
            return this;
        }
        
        private int getSendBufferSize() {
            try {
                return this.javaChannel.getOption(StandardSocketOptions.SO_SNDBUF);
            }
            catch (final IOException e) {
                throw new ChannelException(e);
            }
        }
        
        private NioDomainSocketChannelConfig setSendBufferSize(final int sendBufferSize) {
            try {
                this.javaChannel.setOption(StandardSocketOptions.SO_SNDBUF, sendBufferSize);
            }
            catch (final IOException e) {
                throw new ChannelException(e);
            }
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setConnectTimeoutMillis(final int connectTimeoutMillis) {
            super.setConnectTimeoutMillis(connectTimeoutMillis);
            return this;
        }
        
        @Deprecated
        @Override
        public NioDomainSocketChannelConfig setMaxMessagesPerRead(final int maxMessagesPerRead) {
            super.setMaxMessagesPerRead(maxMessagesPerRead);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setWriteSpinCount(final int writeSpinCount) {
            super.setWriteSpinCount(writeSpinCount);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setAllocator(final ByteBufAllocator allocator) {
            super.setAllocator(allocator);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setRecvByteBufAllocator(final RecvByteBufAllocator allocator) {
            super.setRecvByteBufAllocator(allocator);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setAutoRead(final boolean autoRead) {
            super.setAutoRead(autoRead);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setAutoClose(final boolean autoClose) {
            super.setAutoClose(autoClose);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setWriteBufferHighWaterMark(final int writeBufferHighWaterMark) {
            super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setWriteBufferLowWaterMark(final int writeBufferLowWaterMark) {
            super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setWriteBufferWaterMark(final WriteBufferWaterMark writeBufferWaterMark) {
            super.setWriteBufferWaterMark(writeBufferWaterMark);
            return this;
        }
        
        @Override
        public NioDomainSocketChannelConfig setMessageSizeEstimator(final MessageSizeEstimator estimator) {
            super.setMessageSizeEstimator(estimator);
            return this;
        }
        
        @Override
        protected void autoReadCleared() {
            AbstractNioChannel.this.clearReadPending();
        }
        
        void setMaxBytesPerGatheringWrite(final int maxBytesPerGatheringWrite) {
            this.maxBytesPerGatheringWrite = maxBytesPerGatheringWrite;
        }
        
        int getMaxBytesPerGatheringWrite() {
            return this.maxBytesPerGatheringWrite;
        }
        
        private void calculateMaxBytesPerGatheringWrite() {
            final int newSendBufferSize = this.getSendBufferSize() << 1;
            if (newSendBufferSize > 0) {
                this.setMaxBytesPerGatheringWrite(newSendBufferSize);
            }
        }
        
        private SocketChannel jdkChannel() {
            return this.javaChannel;
        }
    }
}
