#! /usr/bin/env ruby
#
# 15-441 Simple IRC test 
#
# You must have passed the previous tests.  For example, you cannot
# pass the "PART" test if your "JOIN" test failed.
# For this reason, the script exits once the server fails a test.
# If you fail a specific test, we suggest you modify this code 
# to print out exactly # what is going on, or perhaps remove tests that
# fail so that you can test other, unrelated tests if you simply haven't
# implemented a particular bit of functionality yet (e.g., you might
# remove the WHO or LIST tests to test PART).
# 
# Part of the reason we give out the code for this 
# script so that you can modify it and write your own test
# scripts later on.  Remember that passing this does not ensure full credit
# on the final IRC server grade:  The final tests will be more extensive
# so you may wish to add your own, more stressful tests to this script for your own use.
#
# Enjoy!
#
# - 15-441 Staff

## SKELETON STOLEN FROM http://www.bigbold.com/snippets/posts/show/1785
require 'socket'

$SERVER = "127.0.0.1"
$PORT = 9034  ########## DONT FORGET TO CHANGE THIS

if ARGV.size == 0
    puts "Usage: ./simpleirc.rb server_port [server_ip_addr]"
    exit
elsif ARGV.size >= 1

begin
    $PORT = Integer(ARGV[0])
rescue
    puts "The port number must be an integer!"
    exit
end
end

if ARGV.size >= 2
    $SERVER = ARGV[1].to_s()
end

puts "Server address - " + $SERVER + ":" + $PORT.to_s()

