/*
 * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.nio.ch;

import java.io.FileDescriptor;
import java.io.IOException;
import java.net.NetworkInterface;
import java.net.SocketOptions;
import java.nio.channels.*;


class SocketOptsImpl
    implements SocketOpts
{
    // set true when socket is bound and SO_REUSEADDRESS is emulated
    private boolean reuseAddressEmulated;

    // set true/false when socket is already bound and SO_REUSEADDR is emulated
    private boolean isReuseAddress;

    static abstract class Dispatcher {
        abstract int getInt(int opt) throws IOException;
        abstract void setInt(int opt, int arg) throws IOException;
        // Only used meaningfully by DatagramChannelImpl
        abstract boolean getIsBoundCondition();
        // Others that pass addresses, etc., will come later
    }

    private final Dispatcher d;

    SocketOptsImpl(Dispatcher d) {
        this.d = d;
    }

    protected boolean getBoolean(int opt) throws IOException {
        return d.getInt(opt) > 0;
    }

    protected void setBoolean(int opt, boolean b) throws IOException {
        d.setInt(opt, b ? 1 : 0);
    }

    protected int getInt(int opt) throws IOException {
        return d.getInt(opt);
    }

    protected void setInt(int opt, int n) throws IOException {
        d.setInt(opt, n);
    }

    protected NetworkInterface getNetworkInterface(int opt)
        throws IOException
    {
        throw new UnsupportedOperationException("NYI");
    }

    protected void setNetworkInterface(int opt, NetworkInterface ni)
        throws IOException
    {
        throw new UnsupportedOperationException("NYI");
    }

    protected void addToString(StringBuffer sb, String s) {
        char c = sb.charAt(sb.length() - 1);
        if ((c != '[') && (c != '='))
            sb.append(' ');
        sb.append(s);
    }

    protected void addToString(StringBuffer sb, int n) {
        addToString(sb, Integer.toString(n));
    }


    // SO_BROADCAST

    public boolean broadcast() throws IOException {
        return getBoolean(SocketOptions.SO_BROADCAST);
    }

    public SocketOpts broadcast(boolean b) throws IOException {
        setBoolean(SocketOptions.SO_BROADCAST, b);
        return this;
    }


    // SO_KEEPALIVE

    public boolean keepAlive() throws IOException {
        return getBoolean(SocketOptions.SO_KEEPALIVE);
    }

    public SocketOpts keepAlive(boolean b) throws IOException {
        setBoolean(SocketOptions.SO_KEEPALIVE, b);
        return this;
    }


    // SO_LINGER

    public int linger() throws IOException {
        return getInt(SocketOptions.SO_LINGER);
    }

    public SocketOpts linger(int n) throws IOException {
        setInt(SocketOptions.SO_LINGER, n);
        return this;
    }


    // SO_OOBINLINE

    public boolean outOfBandInline() throws IOException {
        return getBoolean(SocketOptions.SO_OOBINLINE);
    }

    public SocketOpts outOfBandInline(boolean b) throws IOException {
        setBoolean(SocketOptions.SO_OOBINLINE, b);
        return this;
    }


    // SO_RCVBUF

    public int receiveBufferSize() throws IOException {
        return getInt(SocketOptions.SO_RCVBUF);
    }

    public SocketOpts receiveBufferSize(int n) throws IOException {
        if (n <= 0)
            throw new IllegalArgumentException("Invalid receive size");
        setInt(SocketOptions.SO_RCVBUF, n);
        return this;
    }


    // SO_SNDBUF

    public int sendBufferSize() throws IOException {
        return getInt(SocketOptions.SO_SNDBUF);
    }

    public SocketOpts sendBufferSize(int n) throws IOException {
        if (n <= 0)
            throw new IllegalArgumentException("Invalid send size");
        setInt(SocketOptions.SO_SNDBUF, n);
        return this;
    }


    // SO_REUSEADDR

    public boolean reuseAddress() throws IOException {
        if (reuseAddressEmulated) {
            return isReuseAddress;
        }
        // no special handling
        return getBoolean(SocketOptions.SO_REUSEADDR);
    }

    public SocketOpts reuseAddress(boolean b) throws IOException {
        if ( Net.useExclusiveBind() && d.getIsBoundCondition() ) {
            reuseAddressEmulated = true;
            this.isReuseAddress = b;
        } else {
            // no special handling
            setBoolean(SocketOptions.SO_REUSEADDR, b);
        }
        return this;
    }


    // toString

    protected void toString(StringBuffer sb) throws IOException {
        int n;
        if (broadcast())
            addToString(sb, "broadcast");
        if (keepAlive())
            addToString(sb, "keepalive");
        if ((n = linger()) > 0) {
            addToString(sb, "linger=");
            addToString(sb, n);
        }
        if (outOfBandInline())
            addToString(sb, "oobinline");
        if ((n = receiveBufferSize()) > 0) {
            addToString(sb, "rcvbuf=");
            addToString(sb, n);
        }
        if ((n = sendBufferSize()) > 0) {
            addToString(sb, "sndbuf=");
            addToString(sb, n);
        }
        if (reuseAddress())
            addToString(sb, "reuseaddr");
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(this.getClass().getInterfaces()[0].getName());
        sb.append('[');
        int i = sb.length();
        try {
            toString(sb);
        } catch (IOException x) {
            sb.setLength(i);
            sb.append("closed");
        }
        sb.append(']');
        return sb.toString();
    }


    // IP-specific socket options

    static class IP
        extends SocketOptsImpl
        implements SocketOpts.IP
    {

        IP(Dispatcher d) {
            super(d);
        }


        // IP_MULTICAST_IF2
        // ## Do we need IP_MULTICAST_IF also?

        public NetworkInterface multicastInterface() throws IOException {
            return getNetworkInterface(SocketOptions.IP_MULTICAST_IF2);
        }

        public SocketOpts.IP multicastInterface(NetworkInterface ni)
            throws IOException
        {
            setNetworkInterface(SocketOptions.IP_MULTICAST_IF2, ni);
            return this;
        }


        // IP_MULTICAST_LOOP

        public boolean multicastLoop() throws IOException {
            return getBoolean(SocketOptions.IP_MULTICAST_LOOP);
        }

        public SocketOpts.IP multicastLoop(boolean b) throws IOException {
            setBoolean(SocketOptions.IP_MULTICAST_LOOP, b);
            return this;
        }


        // IP_TOS

        public int typeOfService() throws IOException {
            return getInt(SocketOptions.IP_TOS);
        }

        public SocketOpts.IP typeOfService(int tos) throws IOException {
            setInt(SocketOptions.IP_TOS, tos);
            return this;
        }


        // toString

        protected void toString(StringBuffer sb) throws IOException {
            super.toString(sb);
            int n;
            if ((n = typeOfService()) > 0) {
                addToString(sb, "tos=");
                addToString(sb, n);
            }
        }


        // TCP-specific IP options

        public static class TCP
            extends SocketOptsImpl.IP
            implements SocketOpts.IP.TCP
        {

            TCP(Dispatcher d) {
                super(d);
            }

            // TCP_NODELAY

            public boolean noDelay() throws IOException {
                return getBoolean(SocketOptions.TCP_NODELAY);
            }

            public SocketOpts.IP.TCP noDelay(boolean b) throws IOException {
                setBoolean(SocketOptions.TCP_NODELAY, b);
                return this;
            }


            // toString

            protected void toString(StringBuffer sb) throws IOException {
                super.toString(sb);
                if (noDelay())
                    addToString(sb, "nodelay");
            }

        }
    }

}