/*
 * Class:     net_tomp2p_sctp_core_Sctp
 * Method:    usrsctp_socket
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL
Java_net_tomp2p_sctp_core_Sctp_usrsctp_1socket
    (JNIEnv *env, jclass clazz, jint localPort)
{
    SctpSocket *sctpSocket;
    struct socket *so;
    struct linger linger_opt;
    struct sctp_assoc_value stream_rst;
    uint32_t nodelay = 1;
    size_t i, eventTypeCount;

    struct sctp_event ev;

    sctpSocket = malloc(sizeof(SctpSocket));
    if (sctpSocket == NULL)
    {
        perror("Out of memory!");
        return 0;
    }

    // Register this class as an address for usrsctp. This is used by SCTP to
    // direct the packets received (by the created socket) to this class.
    usrsctp_register_address(sctpSocket);

    so
        = usrsctp_socket(
                AF_CONN,
                SOCK_STREAM,
                IPPROTO_SCTP,
                onSctpInboundPacket,
                /* send_cb */ NULL,
                /* sb_threshold */ 0,
                sctpSocket);
    if (so == NULL)
    {
        perror("usrsctp_socket");
        free(sctpSocket);
        return 0;
    }

    // Make the socket non-blocking. Connect, close, shutdown etc will not block
    // the thread waiting for the socket operation to complete.
    if (usrsctp_set_non_blocking(so, 1) < 0)
    {
        perror("Failed to set SCTP to non blocking.");
        free(sctpSocket);
        return 0;
    }

    // This ensures that the usrsctp close call deletes the association. This
    // prevents usrsctp from calling onSctpOutboundPacket with references to
    // this class as the address.
    linger_opt.l_onoff = 1;
    linger_opt.l_linger = 0;
    if (usrsctp_setsockopt(so, SOL_SOCKET, SO_LINGER, &linger_opt,
                           sizeof(linger_opt)))
    {
        perror("Failed to set SO_LINGER.");
        free(sctpSocket);
        return 0;
    }

    // Enable stream ID resets.
    stream_rst.assoc_id = SCTP_ALL_ASSOC;
    stream_rst.assoc_value = 1;
    if (usrsctp_setsockopt(so, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET,
                           &stream_rst, sizeof(stream_rst)))
    {
        perror("Failed to set SCTP_ENABLE_STREAM_RESET.");
        free(sctpSocket);
        return 0;
    }

    // Nagle.
    if (usrsctp_setsockopt(so, IPPROTO_SCTP, SCTP_NODELAY, &nodelay,
                           sizeof(nodelay)))
    {
        perror("Failed to set SCTP_NODELAY.");
        free(sctpSocket);
        return 0;
    }

    // Subscribe to SCTP events.
    eventTypeCount = sizeof(SCTP_EVENT_TYPES) / sizeof(int);
    memset(&ev, 0, sizeof(ev));
    ev.se_assoc_id = SCTP_ALL_ASSOC;
    ev.se_on = 1;
    for (i = 0; i < eventTypeCount; i++)
    {
        ev.se_type = SCTP_EVENT_TYPES[i];
        if (usrsctp_setsockopt(so, IPPROTO_SCTP, SCTP_EVENT, &ev, sizeof(ev))
                < 0)
        {
            printf("Failed to set SCTP_EVENT type: %i\n", ev.se_type);
            free(sctpSocket);
            return 0;
        }
    }

    sctpSocket->so = so;
    sctpSocket->localPort = (int) localPort;

    return (jlong) (intptr_t) sctpSocket;
}
int
main(int argc, char *argv[])
{
	struct sockaddr_in sin;
	struct sockaddr_conn sconn;
	struct sctp_event event;
	uint16_t event_types[] = {SCTP_ASSOC_CHANGE,
	                          SCTP_PEER_ADDR_CHANGE,
	                          SCTP_SEND_FAILED_EVENT};
	unsigned int i;
#ifdef _WIN32
	SOCKET fd;
#else
	int fd;
#endif
	struct socket *s;
#ifdef _WIN32
	HANDLE tid;
#else
	pthread_t tid;
#endif
	struct sctp_sndinfo sndinfo;
	char line[LINE_LENGTH];

	usrsctp_init(0, conn_output, debug_printf);
	/* set up a connected UDP socket */
#ifdef _WIN32
	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
		printf("socket() failed with error: %ld\n", WSAGetLastError());
	}
