#!/usr/bin/env ruby

require 'monitor'
require 'dip/ipmsg'
require 'drb/drb'
require 'drb/eq'
require 'drb/observer'

module DIP
  module IPMessenger
    class Addr
      include DRbUndumped
    end

    class Message
      include DRbUndumped
    end
  end
end

module DIP
  class History
    include DRbUndumped

    def initialize(host, msg, time=nil)
      @host = host
      @msg = msg
      @time = time || Time.now
    end
    attr_reader :time, :host, :msg
  end

  class SortedMembers
    include MonitorMixin
    def initialize(server, key)
      super()
      @server = server
      @key = key
      @ary = nil
      @server.add_observer(self)
    end
    
    def update(event, *arg)
      return unless event == :member
      synchronize do
	@ary = nil
      end
    end
    
    def keys
      synchronize do
	return @ary if @ary
	members = @server.members
	@ary = members.keys.sort { |a, b|
	  a = members[a]
	  b = members[b]
	  (a[@key].to_s) <=> (b[@key].to_s)
	}
      end
    end
  end

  class Recent
    include DRbUndumped
    include MonitorMixin
    def initialize(server, fname='.dip_recent')
      super()
      @server = server
      @fname = fname
      fetch_data
    end
    attr_reader :hosts

    def add(host)
      synchronize do
	@hosts = @hosts.dup
	@hosts.delete(host)
	@hosts.unshift(host)
	@hosts[@num..-1] = nil if @hosts.size > @num
	store_data
      end
    end

    def remove(host)
      synchronize do
	@hosts = @hosts.dup
	@hosts.delete(host)
      end
    end

    def each
      unknown = []
      synchronize do
	members = @server.members
	@hosts.each do |host|
	  unknown.push(host) unless members.include?(host)
	  yield(host, members[host] || {})
	end
      end
    end

    private
    def fetch_data
      @num = 16
      @hosts = []
      begin
	File.open(@fname, "r") do |f|
	  while line = f.gets
	    @hosts.push(line.chomp)
	  end
	end
      rescue
      end
    end
    
    def store_data
      begin
	File.open(@fname, "w") do |f|
	  @hosts.each do |host|
	    f.puts(host)
	  end
	end
      rescue
      end
    end
  end

  class DIPMessenger < IPMessenger::EndPoint
    include DRbUndumped
    include Observable

    def initialize(user, nick, group)
      @history = []
      @recent = Recent.new(self)
      @sorted = {}
      super(user, nick, group);
    end
    attr_reader :recent

    def receive_msg(msg)
      @recent.add(msg.from.addr)
      add_history(msg.from.addr, kconv(msg.message))
      changed
      notify_observers(:recv, msg)
    end

    def send_msg(addr, msg, secret)
      @recent.add(addr)
      super(addr, msg, secret)
    end

    def add_member(addr, nick, group, absence)
      super(addr, nick, group, absence)
      changed
      notify_observers(:member)
    end

    def remove_member(addr)
      super(addr)
      changed
      notify_observers(:member)
    end

    def add_history(addr, msg)
      @history_modified = Time.now
      @history.unshift(History.new(addr, msg))
    end
    
    def member(host)
      members[host] || {}
    end

    def history(after=nil)
      after = Time.at(1) unless after
      @history.each do |h|
	yield(h) if h.time > after
      end
    end
    
    def sorted_members(key)
      sorted = @sorted[key] ||= SortedMembers.new(self, key)
      keys = sorted.keys
      if block_given?
	keys.each do |host|
	  yield(host, @members[host] || {})
	end
      end
      keys
    end

    def update_recent
      @recent.hosts.each do |key|
	@members.delete(key)
      end
      login
    end
    
    def add_broadcast_from_file(fname='.dip_broadcast')
      begin
	File.open(fname, "r") do |f|
	  while line = f.gets
	    add_broadcast(line.chomp)
	  end
	end
      rescue
      end
    end
  end

  class Daemon
    def initialize(phrase)
      real, nick, group = nick_from_file
      @server = DIP::DIPMessenger.new(real, nick, group)
      @server.add_broadcast_from_file
      @server.login
      
      @thread = Thread.start do 
	@server.process_command
      end
      
      @front = make_front(phrase)
    end
    attr_reader :server
    attr_reader :thread
    attr_reader :front

    private
    def make_front(phrase)
      DIP::Front.new(phrase, @server)
    end

    def nick_from_file(fname='.dip_nick')
      str = nil
      File.open(fname) do |f|
	str = f.gets.chomp
	ary = str.split(':')
	raise('invalid .dip_nick') unless ary.size >= 3
	return ary[0,3]
      end
    rescue
      ['Masatoshi SEKI', '³±', '¤â¤â¤°¤ß']
    end
  end

  class Front
    include DRbUndumped
    def initialize(phrase, server)
      @phrase = phrase
      @server = server
    end

    def server(phrase)
      raise "Bad Phrase" unless @phrase == phrase
      @server
    end
  end
end