class IRC

    def initialize(server, port, nick, channel)
        @server = server
        @port = port
        @nick = nick
        @channel = channel
    end

    def recv_data_from_server (timeout)
        pending_event = Time.now.to_i
        received_data = Array.new
        k = 0 
        flag = 0
        while flag == 0
            ## check for timeout
            time_elapsed = Time.now.to_i - pending_event
            if (time_elapsed > timeout)
                flag = 1
            end 
            ready = select([@irc], nil, nil, 0.0001)
            next if !ready
            for s in ready[0]
                if s == @irc then
                    next if @irc.eof
                    s = @irc.gets
                    received_data[k] = s
                    k= k + 1
                end
            end
        end
        return received_data
    end

    def test_silence(timeout)
        data=recv_data_from_server(timeout)
        if (data.size > 0)
            return false
        else
            return true
        end
    end
    
    def send(s)
        # Send a message to the irc server and print it to the screen
        puts "--> #{s}"
        @irc.send "#{s}\n", 0 
    end

    def connect()
        # Connect to the IRC server
        @irc = TCPSocket.open(@server, @port)
    end

    def disconnect()
        @irc.close
    end

    def send_nick(s)
        send("NICK #{s}")
    end
    
    def send_user(s)
        send("USER #{s}")
    end

    def get_motd
        data = recv_data_from_server(1)
        ## CHECK data here
        
        if(data[0] =~ /^:[^ ]+ *375 *gnychis *:- *[^ ]+ *Message of the day - *.\n/)
            puts "\tRPL_MOTDSTART 375 correct"
        else
            puts "\tRPL_MOTDSTART 375 incorrect"
            return false
        end
        
        k = 1
        while ( k < data.size-1)
            
            if(data[k] =~ /:[^ ]+ *372 *gnychis *:- *.*/)
                puts "\tRPL_MOTD 372 correct"
            else
                puts "\tRPL_MOTD 372 incorrect"
                return false
            end
            k = k + 1
        end

        if(data[data.size-1] =~ /:[^ ]+ *376 *gnychis *:End of \/MOTD command/)
            puts "\tRPL_ENDOFMOTD 376 correct"
        else
            puts "\tRPL_ENDOFMOTD 376 incorrect"
            return false
        end
        
        return true
    end

    def send_privmsg(s1, s2)
	send("PRIVMSG #{s1} :#{s2}")
    end
        
    def raw_join_channel(joiner, channel)
        send("JOIN #{channel}")
        ignore_reply()
    end
    
    def join_channel(joiner, channel)
        send("JOIN #{channel}")
        
        data = recv_data_from_server(1);
        if(data[0] =~ /^:#{joiner}.*JOIN *#{channel}/)
            puts "\tJOIN echoed back"
        else
            puts "\tJOIN was not echoed back to the client"
            return false
        end
        
        if(data[1] =~ /^:[^ ]+ *353 *#{joiner} *= *#{channel} *:.*#{joiner}/)
            puts "\tRPL_NAMREPLY 353 correct"
        else
            puts "\tRPL_NAMREPLY 353 incorrect"
            return false
        end
        
        if(data[2] =~ /^:[^ ]+ *366 *#{joiner} *#{channel} *:End of \/NAMES list/)
            puts "\tRPL_ENDOFNAMES 366 correct"
        else
            puts "\tRPL_ENDOFNAMES 366 incorrect"
            return false
        end
        
        return true
    end

    def who(s)
        send("WHO #{s}")
        
        data = recv_data_from_server(1);
        
        if(data[0] =~ /^:[^ ]+ *352 *gnychis *#{s} *please *[^ ]+ *[^ ]+ *gnychis *H *:0 *The MOTD/)
            puts "\tRPL_WHOREPLY 352 correct"
        else
            puts "\tRPL_WHOREPLY 352 incorrect"
            return false
        end

        if(data[1] =~ /^:[^ ]+ *315 *gnychis *#{s} *:End of \/WHO list/)
            puts "\tRPL_ENDOFWHO 315 correct"
        else
            puts "\tRPL_ENDOFWHO 315 incorrect"
            return false
        end
        return true
    end
    
    def list
	send("LIST")
        
        data = recv_data_from_server(1);
        if(data[0] =~ /^:[^ ]+ *321 *gnychis *Channel *:Users Name/)
            puts "\tRPL_LISTSTART 321 correct"
        else
            puts "\tRPL_LISTSTART 321 incorrect"
            return false
        end
        
        if(data[1] =~ /^:[^ ]+ *322 *gnychis *#linux.*1/)
            puts "\tRPL_LIST 322 correct"
        else
            puts "\tRPL_LIST 322 incorrect"
            return false
        end
        
        if(data[2] =~ /^:[^ ]+ *323 *gnychis *:End of \/LIST/)
            puts "\tRPL_LISTEND 323 correct"
        else
            puts "\tRPL_LISTEND 323 incorrect"
            return false
        end
        
        return true
    end
    
    def list2
   	send("LIST")
            
        data = recv_data_from_server(1);
        if(data[0] =~ /^:[^ ]+ *321 *gnychis *Channel *:Users Name/)
            puts "\tRPL_LISTSTART 321 correct"
        else
            puts "\tRPL_LISTSTART 321 incorrect"
            return false
        end
            
        if(data[1] =~ /^:[^ ]+ *322 *gnychis *#linux.*1/)
            puts "\tRPL_LIST 322 correct"
        else
            puts "\tRPL_LIST 322 incorrect"
            return false
        end
        
        if(data[2] =~ /^:[^ ]+ *322 *gnychis *#linux2.*1/)
	   puts "\tRPL_LIST 322 correct"
	else
	   puts "\tRPL_LIST 322 incorrect"
	   return false
        end
            
        if(data[3] =~ /^:[^ ]+ *323 *gnychis *:End of \/LIST/)
            puts "\tRPL_LISTEND 323 correct"
        else
            puts "\tRPL_LISTEND 323 incorrect"
            return false
        end
            
        return true
    end
    
    def list3
      	send("LIST")
                
        data = recv_data_from_server(1);
        if(data[0] =~ /^:[^ ]+ *321 *gnychis *Channel *:Users Name/)
            puts "\tRPL_LISTSTART 321 correct"
        else
            puts "\tRPL_LISTSTART 321 incorrect"
            return false
        end
                
        if(data[1] =~ /^:[^ ]+ *322 *gnychis *#linux.*1/)
            puts "\tRPL_LIST 322 correct"
        else
            puts "\tRPL_LIST 322 incorrect"
            return false
        end
            
        if(data[2] =~ /^:[^ ]+ *322 *gnychis *#linux2.*2/)
  	    puts "\tRPL_LIST 322 correct"
    	else
    	    puts "\tRPL_LIST 322 incorrect"
    	    return false
        end
                
        if(data[3] =~ /^:[^ ]+ *323 *gnychis *:End of \/LIST/)
            puts "\tRPL_LISTEND 323 correct"
        else
            puts "\tRPL_LISTEND 323 incorrect"
            return false
        end
                
        return true
    end
    
    def checkmsg(from, to, msg)
	reply_matches(/^:#{from} *PRIVMSG *#{to} *:#{msg}/, "PRIVMSG")
    end
    
    def check2msg(from, to1, to2, msg)
        data = recv_data_from_server(1);
        if((data[0] =~ /^:#{from} *PRIVMSG *#{to1} *:#{msg}/ && data[1] =~ /^:#{from} *PRIVMSG *#{to2} *:#{msg}/) ||
           (data[1] =~ /^:#{from} *PRIVMSG *#{to1} *:#{msg}/ && data[0] =~ /^:#{from} *PRIVMSG *#{to2} *:#{msg}/))
            puts "\tPRIVMSG to #{to1} and #{to2} correct"
            return true
        else
            puts "\tPRIVMSG to #{to1} and #{to2} incorrect"
            return false
        end
    end
    
    def check_echojoin(from, channel)
	reply_matches(/^:#{from}.*JOIN *#{channel}/,
			    "Test if first client got join echo")
    end
    
    def part_channel(parter, channel)
        send("PART #{channel}")
	reply_matches(/^:#{parter}![^ ]+@[^ ]+ *QUIT *:/)

    end


    def part_channel2(parter, channel)
         send("PART #{channel}")
         data = recv_data_from_server(1);
       	 if(data[0] =~ /^:[^ ]+ *442 *#{parter} *#{channel} *:You're not on that channel/)
       	    puts "\tERR_NOTONCHANNEL 442 correct"
    	 else
            puts "\tERR_NOTONCHANNEL 442 incorrect"
            return false
         end
            
        return true
    end

    def part_channel3(parter, channel)
        send("PART #{channel}")
        data = recv_data_from_server(1);
    	if(data[0] =~ /^:[^ ]+ *403 *#{parter} *#{channel} *:No such channel/)
    	    puts "\tERR_NOSUCHCHANNEL 403 correct"
	else
            puts "\tERR_NOSUCHCHANNEL 403 incorrect"
            return false
        end
        
        return true
    end
    
    def nickerror(nick)
    	send("NICK #{nick}")
    	data = recv_data_from_server(1);
	if(data[0] =~ /^:[^ ]+ *433 *\* *#{nick} *:Nickname is already in use/)
    	    puts "\tERR_NICKNAMEINUSE 433 correct"
	else
            puts "\tERR_NICKNAMEINUSE 433 incorrect"
            return false
        end
        
        return true
    end
    
    def nickchange_echo(nickold,nicknew)
        reply_matches(/^:#{nickold}![^ ]+@[^ ]+ *NICK */)
    end
    
    def check_not_reg(nick,channel,command)
    	send("#{command}")
    	data = recv_data_from_server(1);
    	if(data[0] =~ /^:[^ ]+ *451 *\* *:You have not registered/)
	   puts "\tERR_NOTREGISTERED 451 correct"
	else
	   puts "\tERR_NOTREGISTERED 451 incorrect"
	   return false
	end
	        
        return true
    end
    
    def checkmsg_fail(from, to, msg)
        data = recv_data_from_server(1);
    	if(data[0] =~ /^:[^ ]+ *401 *#{from} *#{to} *:No such nick\/channel/)
           puts "\tERR_NOSUCHNICK 401 correct"
    	else
           puts "\tERR_NOSUCHNICK 401 incorrect"
           return false
        end
            
       return true
    end

    def check_part(parter, channel)
	reply_matches(/^:#{parter}![^ ]+@[^ ]+ *QUIT *:/)
    end

    def ignore_reply
        recv_data_from_server(1)
    end

    def reply_matches(rexp, explanation = nil)
	data = recv_data_from_server(1)
	if (rexp =~ data[0])
	    puts "\t #{explanation} correct" if explanation
	    return true
	else
	    puts "\t #{explanation} incorrect: #{data[0]}" if explanation
	    return false
	end
    end

end


##
# The main program.  Tests are listed below this point.  All tests
# should call the "result" function to report if they pass or fail.
##

$total_points = 0

def test_name(n)
    puts "////// #{n} \\\\\\\\\\\\"
    return n
end

def result(n, passed_exp, failed_exp, passed, points)
    explanation = nil
    if (passed)
	print "(+) #{n} passed"
	$total_points += points
	explanation = passed_exp
    else
	print "(-) #{n} failed"
	explanation = failed_exp
    end

    if (explanation)
	puts ": #{explanation}"
    else
	puts ""
    end
end

def eval_test(n, passed_exp, failed_exp, passed, points = 1)
    result(n, passed_exp, failed_exp, passed, points)
    exit(0) if !passed
end

irc = IRC.new($SERVER, $PORT, '', '')
irc.connect()
begin

########## TEST NICK COMMAND ##########################
# The RFC states that there is no response to a NICK command,
# so we test for this.
    tn = test_name("NICK")
    irc.send_nick("gnychis")
    puts "<-- Testing for silence (5 seconds)..."
    
    eval_test(tn, nil, nil, irc.test_silence(5))
    

############## TEST USER COMMAND ##################
# The RFC states that there is no response on a USER,
# so we disconnect first (otherwise the full registration
# of NICK+USER would give us an MOTD), then check
# for silence
    tn = test_name("USER")

    puts "Disconnecting and reconnecting to IRC server\n"
    irc.disconnect()
    irc.connect()

    irc.send_user("please give me :The MOTD")
    puts "<-- Testing for silence (5 seconds)..."

    eval_test(tn, nil, "should not return a response on its own", 
	      irc.test_silence(5))

############# TEST FOR REGISTRATION ##############
# A NICK+USER is a registration that triggers the
# MOTD.  This test sends a nickname to complete the registration,
# and then checks for the MOTD.
    tn = test_name("Registration")
    irc.send_nick("gnychis")
    puts "<-- Listening for MOTD...";
    
    eval_test(tn, nil, nil, irc.get_motd())

############## TEST JOINING ####################
# We join a channel and make sure the client gets
# his join echoed back (which comes first), then
# gets a names list.
    tn = test_name("JOIN")
    eval_test(tn, nil, nil,
	      irc.join_channel("gnychis", "#linux"))

############## WHO ####################
# Who should list everyone in a channel
# or everyone on the server.  We are only
# checking WHO <channel>.
# The response should follow the RFC.
    tn = test_name("WHO")
    eval_test(tn, nil, nil, irc.who("#linux"))

############## LIST ####################
# LIST is used to check all the channels on a server.
# The response should include #linux and its format should follow the RFC.
    tn = test_name("LIST")
    eval_test(tn, nil, nil, irc.list())

############## PRIVMSG ###################
# Connect a second client that sends a message to the original client.
# Test that the original client receives the message.
    tn = test_name("PRIVMSG")
    irc2 = IRC.new($SERVER, $PORT, '', '')
    irc2.connect()
    irc2.send_nick("gnychis2")
    irc2.send_user("please give me :The MOTD2")
    msg = "clown hat curly hair smiley face"
    irc2.send_privmsg("gnychis", msg)
    eval_test(tn, nil, nil, irc.checkmsg("gnychis2", "gnychis", msg))

############## ECHO JOIN ###################
# When another client joins a channel, all clients
# in that channel should get :newuser JOIN #channel
    tn = test_name("ECHO ON JOIN")
    # "raw" means no testing of responses done
    irc2.raw_join_channel("gnychis2", "#linux")
    irc2.ignore_reply()
    eval_test(tn, nil, nil, irc.check_echojoin("gnychis2", "#linux"))


############## MULTI-TARGET PRIVMSG ###################
# A client should be able to send a single message to
# multiple targets, with ',' as a delimiter.
# We use client2 to send a message to gnychis and #linux.
# Both should receive the message.
    tn = test_name("MULTI-TARGET PRIVMSG")
    msg = "awesome blossom with extra awesome"
    irc2.send_privmsg("gnychis,#linux", msg)
    eval_test(tn, nil, nil, irc.check2msg("gnychis2", "gnychis", "#linux", msg))
    irc2.ignore_reply()

############## PART ###################
# When a client parts a channel, a QUIT message
# is sent to all clients in the channel, including
# the client that is parting.
    tn = test_name("PART")
    eval_test("PART echo to self", nil, nil,
	      irc2.part_channel("gnychis2", "#linux"),
	      0) # note that this is a zero-point test!

    eval_test("PART echo to other clients", nil, nil,
	      irc.check_part("gnychis2", "#linux"))


## MY NEW ADDITIONAL TESTS ##

############## PART2 ###################
# When a client attempts to part a channel that
# he/she is not connected to, an error is sent indicating such
# 
  tn = test_name("PART2")
  eval_test("PART2 echo to self", nil, nil, irc2.part_channel2("gnychis2", "#linux"))
	      
############## PART3 ###################
# When a client attempts to part a channel that
# does not exist, an error is sent indicating such.
# 
  tn = test_name("PART3")
  eval_test("PART3 echo to self", nil, nil, irc2.part_channel3("gnychis2", "#fee"))

############## NICK2 ###################
# Connect a third client that attempts to set a nickname
# already in use
    tn = test_name("NICK2")
    irc3 = IRC.new($SERVER, $PORT, '', '')
    irc3.connect()
    eval_test(tn, nil, nil, irc3.nickerror("gnychis"))

############## NICK3 ###################
# Client 2 connects back to #linux and changes his nick.
# Client 1 should be sent a message indicating such.
#    tn = test_name("NICK3")
#    irc2.raw_join_channel("gnychis2", "#linux")
#    irc2.ignore_reply()
#    eval_test(tn, nil, nil, irc.nickchange_echo("gnychis2", "gnychis2_new"))


############## LIST2 ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
    tn = test_name("LIST2")
    irc2.raw_join_channel("gnychis2", "#linux2")
    eval_test(tn, nil, nil, irc.list2())

############## LIST3 ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel,multi user check)
    tn = test_name("LIST3")
    irc3.send_nick("gnychis3")
    irc3.send_user("please give me :The MOTD2")
    irc3.raw_join_channel("gnychis3", "#linux2")
    eval_test(tn, nil, nil, irc.list3())

############## PRIVMSG2 ###################
# Attempt to send a message to a client not on the server
# An error message is sent back indicating as such.
    tn = test_name("PRIVMSG2")
    msg = "This is gonna fail"
    irc3.send_privmsg("doesnotexist", msg)
    eval_test(tn, nil, nil, irc3.checkmsg_fail("gnychis3", "doesnotexist", msg))

############## JOIN NOT REG ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
#    tn = test_name("JOIN NOT REG")
    irc4 = IRC.new($SERVER, $PORT, '', '')
    irc4.connect()
    eval_test(tn, nil, nil,irc4.check_not_reg("gnychis4", "#linux2", "JOIN #linux"))

############## PART NOT REG ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
    tn = test_name("PART NOT REG")
    eval_test(tn, nil, nil,irc4.check_not_reg("gnychis4", "#linux2", "PART #linux"))

############## LIST NOT REG ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
    tn = test_name("LIST NOT REG")
    eval_test(tn, nil, nil,irc4.check_not_reg("gnychis4", "#linux2", "LIST"))

############## WHO NOT REG ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
    tn = test_name("WHO NOT REG")
    eval_test(tn, nil, nil,irc4.check_not_reg("gnychis4", "#linux2", "WHO #linux"))
    
############## PRIVMSG NOT REG ####################
# LIST is used to check all the channels on a server.
# There should be 2 channels listed now (multi channel check)
   tn = test_name("PRIVMSG NOT REG")
   eval_test(tn, nil, nil,irc4.check_not_reg("gnychis4", "#linux2", "PRIVMSG gnychis :hey"))

## Your tests go here!
# 
# Things you might want to test:
#  - Multiple clients in a channel
#  - Abnormal messages of various sorts
#  - Clients that misbehave/disconnect/etc.
#  - Lots and lots of clients
#  - Lots and lots of channel switching
#  - etc.
##


rescue Interrupt
rescue Exception => detail
    puts detail.message()
    print detail.backtrace.join("\n")
ensure
    irc.disconnect()
    puts "Your score: #{$total_points} / 10"
    puts ""
    puts "Good luck with the rest of the project!"
end
