/******************************************************************************
* Copyright (c) 2000-2019 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* Krisztian Pandi
******************************************************************************/
//
//  File:               UD_PT.cc
//  Description:        UD test port source
//  Rev:                R2A
//  Prodnr:             CNL 113 702
//


#include "UD_PT.hh"
#include "UD_PortType.hh"
#include "UD_Types.hh"

#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <iostream>

#define DEFAULT_LOCAL_PORT	(50000)
#define DEFAULT_NUM_CONN	(10)

using namespace UD__Types;

namespace UD__PortType {

INTEGER simpleGetMsgLen(const OCTETSTRING& stream, ro__integer& /*args*/)
{
	return stream.lengthof();
}

UD__PT_PROVIDER::UD__PT_PROVIDER(const char *par_port_name)
: PORT(par_port_name)
, debugging(false)
, sock_type(SOCK_STREAM)
, target_fd(-1)
{
	conn_list = NULL;
	num_of_conn  = 0;
	conn_list_length = 0;
	operation_mode=false;

	conn_list_server = NULL;
	num_of_conn_server  = 0;
	conn_list_length_server = 0;
	defaultGetMsgLen = simpleGetMsgLen;
	defaultMsgLenArgs = new ro__integer(NULL_VALUE);
}

UD__PT_PROVIDER::~UD__PT_PROVIDER()
{
	Free(conn_list);
	Free(conn_list_server);
}

void UD__PT_PROVIDER::setUpSocket()
{
	log("entering UD__PT::setUpSocket()");
	log("leaving UD__PT::setUpSocket()");
}

void UD__PT_PROVIDER::closeDownSocket()
{
	log("entering UD__PT::closeDownSocket()");

	for(int a=0;a<conn_list_length_server;a++){
		if(conn_list_server[a].status){
			close(conn_list_server[a].fd);
			//close(conn_list_server[a].conn);

			Handler_Remove_Fd_Read(conn_list_server[a].fd);
			//Handler_Remove_Fd_Read(conn_list_server[a].conn);

			unlink(conn_list_server[a].remote_Addr.sun_path);
		}
	}

	Free(conn_list_server);
	conn_list_server = NULL;

	for(int a=0;a<conn_list_length;a++){
		if(conn_list[a].status){
			close(conn_list[a].fd);
			Handler_Remove_Fd_Read(conn_list[a].fd);
		}

	}

	Free(conn_list);
	conn_list = NULL;

	log("entering UD__PT::closeDownSocket()");
}

void UD__PT_PROVIDER::log(const char *fmt, ...)
{
	if (debugging) {
		TTCN_Logger::begin_event(TTCN_DEBUG);
		TTCN_Logger::log_event("UD test port (%s): ", get_name());
		va_list args;
		va_start(args, fmt);
		TTCN_Logger::log_event_va_list(fmt, args);
		va_end(args);
		TTCN_Logger::end_event();
	}
}

void UD__PT_PROVIDER::logHex(const char *prompt, const OCTETSTRING& msg)
{
	if (debugging) { //if debug
		TTCN_Logger::begin_event(TTCN_DEBUG);
		TTCN_Logger::log_event_str(prompt);
		TTCN_Logger::log_event("Size: %d,\nMsg: ",msg.lengthof());

		for(int i=0; i<msg.lengthof(); i++)
			TTCN_Logger::log_event(" %02x", ((const unsigned char*)msg)[i]);
		TTCN_Logger::log_event("\n");
		TTCN_Logger::end_event();
	}
}

void UD__PT_PROVIDER::set_parameter(const char *parameter_name,
		const char *parameter_value)
{
	log("entering UD__PT::set_parameter(%s, %s)", parameter_name, parameter_value);

	if (!strcmp(parameter_name, "socket_type")) {
#ifdef SOCK_SEQPACKET
		if (!strcasecmp(parameter_value, "SEQPACKET"))
			sock_type = SOCK_SEQPACKET;
		else
#endif
		if (!strcasecmp(parameter_value, "STREAM"))
			sock_type = SOCK_STREAM;
		else if (!strcasecmp(parameter_value, "DGRAM"))
			sock_type = SOCK_DGRAM;
		else
			TTCN_warning("UD__PT_PROVIDER::set_parameter: %s: Invalid %s value '%s'", port_name, parameter_name, parameter_value);
	}

	log("leaving UD__PT::set_parameter(%s, %s)", parameter_name, parameter_value);
}

void UD__PT_PROVIDER::Handle_Fd_Event_Error(int fd)
{
}

void UD__PT_PROVIDER::Handle_Fd_Event_Writable(int fd)
{
}

void UD__PT_PROVIDER::Handle_Fd_Event_Readable(int fd)
{
	log("entering UD__PT::Handle_Fd_Event_Readable()");
	unsigned char msg[65535];        // Allocate memory for possible messages

	//UD__Types::UD__send__data__s parameters_s;
	UD__Types::UD__send__data parameters;

			for(int a=0;a<conn_list_length;a++){

			if(fd == conn_list[a].fd){

				if(conn_list[a].status){
					int size_read = recv(conn_list[a].fd, msg, sizeof(msg), 0);

					if (size_read <= 0){
						//there is an empty message, or error in receive
						//remove the socket
						int close_fd=conn_list[a].fd;
						log("recv() returned %d, removing fd=%d", size_read, close_fd);
						conn_list[a].status=0;
						Free(conn_list[a].buf);
						conn_list[a].buf = NULL;
						num_of_conn--;
						Handler_Remove_Fd_Read(close_fd);
						close(close_fd);

						//notify the port user about that
						UD__Types::UD__connect__result cr;
						cr.id() = a;
						cr.result()().result__code() = UD__Types::UD__Result__code::ERROR_;
						cr.result()().err() = "Connection closed by remote peer";
						incoming_message(cr);

						//unlink(conn_list[a].remote_Addr.sun_path);
					}
					else{
						if (sock_type == SOCK_STREAM) {
							/* append just-read data to the buffer */
							(*conn_list[a].buf)->put_s(size_read, msg);

							bool msgFound = false;
							do {
								/* determine message length by callback function */
								if (conn_list[a].getMsgLen != simpleGetMsgLen) {
									if (conn_list[a].msgLen == -1) {
										OCTETSTRING oct;
										(*conn_list[a].buf)->get_string(oct);
										conn_list[a].msgLen = conn_list[a].getMsgLen.invoke(oct, *conn_list[a].msgLenArgs);
									}
								} else {
									conn_list[a].msgLen = (*conn_list[a].buf)->get_len();
								}
								/* did we find a complete message inside the buffer? */
								msgFound = (conn_list[a].msgLen != -1) && (conn_list[a].msgLen <= (int)conn_list[a].buf[0]->get_len());
								if (msgFound) {
									parameters.data() = OCTETSTRING(conn_list[a].msgLen, conn_list[a].buf[0]->get_data());
									conn_list[a].buf[0]->set_pos((size_t)conn_list[a].msgLen);
									conn_list[a].buf[0]->cut();
									parameters.id() = a;
									incoming_message(parameters);
									conn_list[a].msgLen = -1;
								}
								/* continue to iterate as long as data is left and callback continues to find full messages inside */
							} while (msgFound && conn_list[a].buf[0]->get_len() != 0);
						} else {
							/* SOCK_DGRAM, SOCK_SEQPACKET: forward directly to
							 * testsuite, bypassing buffering + getMsgLen() */
							parameters.data() = OCTETSTRING(size_read, msg);
							parameters.id() = a;
							incoming_message(parameters);
						}
					}
				}
			}
		}




		for(int a=0;a<conn_list_length_server;a++){

			if(conn_list_server[a].status){

				if(fd == conn_list_server[a].fd){

					struct sockaddr_un store_addr;

					strcpy(store_addr.sun_path, conn_list_server[a].remote_Addr.sun_path);

					socklen_t addr_length = sizeof(conn_list_server[a].remote_Addr);

					int cn=0;
					if(num_of_conn<conn_list_length){
						cn=0;

						while(conn_list[cn].status)
						{
							cn++;
						}
					}
					UD__Types::UD__connected result;

					if(
							(conn_list[cn].fd = accept(conn_list_server[a].fd, (struct sockaddr*)&conn_list_server[a].remote_Addr, &addr_length))
							>= 0 ){
						conn_list[cn].status = 1;

						num_of_conn++;

						strcpy(conn_list_server[a].remote_Addr.sun_path, store_addr.sun_path);
						strcpy(conn_list[cn].remote_Addr.sun_path, store_addr.sun_path);

						result.result()().result__code() = UD__Types::UD__Result__code::SUCCESS;
						result.result()().err() = OMIT_VALUE;

					}
					else{
						result.result()().result__code() = UD__Types::UD__Result__code::ERROR_;
						result.result()().err() = "Accept rejected";
					}

					result.id()=cn;
					result.path() = conn_list[cn].remote_Addr.sun_path;

					incoming_message(result);

					Handler_Add_Fd_Read(conn_list[cn].fd);
				}
			}
		}




	//}
	log("leaving UD__PT::Handle_Fd_Event_Readable()");
}

void UD__PT_PROVIDER::user_map(const char *system_port)
{
	log("entering UD__PT::user_map()");

	if (conn_list != NULL) TTCN_error("UD Test Port (%s): Internal error: "
			"conn_list is not NULL when mapping.", port_name);
	conn_list = (conn_data*)Malloc(DEFAULT_NUM_CONN * sizeof(*conn_list));
	num_of_conn=0;
	conn_list_length=DEFAULT_NUM_CONN;
	for(int a=0;a<conn_list_length;a++){conn_list[a].status=0;}

	if (conn_list_server != NULL) TTCN_error("UD Test Port (%s): Internal error: "
			"conn_list_server is not NULL when mapping.", port_name);
	conn_list_server = (conn_data_server*)Malloc(DEFAULT_NUM_CONN * sizeof(*conn_list_server));
	num_of_conn_server=0;
	conn_list_length_server=DEFAULT_NUM_CONN;
	for(int a=0;a<conn_list_length_server;a++){conn_list_server[a].status=0;}

	log("leaving UD__PT::user_map()");
}

void UD__PT_PROVIDER::user_unmap(const char *system_port)
{
	log("entering UD__PT::user_unmap()");

	closeDownSocket();

	log("leaving UD__PT::user_unmap()");
}

void UD__PT_PROVIDER::user_start()
{
}

void UD__PT_PROVIDER::user_stop()
{
}



void UD__PT_PROVIDER::outgoing_send(const UD__Types::UD__close& send_par)
{
	  log("entering UD__PT::outgoing_send(UD__close)");
	  int close_fd=conn_list[send_par.id()].fd;

	  conn_list[send_par.id()].status=0;
	  conn_list[send_par.id()].fd=0;
	  num_of_conn--;
	  Handler_Remove_Fd_Read(close_fd);

	  close(close_fd);

	  log("leaving UD__PT::outgoing_send(UD__close)");
}

void UD__PT_PROVIDER::outgoing_send(const UD__Types::UD__connect& send_par)
{
	//Client connects to certain server
	log("entering UD__PT::outgoing_send(UD__connect)");
	int cn;
	UD__Types::UD__connect__result result;
	struct sockaddr_un localAddr;
	localAddr.sun_family = AF_UNIX;

	strcpy(serveraddr.sun_path,send_par.path());

	if(num_of_conn<conn_list_length){
		cn=0;

		while(conn_list[cn].status)
		{
			cn++;
		}
	}
	else{
		conn_list = (conn_data*)Realloc(conn_list, 2 * conn_list_length * sizeof(*conn_list));
		for(int a=conn_list_length;a<conn_list_length*2;a++){conn_list[a].status=0;}
		cn=conn_list_length;
		conn_list_length*=2;
	}

	if((target_fd = socket(PF_UNIX,sock_type,0))<0) {
		TTCN_error("Cannot open socket \n");
	}

	size_t serveraddrLength;
	serveraddrLength = sizeof(serveraddr.sun_family) + strlen(serveraddr.sun_path);
	serveraddr.sun_family = AF_UNIX;

	if(connect(target_fd, (struct sockaddr *) &serveraddr, serveraddrLength) != 0)
	//if(connect(send_par.id(), (struct sockaddr *) &serveraddr, serveraddrLength) != 0)
	{

		result.result()().result__code() = UD__Types::UD__Result__code::ERROR_;
		result.result()().err() = "Connect was unsuccessful";

	}
	else{
		result.result()().result__code() = UD__Types::UD__Result__code::SUCCESS;
		result.result()().err() = OMIT_VALUE;

	}

	conn_list[cn].fd=target_fd;
	conn_list[cn].status=1;
	conn_list[cn].remote_Addr.sun_family = AF_UNIX;
	conn_list[cn].getMsgLen = defaultGetMsgLen;
	conn_list[cn].msgLenArgs = new ro__integer(*defaultMsgLenArgs);
	if (sock_type == SOCK_STREAM) {
		conn_list[cn].buf = (TTCN_Buffer **)Malloc(sizeof(TTCN_Buffer *));
		*conn_list[cn].buf = new TTCN_Buffer;
		conn_list[cn].msgLen = -1;
	} else
		conn_list[cn].buf = NULL;

	strcpy(conn_list[cn].remote_Addr.sun_path,send_par.path());

	num_of_conn++;

	Handler_Add_Fd_Read(target_fd);


	result.id()=cn;
	//result.path() = send_par.path();

	incoming_message(result);
	log("leaving UD__PT::outgoing_send(UD__connect)");


}

void UD__PT_PROVIDER::outgoing_send(const UD__Types::UD__listen& send_par)
{
	log("entering UD__PT::outgoing_send(UD__listen)");
	int cn;
	UD__Types::UD__listen__result result;
	struct sockaddr_un localAddr;
	localAddr.sun_family = AF_UNIX;

	strcpy(serveraddr.sun_path,send_par.path());

	if(num_of_conn_server<conn_list_length_server){
		cn=0;
		while(conn_list_server[cn].status){cn++;}
	}
	else{
		conn_list_server = (conn_data_server*)Realloc(conn_list_server, 2 * conn_list_length_server * sizeof(*conn_list_server));
		for(int a=conn_list_length_server;a<conn_list_length_server*2;a++){conn_list_server[a].status=0;}
		cn=conn_list_length_server;
		conn_list_length_server*=2;
	}

	if((target_fd = socket(PF_UNIX,sock_type,0))<0) {
		TTCN_warning("Cannot open socket \n");

	}


	unlink(serveraddr.sun_path);
	size_t serveraddrLength;
	serveraddr.sun_family = AF_UNIX;
	serveraddrLength = sizeof(serveraddr.sun_family) + strlen(serveraddr.sun_path);
	if(bind(target_fd, (struct sockaddr *) &serveraddr, serveraddrLength )<0) {
		close(target_fd);
		TTCN_warning("Cannot bind port\n");
		result.result()().result__code() = UD__Types::UD__Result__code::ERROR_;

	}

	if(listen(target_fd, 5)){
		TTCN_warning("Error while listening");
		result.result()().result__code() = UD__Types::UD__Result__code::ERROR_;
	}

	//conn_list_server[cn].fd=sock;
	conn_list_server[cn].fd=target_fd;
	conn_list_server[cn].status=1;
	conn_list_server[cn].remote_Addr.sun_family = AF_UNIX;

	strcpy(conn_list_server[cn].remote_Addr.sun_path,send_par.path());

	num_of_conn_server++;


	Handler_Add_Fd_Read(target_fd);

	//UD__Types::UD__open__result result;

	result.id()=cn;
	result.result()().result__code() = UD__Types::UD__Result__code::SUCCESS;
	result.result()().err() = OMIT_VALUE;

	incoming_message(result);
	log("leaving UD__PT::outgoing_send(UD__listen)");
}

void UD__PT_PROVIDER::outgoing_send(const UD__Types::UD__shutdown& send_par)
{
	log("entering UD__PT::outgoing_send(UD__Shutdown)");

	/**if(num_of_conn > 0){

		if(conn_list[send_par.id()].status!=0){
			int close_fd=conn_list[send_par.id()].fd;
			conn_list[send_par.id()].status=0;
			num_of_conn--;
			Handler_Remove_Fd_Read(close_fd);

			close(close_fd);

			unlink(conn_list[send_par.id()].remote_Addr.sun_path);
		}
		else{
			//it has been already removed
		}
	}*/

	if(conn_list_server[send_par.id()].status!=0){
			int close_fd=conn_list_server[send_par.id()].fd;
			conn_list_server[send_par.id()].status=0;
			num_of_conn_server--;
			Handler_Remove_Fd_Read(close_fd);

			close(close_fd);

			unlink(conn_list_server[send_par.id()].remote_Addr.sun_path);
	}



	log("leaving UD__PT::outgoing_send(UD__Shutdown)");
}

void UD__PT_PROVIDER::outgoing_send(const UD__Types::UD__send__data& send_par)
{
	log("entering UD__PT::outgoing_send(UD__send__data)");
	logHex("Sending data: ", send_par.data());
	int sock;
	struct sockaddr_un targetAddr;
	targetAddr.sun_family = AF_UNIX;


	int cn=send_par.id();

	//targetAddr.sun_path = conn_list[cn].remote_Addr.sun_path;
	strcpy(targetAddr.sun_path,conn_list[cn].remote_Addr.sun_path);

	sock=conn_list[cn].fd;

	int nbytes = send_par.data().lengthof();

	int nrOfBytesSent;
	//if ((nrOfBytesSent = write(sock, send_par.data(), nbytes) ) != nbytes)
	if ((nrOfBytesSent = send(sock, send_par.data(), nbytes, 0) ) != nbytes)
	{
		TTCN_error("Write error");
	}

	log("Nr of bytes sent = %d", nrOfBytesSent);

	if (nrOfBytesSent != send_par.data().lengthof()) {
		TTCN_error("Sendto system call failed: %d bytes was sent instead of %d", nrOfBytesSent, send_par.data().lengthof());
	}

	log("leaving UDPasp__PT::outgoing_send(ASP__UDP__message)");

}

void f__UD__PT_PROVIDER__setGetMsgLen(UD__PT_PROVIDER& portRef, const INTEGER& connId, f__UD__getMsgLen& f, const ro__integer& msgLenArgs)
{
	if ((int)connId == -1) {
		portRef.defaultGetMsgLen = f;
		delete portRef.defaultMsgLenArgs;
		portRef.defaultMsgLenArgs = new Socket__API__Definitions::ro__integer(msgLenArgs);
	} else {
		if (!portRef.isConnIdValid(connId)) {
			TTCN_error("UD: cannot setGetMsgLen: connId %d not valid", (int)connId);
			return;
		}
		portRef.conn_list[(int)connId].getMsgLen = f;
		delete portRef.conn_list[connId].msgLenArgs;
		portRef.conn_list[connId].msgLenArgs = new Socket__API__Definitions::ro__integer(msgLenArgs);
	}
}

void f__UD__setGetMsgLen(UD__PT& portRef, const INTEGER& connId, f__UD__getMsgLen& f, const ro__integer& msgLenArgs) {
	f__UD__PT_PROVIDER__setGetMsgLen(portRef, connId, f, msgLenArgs);
}


}