require 'socket'
require 'nkf'

module DIP
  module IPMessenger
    module Protocol
      # command 
      IPMSG_NOOPERATION=0x00000000
      IPMSG_BR_ENTRY=0x00000001
      IPMSG_BR_EXIT=0x00000002
      IPMSG_ANSENTRY=0x00000003
      IPMSG_BR_ABSENCE=0x00000004
      
      IPMSG_BR_ISGETLIST=0x00000010
      IPMSG_OKGETLIST=0x00000011
      IPMSG_GETLIST=0x00000012
      IPMSG_ANSLIST=0x00000013
      IPMSG_BR_ISGETLIST2=0x00000018

      IPMSG_SENDMSG=0x00000020
      IPMSG_RECVMSG=0x00000021
      IPMSG_READMSG=0x00000030
      IPMSG_DELMSG=0x00000031
      IPMSG_ANSREADMSG=0x00000032

      IPMSG_GETINFO=0x00000040
      IPMSG_SENDINFO=0x00000041

      IPMSG_GETABSENCEINFO=0x00000050
      IPMSG_SENDABSENCEINFO=0x00000051

      # option for all command
      IPMSG_ABSENCEOPT=0x00000100
      IPMSG_SERVEROPT=0x00000200
      IPMSG_DIALUPOPT=0x00010000

      # option for send command  
      IPMSG_SENDCHECKOPT=0x00000100
      IPMSG_SECRETOPT=0x00000200
      IPMSG_BROADCASTOPT=0x00000400
      IPMSG_MULTICASTOPT=0x00000800
      IPMSG_NOPOPUPOPT=0x00001000
      IPMSG_AUTORETOPT=0x00002000
      IPMSG_RETRYOPT=0x00004000
      IPMSG_PASSWORDOPT=0x00008000
      IPMSG_NOLOGOPT=0x00020000
      IPMSG_NEWMUTIOPT=0x00040000
      IPMSG_NOADDLISTOPT=0x00080000
      IPMSG_READCHECKOPT=0x00100000
      IPMSG_SECRETEXOPT=(IPMSG_READCHECKOPT|IPMSG_SECRETOPT)

      PORT = 2425
      BUFSIZE = 8192
      PROTCOL_VER = 1
    end

    class Addr
      def self.new_with_socaddr(ary)
	Addr.new(ary[3], ary[1])
      end

      def initialize(addr, port)
	@addr = addr
	@port = port
      end
      attr_reader :addr, :port
    end
    
    class Event
      include Protocol
      def initialize(buf, from)
	@buf = buf
	@from = Addr.new_with_socaddr(from)
	
	@telegram = @buf.split(':', 6)
	@command = @telegram[4].to_i
	@cmd_no  = @command & 0x000000ff
	@packet_no = @telegram[1]
      end
      
      attr_reader :buf, :from
      attr_reader :telegram, :command, :cmd_no, :packet_no

      def addr; @from.addr; end
      def port; @from.port; end

      def supplement
	@telegram[5]
      end

      def sender
	supplement.split("\0")
      end

      def opt?(flag)
	(flag & @command) != 0
      end
      
      def opt_absence
	opt?(IPMSG_ABSENCEOPT)
      end
      
      def opt_sendcheck
	opt?(IPMSG_SENDCHECKOPT)
      end
      
      def opt_secret
	opt?(IPMSG_SECRETOPT)
      end

      def opt_readcheck
	opt?(IPMSG_READCHECKOPT)
      end
      
      def opt_broadcast
	opt?(IPMSG_BROADCASTOPT)
      end
      
      def opt_autoret
	opt?(IPMSG_AUTORETOPT)
      end
    end
    
    class Message
      include Protocol

      def self.make_key(ev)
	"#{ev.addr}:#{ev.packet_no}"
      end

      def self.new_with_event(ep, event)
	message = event.supplement.split("\0").join("")
	self.new(ep, nil, event.from, message, event.packet_no, event)
      end

      def initialize(end_point, to, from, message, msg_id, event=nil)
	@end_point = end_point
	@to = to
	@from = from
	@message = message
	@msg_id = msg_id
	@opened = false
	@event = event
	@key = nil
	@key = make_key
	@time = Time.now
      end
      attr_reader :to, :from, :message, :msg_id, :event, :key
      attr_accessor :opened, :time

      def from_addr; @from.addr; end
      def to_addr; @to.addr; end

      def sent?
	return false if @event
	true
      end
      
      def open
	return if @opened
	@opened = true
	return unless @event
	if @event.opt_secret
	  send_to_from(IPMSG_READMSG|IPMSG_READCHECKOPT, @msg_id)
	end
      end
      
      private
      def make_key
	if @event
	  self.class.make_key(@event)
	else
	  @msg_id.to_s
	end
      end

      def send_to_from(cmd, supplement)
	@end_point.send(@from.addr, @from.port, cmd, supplement)
      end
    end

    class MessageHolder
      def initialize(ep)
	@endpoint = ep
	@ary = []
	@hash = {}
      end
      
      def add(ev)
	msg = Message.new_with_event(@endpoint, ev)
	@ary.push(msg)
	@hash[msg.key] = msg
	msg
      end

      def add_sent(host, port, msg, msg_id)
	msg = Message.new(@endpoint,  Addr.new(host, msg), @endpoint.addr,
			  msg, msg_id)
	@ary.push(msg)
	@hash[msg.key] = msg
	msg
      end
      
      def include?(ev)
	key = Message.make_key(ev)
	@hash.include?(key)
      end

      def [](key)
	@hash[key]
      end
      
      def each
	@ary.reverse_each {|x| yield(x)}
      end

      def has_unopened?
	@ary.each do |x|
	  next if x.sent?
	  return true unless x.opened
	end
	return false
      end
    end

    class Seq
      include MonitorMixin
      def initialize
	super
	@seq = Time.now.to_i
      end
      
      def succ
	synchronize do
	  @seq += 1
	  return @seq
	end
      end
    end

    class EndPoint
      include Socket::Constants
      include Protocol
      
      def initialize(username, nickname, group)
	@username = username
	@hostname = Socket.gethostname
	@nickname = nickname
	@absence_mode = false
	@absence_msg = ""
	@group = group
	@socket = UDPSocket.open
	@socket.bind("", PORT)
	@socket.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
	@members = Hash.new
	@broadcast_addr = ["<broadcast>"]
	@port = PORT
	@packet_no = Seq.new
	@msg_holder = MessageHolder.new(self)
	@addr = Addr.new_with_socaddr(@socket.addr)
      end
      attr_reader :socket, :msg_holder
      attr_accessor :members, :broadcast_addr
      attr_reader :port
      attr_reader :addr
      attr_reader :absence_msg

      def kconv(str)
	return nil if str == nil
	NKF.nkf('-edXm0', str.to_s)
      end

      def log(msg, flag=nil)
	return unless $DEBUG
	print kconv(msg)
      end
      
      def add_broadcast(addr)
	@broadcast_addr << addr
      end

      def login
	send_BR_ENTRY
      end

      def logout
	send_BR_EXIT
      end

      def absence(msg, mode)
	if mode
	  @absence_msg = "[" + msg + "]"
	  @absence_msg = "ABSENCE" if msg == nil || msg.size == ""
	  command = IPMSG_BR_ABSENCE|IPMSG_ABSENCEOPT
	  @absence_mode = true
	else
	  @absence_msg = ""
	  command = IPMSG_BR_ABSENCE
	  @absence_mode = false
	end
	broadcast(command, "#{@nickname}#{@absence_msg}\0#{@group}")
      end

      def receive_msg(msg)
      end

      def add_member(addr, nickname, group, absence)
	@members[addr] = {"nickname" => kconv(nickname),
	  "group" => kconv(group),
	  "addr"  => kconv(addr),
	  "absence" => absence}
      end

      def remove_member(addr)
	@members.delete(addr)
      end

      def telegram(command, supplement)
	seq = @packet_no.succ
	str = "#{PROTCOL_VER}:#{seq}:#{@username}:#{@hostname}:#{command}:#{supplement}"
	str = NKF.nkf('-sdXm0', str)
	return seq, str
      end

      def send_msg(host, msg, secret)
	mode = secret ? IPMSG_SECRETOPT : 0
	seq, buf = telegram(IPMSG_SENDMSG|mode, msg)
	@msg_holder.add_sent(host, @port, msg, seq)
	begin
	  @socket.send(buf, 0, host, @port)
	rescue SystemCallError
	  log([$!, $@].inspect)
	end
	seq
      end

      def send(addr, port, command, supplement)
	seq, msg = telegram(command, supplement)
	log(">>>>>Send to #{addr} - \"#{msg}\"\n")
	begin
	  @socket.send(msg, 0, addr, port)
	rescue SystemCallError
	  log([$!, $@].inspect)
	end
	seq
      end

      def broadcast(command, supplement)
	seq, msg = telegram(command, supplement)
	log(">>>>>Broadcast message - \"#{msg}\"\n")

	@broadcast_addr.each do |addr|
	  begin
	    @socket.send(msg, 0, addr, @port)
	  rescue SystemCallError
	    log([$!, $@].inspect)
	  end
	end
	seq
      end

      def next_event
	buf, addr = @socket.recvfrom(BUFSIZE) 
	log("<<<<< Recieve from #{addr[3]} - \"#{buf}\"\n", :debug)
	Event.new(kconv(buf), addr)
      end

      def process_command
	while true
	  begin
	    ev = next_event
	  rescue
	    next
	  end

	  dispatch_event(ev)
	end
      end

      def dispatch_event(ev)
	case ev.cmd_no
	when IPMSG_BR_ENTRY
	  on_BR_ENTRY(ev)
	when IPMSG_BR_EXIT
	  on_BR_EXIT(ev)
	when IPMSG_ANSENTRY
	  on_ANSENTRY(ev)
	when IPMSG_BR_ABSENCE
	  on_BR_ABSENCE(ev)
	when IPMSG_SENDMSG
	  on_SENDMSG(ev)
	when IPMSG_RECVMSG
	  on_RECVMSG(ev)
	when IPMSG_READMSG
	  on_READMSG(ev)
	when IPMSG_ANSREADMSG
	  on_ANSREADMSG(ev)
	when IPMSG_BR_ISGETLIST2
	  on_BR_ISGETLIST2(ev)
	when IPMSG_OKGETLIST
	  on_OKGETLIST(ev)
	when IPMSG_GETLIST
	  on_GETLIST(ev)
	when IPMSG_ANSLIST
	  on_ANSLIST(ev)
	when IPMSG_GETINFO
	  on_GETINFO(ev)
	when IPMSG_SENDINFO
	  on_SENDINFO(ev)
	when IPMSG_GETABSENCEINFO
	  on_GETABSENCEINFO(ev)
	when IPMSG_SENDABSENCEINFO
	  on_SENDABSENCEINFO(ev)
	end
      end

      private
      def on_BR_ENTRY(ev)
	ev_add_member(ev)
	if @absence_mode
	  send(ev.addr, ev.port, IPMSG_ANSENTRY|IPMSG_ABSENCEOPT, 
	       "#{@nickname}#{@absence_msg}\0#{@group}")
	else
	  send(ev.addr, ev.port, IPMSG_ANSENTRY, "#{@nickname}\0#{@group}")
	end
      end
      
      def on_BR_EXIT(ev)
	ev_remove_member(ev)
      end
      
      def on_ANSENTRY(ev)
	ev_add_member(ev)
      end
      
      def on_BR_ABSENCE(ev)
	ev_add_member(ev)
      end

      def on_SENDMSG(ev)
	return nil if known_msg?(ev)
	reply_RECVMSG(ev)
	ev_receive_message(ev)
      end

      def on_RECVMSG(ev); end  # ΊΖΑχ΄ΙΝύ??

      def on_READMSG(ev)
	msg = @msg_holder[ev.supplement]
	msg.open if msg
	# puts "Opened! #{ev.addr}, #{ev.supplement}"
	if ev.opt_readcheck
	  send(ev.addr, ev.port, IPMSG_ANSREADMSG, ev.packet_no)
	end
      end

      def on_ANSREADMSG(ev); end
      def on_BR_ISGETLIST2(ev); end
      def on_OKGETLIST(ev); end
      def on_GETLIST(ev);end
      def on_ANSLIST(ev); end
      
      def on_GETINFO(ev)
	send(ev.addr, ev.port, IPMSG_SENDINFO, "Ruby/Dip 0.1")
      end
      
      def on_SENDINFO(ev); end
      def on_GETABSENCEINFO(ev); end
      def on_SENDABSENCEINFO(ev); end
      
      def known_msg?(ev)
	@msg_holder.include?(ev)
      end

      def reply_RECVMSG(ev)
	return nil unless ev.opt_sendcheck
	return nil if ev.opt_broadcast && ev.opt_autoret
	send(ev.addr, ev.port, IPMSG_RECVMSG, ev.packet_no)
      end
      
      def send_BR_ENTRY
	broadcast(IPMSG_BR_ENTRY|IPMSG_BROADCASTOPT, "#{@nickname}\0#{@group}")
      end
      
      def send_BR_EXIT
	broadcast(IPMSG_BR_EXIT|IPMSG_BROADCASTOPT, "#{@nickname}\0#{@group}")
      end
      
      def ev_add_member(ev)
	sender, sendergroup = ev.sender
	add_member(ev.addr, sender, sendergroup, ev.opt_absence)
      end

      def ev_remove_member(ev)
	remove_member(ev.addr)
      end

      def ev_receive_message(ev)
	msg = @msg_holder.add(ev)
	receive_msg(msg)
      end
    end
  end
end
