/*
 * Copyright (c) 2002-2010 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 *  the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.util.net.socket;

import com.jscape.util.IOTools;
import com.jscape.util.Time;
import com.jscape.util.net.TransportAddress;
import com.jscape.util.net.connection.PassiveConnector;
import com.jscape.util.net.connection.SyncRawConnection;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class SocketPassiveConnector
        implements PassiveConnector<SyncRawConnection> {

    private final ServerSocket acceptor;
    private final Time timeout;
    private final SocketParameters socketParameters;
    private final TransportAddress boundAddress;

    public static SocketPassiveConnector connectorFor(TransportAddress address,
                                                      int backlog,
                                                      Time timeout,
                                                      SocketParameters socketParameters)
            throws IOException {
        ServerSocket acceptor = new ServerSocket(address.getPort(), backlog, address.inetAddress());
        acceptor.setReuseAddress(true);

        if (timeout != null) {
            acceptor.setSoTimeout((int) timeout.toMillis());
        }

        return new SocketPassiveConnector(acceptor, timeout, socketParameters);
    }

    public SocketPassiveConnector(ServerSocket acceptor, Time timeout, SocketParameters socketParameters) {
        assertNotNull(acceptor);
        this.acceptor = acceptor;

        this.timeout = timeout;

        assertNotNull(socketParameters);
        this.socketParameters = socketParameters;

        this.boundAddress = new TransportAddress(this.acceptor.getInetAddress(), this.acceptor.getLocalPort());
    }

    public TransportAddress boundAddress() {
        return this.boundAddress;
    }

    public void close() {
        IOTools.close(this.acceptor);
    }

    public SyncRawConnection connect()
            throws OperationException {
        Socket socket = null;

        try {
            socket = this.acceptor.accept();
            initSocket(socket);

            return new SocketRawConnection(socket);
        } catch (SocketTimeoutException e) {
            IOTools.close(socket);
            throw new OperationTimeoutException();
        } catch (Exception e) {
            IOTools.close(socket);
            throw new OperationException(e);
        }
    }

    @Override
    public String toString() {
        return "SocketPassiveConnector {"
               + "acceptor=" + this.acceptor
               + ", timeout=" + this.timeout
               + ", socketParameters=" + this.socketParameters
               + ", boundAddress=" + this.boundAddress
               + '}';
    }

    private void initSocket(Socket socket)
            throws Exception {
        if (this.timeout != null) {
            socket.setSoTimeout((int) this.timeout.toMillis());
        }

        this.socketParameters.applyTo(socket);
    }
}
