#!/usr/bin/env ruby
# vim:encoding=UTF-8:

require 'rubygems'
require 'net/irc'

class NetIrcServer < Net::IRC::Server::Session
	def server_name
		"net-irc"
	end

	def server_version
		"0.0.0"
	end

	def available_user_modes
		"iosw"
	end

	def default_user_modes
		""
	end

	def available_channel_modes
		"om"
	end

	def default_channel_modes
		""
	end

	def initialize(*args)
		super
		@@channels ||= {}
		@@users    ||= {}
		@ping        = false
	end

	def on_pass(m)
	end

	def on_user(m)
		@user, @real = m.params[0], m.params[3]
		@host        = @socket.peeraddr[2]
		@prefix      = Prefix.new("#{@nick}!#{@user}@#{@host}")
		@joined_on   = @updated_on = Time.now.to_i

		post @socket, @prefix, NICK, nick
		@nick = nick
		@prefix = "#{@nick}!#{@user}@#{@host}"

		time = Time.now.to_i
		@@users[@nick.downcase] = {
			:nick       => @nick,
			:user       => @user,
			:host       => @host,
			:real       => @real,
			:prefix     => @prefix,
			:socket     => @socket,
			:joined_on  => time,
			:updated_on => time
		}

		initial_message

		start_ping
	end

	def on_join(m)
		channels = m.params[0].split(/\s*,\s*/)
		password = m.params[1]

		channels.each do |channel|
			unless channel.downcase =~ /^#/
				post @socket, server_name, ERR_NOSUCHCHANNEL, @nick, channel, "No such channel"
				next
			end

			unless @@channels.key?(channel.downcase)
				channel_create(channel)
			else
				return if @@channels[channel.downcase][:users].key?(@nick.downcase)

				@@channels[channel.downcase][:users][@nick.downcase] = []
			end

			mode = @@channels[channel.downcase][:mode].empty? ? "" : "+" + @@channels[channel.downcase][:mode]
			post @socket, server_name, RPL_CHANNELMODEIS, @nick, @@channels[channel.downcase][:alias], mode

			channel_users = ""
			@@channels[channel.downcase][:users].each do |nick, m|
				post @@users[nick][:socket], @prefix, JOIN, @@channels[channel.downcase][:alias]

				case
				when m.index("@")
					f = "@"
				when m.index("+")
					f = "+"
				else
					f = ""
				end
				channel_users += "#{f}#{@@users[nick.downcase][:nick]} "
			end
			post @socket, server_name, RPL_NAMREPLY, @@users[nick][:nick], "=", @@channels[channel.downcase][:alias], "#{channel_users.strip}"
			post @socket, server_name, RPL_ENDOFNAMES, @@users[nick][:nick], @@channels[channel.downcase][:alias], "End of /NAMES list"
		end
	end

	def on_part(m)
		channel, message = *m.params

		@@channels[channel.downcase][:users].each do |nick, f|
			post @@users[nick][:socket], @prefix, PART, @@channels[channel.downcase][:alias], message
		end
		channel_part(channel)
	end

	def on_quit(m)
		message = m.params[0]
		@@channels.each do |channel, f|
			if f[:users].key?(@nick.downcase)
				channel_part(channel)
				f[:users].each do |nick, m|
					post @@users[nick][:socket], @prefix, QUIT, message
				end
			end
		end
		finish
	end

	def on_disconnected
		super
		@@channels.each do |channel, f|
			if f[:users].key?(@nick.downcase)
				channel_part(channel)
				f[:users].each do |nick, m|
					post @@users[nick][:socket], @prefix, QUIT, "disconnect"
				end
			end
		end
		channel_part_all
		@@users.delete(@nick.downcase)
	end

	def on_who(m)
		channel = m.params[0]
		return unless channel

		c = channel.downcase
		case
		when @@channels.key?(c)
			@@channels[c][:users].each do |nickname, m|
				nick = @@users[nickname][:nick]
				user = @@users[nickname][:user]
				host = @@users[nickname][:host]
				real = @@users[nickname][:real]
				case
				when m.index("@")
					f = "@"
				when m.index("+")
					f = "+"
				else
					f = ""
				end
				post @socket, server_name, RPL_WHOREPLY, @nick, @@channels[c][:alias], user, host, server_name, nick, "H#{f}", "0 #{real}"
			end
			post @socket, server_name, RPL_ENDOFWHO, @nick, @@channels[c][:alias], "End of /WHO list"
		end
	end

	def on_mode(m)
	end

	def on_privmsg(m)
		while (Time.now.to_i - @updated_on < 2)
			sleep 2
		end
		idle_update

		return on_ctcp(m[0], ctcp_decoding(m[1])) if m.ctcp?

		target, message = *m.params
		t = target.downcase

		case
		when @@channels.key?(t)
			if @@channels[t][:users].key?(@nick.downcase)
				@@channels[t][:users].each do |nick, m|
					post @@users[nick][:socket], @prefix, PRIVMSG, @@channels[t][:alias], message unless nick == @nick.downcase
				end
			else
				post @socket, nil, ERR_CANNOTSENDTOCHAN, @nick, target, "Cannot send to channel"
			end
		when @@users.key?(t)
			post @@users[nick][:socket], @prefix, PRIVMSG, @@users[t][:nick], message
		else
			post @socket, nil, ERR_NOSUCHNICK, @nick, target, "No such nick/channel"
		end
	end

	def on_ping(m)
		post @socket, server_name, PONG, m.params[0]
	end

	def on_pong(m)
		@ping = true
	end

	def idle_update
		@updated_on = Time.now.to_i
		if logged_in?
			@@users[@nick.downcase][:updated_on] = @updated_on
		end
	end

	def channel_create(channel)
		@@channels[channel.downcase] = {
			:alias      => channel,
			:topic      => "",
			:mode       => default_channel_modes,
			:users      => {@nick.downcase => ["@"]},
		}
	end

	def channel_part(channel)
		@@channels[channel.downcase][:users].delete(@nick.downcase)
		channel_delete(channel.downcase) if @@channels[channel.downcase][:users].size == 0
	end

	def channel_part_all
		@@channels.each do |c|
			channel_part(c)
		end
	end

	def channel_delete(channel)
		@@channels.delete(channel.downcase)
	end

	def post(socket, prefix, command, *params)
		m = Message.new(prefix, command, params.map{|s|
			s.gsub(/[\r\n]/, "")
		})
		socket << m
	rescue
		finish
	end

	def start_ping
		Thread.start do
			loop do
				@ping = false
				time = Time.now.to_i
				if @ping == false && (time - @updated_on > 60)
					post @socket, server_name, PING, @prefix
					loop do
						sleep 1
						if @ping
							break
						end
						if 60 < Time.now.to_i - time
							Thread.stop
							finish
						end
					end
				end
				sleep 60
			end
		end
	end

	# Call when client connected.
	# Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
	def initial_message
		post @socket, server_name, RPL_WELCOME,  @nick, "Welcome to the Internet Relay Network #{@prefix}"
		post @socket, server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
		post @socket, server_name, RPL_CREATED,  @nick, "This server was created #{Time.now}"
		post @socket, server_name, RPL_MYINFO,   @nick, "#{server_name} #{server_version} #{available_user_modes} #{available_channel_modes}"
	end