#else
	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		perror("socket");
	}
#endif
	memset(&sin, 0, sizeof(struct sockaddr_in));
	sin.sin_family = AF_INET;
#ifdef HAVE_SIN_LEN
	sin.sin_len = sizeof(struct sockaddr_in);
#endif
	sin.sin_port = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
#ifdef _WIN32
	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
		printf("bind() failed with error: %ld\n", WSAGetLastError());
	}
#else
	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
		perror("bind");
	}
#endif
	memset(&sin, 0, sizeof(struct sockaddr_in));
	sin.sin_family = AF_INET;
#ifdef HAVE_SIN_LEN
	sin.sin_len = sizeof(struct sockaddr_in);
#endif
	sin.sin_port = htons(atoi(argv[4]));
	sin.sin_addr.s_addr = inet_addr(argv[3]);
#ifdef _WIN32
	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
		printf("connect() failed with error: %ld\n", WSAGetLastError());
	}
#else
	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
		perror("connect");
	}
#endif
#ifdef _WIN32
	tid = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&handle_packets, (void *)&fd, 0, NULL);
#else
	pthread_create(&tid, NULL, &handle_packets, (void *)&fd);
#endif
#ifdef SCTP_DEBUG
	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
#endif
	usrsctp_register_address((void *)&fd);
	usrsctp_sysctl_set_sctp_ecn_enable(0);
	if ((s = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd)) == NULL) {
		perror("usrsctp_socket");
	}
	/* Enable the events of interest. */
	if (usrsctp_set_non_blocking(s, 1) < 0) {
		perror("usrsctp_set_non_blocking");
	}
	memset(&event, 0, sizeof(event));
	event.se_assoc_id = SCTP_ALL_ASSOC;
	event.se_on = 1;
	for (i = 0; i < sizeof(event_types)/sizeof(uint16_t); i++) {
		event.se_type = event_types[i];
		if (usrsctp_setsockopt(s, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) < 0) {
			perror("setsockopt SCTP_EVENT");
		}
	}
	memset(&sconn, 0, sizeof(struct sockaddr_conn));
	sconn.sconn_family = AF_CONN;
#ifdef HAVE_SCONN_LEN
	sconn.sconn_len = sizeof(struct sockaddr_conn);
#endif
	sconn.sconn_port = htons(atoi(argv[5]));
	sconn.sconn_addr = &fd;
	if (usrsctp_bind(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
		perror("usrsctp_bind");
	}
	memset(&sconn, 0, sizeof(struct sockaddr_conn));
	sconn.sconn_family = AF_CONN;
#ifdef HAVE_SCONN_LEN
	sconn.sconn_len = sizeof(struct sockaddr_conn);
#endif
	sconn.sconn_port = htons(atoi(argv[6]));
	sconn.sconn_addr = &fd;
	if (usrsctp_connect(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
		perror("usrsctp_connect");
	}
	for (;;) {
#ifdef _WIN32
		if (gets_s(line, LINE_LENGTH) == NULL) {
#else
		if (fgets(line, LINE_LENGTH, stdin) == NULL) {
#endif
			if (usrsctp_shutdown(s, SHUT_WR) < 0) {
				perror("usrsctp_shutdown");
			}
			while (usrsctp_finish() != 0) {
#ifdef _WIN32
				Sleep(1000);
#else
				sleep(1);
#endif
			}
			break;
		}
		sndinfo.snd_sid = 1;
		sndinfo.snd_flags = 0;
		sndinfo.snd_ppid = htonl(DISCARD_PPID);
		sndinfo.snd_context = 0;
		sndinfo.snd_assoc_id = 0;
		if (usrsctp_sendv(s, line, strlen(line), NULL, 0, (void *)&sndinfo,
	                          (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
			perror("usrsctp_sendv");
		}
	}
	return (0);
}