/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.net;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.harmony.luni.platform.INetworkSystem;
import org.apache.harmony.luni.platform.Platform;

/**
 * This class is the base of all streaming socket implementation classes.
 * Streaming sockets are wrapped by two classes, {@code ServerSocket} and
 * {@code Socket} at the server and client end of a connection. At the server,
 * there are two types of sockets engaged in communication, the {@code
 * ServerSocket} on a well known port (referred to as listener) used to
 * establish a connection and the resulting {@code Socket} (referred to as
 * host).
 */
public abstract class SocketImpl implements SocketOptions {

    /**
     * The remote address this socket is connected to.
     */
    protected InetAddress address;

    /**
     * The remote port this socket is connected to.
     */
    protected int port;

    /**
     * The file descriptor of this socket.
     */
    protected FileDescriptor fd;

    /**
     * The local port this socket is connected to.
     */
    protected int localport;

    INetworkSystem netImpl = Platform.getNetworkSystem();

    boolean streaming = true;

    /**
     * Waits for an incoming request and blocks until the connection is opened
     * on the given socket.
     *
     * @param newSocket
     *            the socket to accept connections on.
     * @throws IOException
     *             if an error occurs while accepting a new connection.
     */
    protected abstract void accept(SocketImpl newSocket) throws IOException;

    /**
     * Returns the available number of bytes which are readable from this socket
     * without blocking.
     *
     * @return the number of bytes that may be read without blocking.
     * @throws IOException
     *             if an error occurs while reading the number of bytes.
     */
    protected abstract int available() throws IOException;

    /**
     * Binds this socket to the specified local host address and port number.
     *
     * @param address
     *            the local machine address to bind this socket to.
     * @param port
     *            the port on the local machine to bind this socket to.
     * @throws IOException
     *             if an error occurs while binding this socket.
     */
    protected abstract void bind(InetAddress address, int port)
            throws IOException;

    /**
     * Closes this socket. This makes later access invalid.
     *
     * @throws IOException
     *             if an error occurs while closing this socket.
     */
    protected abstract void close() throws IOException;

    /**
     * Connects this socket to the specified remote host and port number.
     *
     * @param host
     *            the remote host this socket has to be connected to.
     * @param port
     *            the remote port on which this socket has to be connected.
     * @throws IOException
     *             if an error occurs while connecting to the remote host.
     */
    protected abstract void connect(String host, int port) throws IOException;

    /**
     * Connects this socket to the specified remote host address and port
     * number.
     *
     * @param address
     *            the remote host address this socket has to be connected to.
     * @param port
     *            the remote port on which this socket has to be connected.
     * @throws IOException
     *             if an error occurs while connecting to the remote host.
     */
    protected abstract void connect(InetAddress address, int port)
            throws IOException;

    /**
     * Creates a new unconnected socket. The argument {@code isStreaming}
     * defines whether the new socket is a streaming or a datagram socket.
     *
     * @param isStreaming
     *            defines whether the type of the new socket is streaming or
     *            datagram.
     * @throws IOException
     *             if an error occurs while creating the socket.
     */
    protected abstract void create(boolean isStreaming) throws IOException;

    /**
     * Gets the file descriptor of this socket.
     *
     * @return the file descriptor of this socket.
     */
    protected FileDescriptor getFileDescriptor() {
        return fd;
    }

    /**
     * Gets the remote address this socket is connected to.
     *
     * @return the remote address of this socket.
     */
    protected InetAddress getInetAddress() {
        return address;
    }

    /**
     * Gets the input stream of this socket.
     *
     * @return the input stream of this socket.
     * @throws IOException
     *             if an error occurs while accessing the input stream.
     */
    protected abstract InputStream getInputStream() throws IOException;

    /**
     * Gets the local port number of this socket. The field is initialized to
     * {@code -1} and upon demand will go to the IP stack to get the bound
     * value. See the class comment for the context of the local port.
     *
     * @return the local port number this socket is bound to.
     */
    protected int getLocalPort() {
        return localport;
    }

