#include "stdhead.h"
#include "lib.h"
#include "advertise.h"
#include "reply.h"
#include "parse.h"
#include "leases.h"

// Global state of the server
struct config_head **subnet_conf;		// Configuration for each subnet
struct lease_details *partial_lease;	// Partial lease data structure
struct lease_details *lease;			// Lease data structure
int subnet_count;					// Number of subnets
struct DUID *server_duid;			// Server's duid

void quit (int signo)
{
   exit (1);
}

int main (int argc, char **argv)
{
    int listening_socket;			// Listening socket for server
    int sending_socket;				// Socket to send replies to clients
    struct sockaddr_in6 server_address; 	// Server socket address
    struct sockaddr_in6 client_address; 	// Client socket address
    char udp_message[MIN_MESSAGE_SIZE]; 	// UDP message buffer
    socklen_t cl = sizeof (client_address);// Client address's length
    
    // Local variables
    int i, result, message_len;
    char name[64];
    fd_set testfds, readfds;
    struct timeval timeout;
    struct DHCP_MESSAGE *message, *sending_message;
    struct OPTIONS *opt_ptr;
    struct IA *ia_ptr;
    struct addr_details available_addr;
    
    // Setup a signal handler for Ctrl-C (quit)
    signal (SIGINT, quit);
    
    // Initialize listening socket
    write_to_log ("Initializing listening socket ...", 1);
    listening_socket = socket (PF_INET6, SOCK_DGRAM, 0);
    INITIALIZE_SOCKADDR (server_address);
    server_address.sin6_port = htons (AGENT_PORT);
    server_address.sin6_addr = in6addr_any;

    // Initialize sending socket
    write_to_log ("Initializing sending socket ...", 1);
    sending_socket = socket (PF_INET6, SOCK_DGRAM, 0);

    // Join dhcpv6 multicast groups
    write_to_log ("Joining multicast group ... ", 1);
    result = mcast_join (listening_socket, "eth0", ALL_DHCP_AGENTS);
    if (result == -1)
    {
	printf ("Server could not join ALL_DHCP_AGENTS multicast group\n");
	exit (1);
    }

    result = mcast_join (listening_socket, "eth0", ALL_DHCP_SERVERS);
    if (result == -1)
    {
	printf ("Server could not join ALL_DHCP_SERVERS multicast group\n");
	exit (1);
    }
    
    // Bind the listening socket to the server address
    write_to_log ("Binding listening socket to server address ...", 1);
    result = bind (listening_socket, (struct sockaddr *) &server_address, sizeof (server_address));
    if (result == -1)
    {
	printf ("Server could not bind to agent port\n");
	exit (1);
    }
    
    // Read server configuration file
    // check getopt(3) manual page for parsing command line options
/*    for (i = 0; i < argc; i++)
	if (!strcmp (argv[i], "--conf"))
	    break;
    if (i < argc)*/
    write_to_log ("Reading server configuration file ...", 1);
	subnet_conf = parse_and_assign (NULL, &subnet_count, 0);
    
#if DEBUG == 1
    printf ("Server configuration file was parsed\n");
#elif DEBUG == 2
    print_config_list_contents (subnet_conf[0]);
    print_server_duid();
#endif

    // Open leases files
    write_to_log ("Reading leases and partial leases ...", 1);
    lease = read_leases_file (LEASES_FILE);
    partial_lease = read_leases_file (PARTIAL_LEASES_FILE);
    // Check for expired nodes in partial lease structure
    write_to_log ("Checking for and discarding expired nodes in partial leases ...", 1);
    remove_expired_nodes_from_partial_lease();
    // Check for expired nodes in lease structure
    write_to_log ("Checking for expired nodes in leases ...", 1);
    check_leases_for_expiry();
    // Dump leases structures to files
    write_to_log ("Dumping back leases and partial leases ...", 1);
    write_leases_file (LEASES_FILE, lease);
    write_leases_file (PARTIAL_LEASES_FILE, partial_lease);

#if DEBUG == 1
    printf ("Server about to enter select function\n");
#endif

    // Add the listening socket to the select function's file descriptor set
    FD_ZERO (&readfds);
    FD_SET (listening_socket, &readfds);
    write_to_log ("\n\n\n-------------------------------------------------\n\n\n", 0);
    while (1)
    {
	testfds = readfds;
	// Set timeout to check expiry of leases
	timeout.tv_sec = FIVE_MINUTES;

	// Check for expired nodes in partial lease structure
	write_to_log ("Checking for and discarding expired nodes in partial leases ...", 1);
	remove_expired_nodes_from_partial_lease();
	// Check for expired nodes in lease structure
	write_to_log ("Checking for expired nodes in leases ...", 1);
	check_leases_for_expiry();
	
	// Dump leases structures to files
	write_to_log ("Dumping back leases and partial leases ...", 1);
	write_leases_file (LEASES_FILE, lease);
	write_leases_file (PARTIAL_LEASES_FILE, partial_lease);
	
	// Wait for activity on the socket
	select (listening_socket + 1, &testfds, 0, 0, &timeout);

	if (FD_ISSET (listening_socket, &testfds))
	{
	    // Receive message from client
	    message_len = recvfrom (listening_socket, udp_message, MIN_MESSAGE_SIZE, 0, (struct sockaddr *) &client_address, &cl);
	    // Change client address port for the purpose of sending back a reply
	    client_address.sin6_port = htons (CLIENT_PORT);
	    message = (struct DHCP_MESSAGE *) malloc (sizeof (struct DHCP_MESSAGE));
	    build_linked_list (udp_message, message_len, message);
	    sending_message = 0;
#if DEBUG == 1
	    printf ("Message type %d received from client with link - local address %s\n",
	   		 message -> u_msg_type.msg_type, inet_ntop (AF_INET6, &client_address.sin6_addr.s6_addr, name, 64));
#elif DEBUG == 2
	    print_linked_list_contents (message);
#endif

	    switch (message -> u_msg_type.msg_type)
	    {
	      case SOLICIT :
		    write_to_log ("Solicit message received from ", 1);
		    write_to_log ((char *) inet_ntop (AF_INET6, &client_address.sin6_addr.s6_addr, name, 64), 0);
		    // Check for validity of solicit message
		    if (!check_message (message, SOLICIT))
		    {
		        BLOG("BLOG: solicit msg invalid\n");   
		        write_to_log ("Solicit message received is invalid. Discarding ...", 1);
			    break;
		    }
		    write_to_log ("Solicit message received is valid.", 1);
		    // Check for duplicate solicit message
		    if (check_for_duplicate_packet (message))
		    {
		        BLOG("BLOG: solicit msg duplicate\n"); 
		        write_to_log ("Solicit message received is duplicate. Discarding ...", 1);
			    break;
		    }
		    // Check if preferred address available or not
		    if (get_pref_address (&available_addr, partial_lease, lease,
		   				subnet_conf[0], client_address, get_options_ptr (message, OPTION_IAADDR)))
		    {
		        BLOG("BLOG: get preferred address\n"); 
			    // send advertise message
			    write_to_log ("Constructing advertise message with preferred IPv6 address...", 1);
			    sending_message = create_advertise_message (message, &available_addr);
			    // Add to partial lease structure
			    write_to_log ("Adding node to partial leases ...", 1);
			    add_node_to_partial_lease_structure (sending_message, &available_addr);
		    }   
		    // Obtain the next available address
		    else if (get_available_address (&available_addr, partial_lease, 
					lease, subnet_conf[0], client_address))
		    {
		        BLOG("BLOG: get available address\n"); 
			    // send advertise message
			    write_to_log ("Constructing advertise message with next available IPv6 address...", 1);
			    sending_message = create_advertise_message (message, &available_addr);
			    // Add to partial lease structure
			    write_to_log ("Adding node to partial leases ...", 1);
			    add_node_to_partial_lease_structure (sending_message, &available_addr);
		    }
		    else
		    {
		        BLOG("BLOG: can not get available address\n"); 
			    // send advertise message with status code option as AddrUnavailable
			    write_to_log ("Constructing advertise message with Address Unavailable status code...", 1);
			    sending_message = create_message_with_status_code (message, ADVERTISE, Success, "Address Unavailable");
		    }
		    break;
		    
		case REQUEST :
		    write_to_log ("Request message received.", 1);
		    // Check for validity of request message
		    if (!check_message (message, REQUEST))
		    {
		        BLOG("Request message received is invalid\n"); 
		        write_to_log ("Request message received is invalid. Discarding ...", 1);		    
			break;
		    }
		    // Check for duplicate request message
		    if (check_for_duplicate_packet (message))
		    {
		        BLOG("Request message received is duplicate.\n"); 
		        write_to_log ("Request message received is duplicate. Discarding ...", 1);
		        break;
		    }
		    // Create reply message
		    write_to_log ("Constructing Reply (for Request) message ...", 1);
		    sending_message = create_reply_message (message);
		    // Move lease node from partial leases structure to leases structure
		    write_to_log ("Moving node from partial lease to lease ...", 1);
		    move_node_from_partial_to_lease (message);
		    break;
	    
		case RENEW :
		case REBIND :
		    if (message -> u_msg_type.msg_type == RENEW)
		      write_to_log ("Renew message received.", 1);
		   else
		   	write_to_log ("Rebind message received.", 1);
		    // Check for validity of renew/rebind message
		   if (!check_message (message, message -> u_msg_type.msg_type))
		   {
			if (message -> u_msg_type.msg_type == RENEW)
		   	   write_to_log ("Renew message received is invalid. Discarding ...", 1);
			else
		   	   write_to_log ("Rebind message received is invalid. Discarding ...", 1);
			break;
		   }
		   if (message -> u_msg_type.msg_type == RENEW)
		   {
			// Check for server duid
			if (!check_duid (OPTION_SERVERID, message, lease))
			{
			    write_to_log ("Failed to find client duid in leases for Renewal of lease.", 1);
			    break;
			}
			write_to_log ("Client duid has been matched in leases for Renewal of lease.", 1);
		   }
		    // Check for client binding
		   if (!check_duid (OPTION_CLIENTID, message, lease))
		   {
			sending_message = create_dummy_reply_message (message);
			opt_ptr = get_options_ptr (sending_message, OPTION_IA);
			ia_ptr = (struct IA *) opt_ptr -> opt_data;
			// Set IA status to indicate no record of client binding			
			ia_ptr -> status = NoBinding;
			break;
		   }
		    // Check all values of the binding for exact match
		    if (!check_for_match (message, lease))
		    {
			sending_message = create_dummy_reply_message (message);
			opt_ptr = get_options_ptr (sending_message, OPTION_IA);
			ia_ptr = (struct IA *) opt_ptr -> opt_data;
			if (message -> u_msg_type.msg_type == RENEW)
			{
			   // Set IA status to indicate mismatch in binding parameters for renew message
			   ia_ptr -> status = RenwNoMatch;
			}
			else if (message -> u_msg_type.msg_type == REBIND)
			{
			   // Set IA status to indicate mismatch in binding parameters for rebind message
			   ia_ptr -> status = RebdNoMatch;
			}
			break;
		    }
		    // Check renewal policy (if number of renewals has exceeded the max allowable number)
		    if (!check_renewal_policy (message, lease))
		    {
			// Valid reply message sent
			sending_message = create_reply_message (message);
		    }
		    else
		    {
			// Address unavailable
			sending_message = create_dummy_reply_message (message);
			opt_ptr = get_options_ptr (sending_message, OPTION_IA);
			ia_ptr = (struct IA *) opt_ptr -> opt_data;
			// Set IA status to indicate unavailabilty of address
			ia_ptr -> status = AddrUnavail;

		      // Move lease node from leases structure to partial leases structure
		      // Anticipating a request message
		      move_node_from_lease_to_partial (message);
		    }
		    break;
		    
		case DECLINE :
		case RELEASE :
		    // Check for validity of message
		    if (!check_message (message, message -> u_msg_type.msg_type)){
                BLOG("BLOG: REL msg invalid\n");
                break;
            }
		    // Check for duplicate decline/release message
		    if (check_for_duplicate_packet (message)){
                BLOG("BLOG: DUP msg\n");
                break;
            }
		    // Create appropriate reply message 
		    if (message -> u_msg_type.msg_type == DECLINE){
			    sending_message = create_reply_message_for_decline (message);
		    }else if (message -> u_msg_type.msg_type == RELEASE){
		        sending_message = create_reply_message_for_release (message);
            }
		    // Delete node from leases structure
		    del_node_from_lease (message, lease);
		    break;
	    
		default :
#if DEBUG == 2
		    printf ("\nInvalid message type received\n");
#endif
		    sending_message = 0;
		    break;
	    }
	    
	    if (sending_message)
	    {
#if DEBUG == 2
	      print_linked_list_contents (sending_message);
#endif
	      message_len = store_in_buffer (sending_message, udp_message);
	      sendto (sending_socket, udp_message, message_len, 0, (struct sockaddr *) &client_address, cl);
		// Free message linked list
   		free_message_mem (sending_message);
	   }
	   // Free message linked list
   	   free_message_mem (message);
	}
    }
}