end


if __FILE__ == $0
	require "optparse"

	opts = {
		:port  => 6969,
		:host  => "localhost",
		:log   => nil,
		:debug => false,
		:foreground => false,
	}

	OptionParser.new do |parser|
		parser.instance_eval do
			self.banner = <<-EOB.gsub(/^\t+/, "")
				Usage: #{$0} [opts]

			EOB

			separator ""

			separator "Options:"
			on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
				opts[:port] = port
			end

			on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
				opts[:host] = host
			end

			on("-l", "--log LOG", "log file") do |log|
				opts[:log] = log
			end

			on("--debug", "Enable debug mode") do |debug|
				opts[:log]   = $stdout
				opts[:debug] = true
			end

			on("-f", "--foreground", "run foreground") do |foreground|
				opts[:log]        = $stdout
				opts[:foreground] = true
			end

			on("-n", "--name [user name or email address]") do |name|
				opts[:name] = name
			end

			parse!(ARGV)
		end
	end

	opts[:logger] = Logger.new(opts[:log], "daily")
	opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO

	#def daemonize(foreground = false)
	#	[:INT, :TERM, :HUP].each do |sig|
	#		Signal.trap sig, "EXIT"
	#	end
	#	return yield if $DEBUG or foreground
	#	Process.fork do
	#		Process.setsid
	#		Dir.chdir "/"
	#		STDIN.reopen  "/dev/null"
	#		STDOUT.reopen "/dev/null", "a"
	#		STDERR.reopen STDOUT
	#		yield
	#	end
	#	exit! 0
	#end

	#daemonize(opts[:debug] || opts[:foreground]) do
	Net::IRC::Server.new(opts[:host], opts[:port], NetIrcServer, opts).start
	#end
end

