/*
 * 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.ConnectionDescriptor;
import com.jscape.util.net.TransportAddress;
import com.jscape.util.net.connection.Connector;
import com.jscape.util.net.connection.SyncRawConnection;

import java.net.Socket;
import java.net.SocketTimeoutException;

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

/**
 * @author Alex Usun
 */
public class SocketConnector
        implements Connector<SyncRawConnection> {

    private final ConnectionDescriptor descriptor;
    private final SocketParameters socketParameters;

    public static SocketConnector connectorTo(TransportAddress address, Time timeout) {
        return new SocketConnector(new ConnectionDescriptor(address, timeout.toMillis()), SocketParameters.defaultParameters());
    }

    public static SocketConnector connectorTo(String host, int port, long timeoutMillis) {
        return connectorTo(new TransportAddress(host, port), Time.millis(timeoutMillis));
    }
    
    public static SocketConnector connectorTo(String host, int port, Time timeout) {
        return connectorTo(new TransportAddress(host, port), timeout);
    }

    public SocketConnector(ConnectionDescriptor descriptor, SocketParameters socketParameters) {
        assertNotNull(descriptor);
        this.descriptor = descriptor;

        assertNotNull(socketParameters);
        this.socketParameters = socketParameters;
    }

    public SocketConnector(ConnectionDescriptor descriptor) {
        this(descriptor, SocketParameters.defaultParameters());
    }

    public TransportAddress remoteAddress() {
        return this.descriptor.getRemoteAddress();
    }

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

        try {
            socket = new Socket();
            initSocket(socket);
            socket.connect(this.descriptor.getRemoteAddress().asSocketAddress(), (int) this.descriptor.getTimeout());

            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 "SocketConnector {"
               + "descriptor=" + this.descriptor
               + ", socketParameters=" + this.socketParameters
               + '}';
    }

    protected void initSocket(Socket socket)
            throws Exception {
        socket.setSoTimeout((int) this.descriptor.getTimeout());
        this.socketParameters.applyTo(socket);

        if (this.descriptor.getLocalAddress() != null) {
            socket.bind(this.descriptor.getLocalAddress().asSocketAddress());
        }
    }
}