    /**
     * Gets the output stream of this socket.
     *
     * @return the output stream of this socket.
     * @throws IOException
     *             if an error occurs while accessing the output stream.
     */
    protected abstract OutputStream getOutputStream() throws IOException;

    /**
     * Gets the remote port number of this socket. This value is not meaningful
     * when this instance is wrapped by a {@code ServerSocket}.
     *
     * @return the remote port this socket is connected to.
     */
    protected int getPort() {
        return port;
    }

    /**
     * Listens for connection requests on this streaming socket. Incoming
     * connection requests are queued up to the limit specified by {@code
     * backlog}. Additional requests are rejected. The method {@code listen()}
     * may only be invoked on streaming sockets.
     *
     * @param backlog
     *            the maximum number of outstanding connection requests.
     * @throws IOException
     *             if an error occurs while listening.
     */
    protected abstract void listen(int backlog) throws IOException;

    /**
     * Returns a string containing a concise, human-readable description of the
     * socket.
     *
     * @return the textual representation of this socket.
     */
    @SuppressWarnings("nls")
    @Override
    public String toString() {
        return new StringBuilder(100).append("Socket[addr=").append(
                getInetAddress()).append(",port=").append(port).append(
                ",localport=").append(getLocalPort()).append("]").toString();
    }

    /**
     * In the IP stack, write at most {@code count} bytes on the socket
     * from the {@code buffer}, from the {@code offset}.
     *
     * @param buffer
     *            the buffer to read into
     * @param offset
     *            the offset into the buffer
     * @param count
     *            the number of bytes to write
     * @return int the actual number of bytes written
     * @throws IOException
     *                thrown if an error occurs while writing
     */
    int write(byte[] buffer, int offset, int count) throws IOException {
        if (streaming) {
            return this.netImpl.write(fd, buffer, offset, count);
        } else {
            return this.netImpl.send(fd, buffer, offset, count, port, address);
        }
    }

    /**
     * Closes the input channel of this socket.
     * <p>
     * This default implementation always throws an {@link IOException} to
     * indicate that the subclass should have overridden this method.
     *
     * @throws IOException
     *             always because this method should be overridden.
     */
    protected void shutdownInput() throws IOException {
        throw new IOException("Method has not been implemented");
    }

    /**
     * Closes the output channel of this socket.
     * <p>
     * This default implementation always throws an {@link IOException} to
     * indicate that the subclass should have overridden this method.
     *
     * @throws IOException
     *             always because this method should be overridden.
     */
    protected void shutdownOutput() throws IOException {
        throw new IOException("Method has not been implemented");
    }

    /**
     * Connects this socket to the remote host address and port number specified
     * by the {@code SocketAddress} object with the given timeout. This method
     * will block indefinitely if the timeout is set to zero.
     *
     * @param remoteAddr
     *            the remote host address and port number to connect to.
     * @param timeout
     *            the timeout value in milliseconds.
     * @throws IOException
     *             if an error occurs while connecting.
     */
    protected abstract void connect(SocketAddress remoteAddr, int timeout)
            throws IOException;

    /**
     * Returns whether the socket supports urgent data or not. Subclasses should
     * override this method.
     *
     * @return {@code false} because subclasses must override this method.
     */
    protected boolean supportsUrgentData() {
        return false;
    }

    /**
     * Sends the single byte of urgent data on the socket.
     *
     * @param value
     *            the byte of urgent data.
     * @throws IOException
     *             if an error occurs sending urgent data.
     */
    protected abstract void sendUrgentData(int value) throws IOException;

    /**
     * Sets performance preference for connection time, latency and bandwidth.
     * Does nothing by default.
     *
     * @param connectionTime
     *            the importance of connect time.
     * @param latency
     *            the importance of latency.
     * @param bandwidth
     *            the importance of bandwidth.
     */
    protected void setPerformancePreferences(int connectionTime, int latency,
            int bandwidth) {
        // Our socket implementation only provide one protocol: TCP/IP, so
        // we do nothing for this method
    }
}
