module Potato
  module IRC
    # A single client that intends to use dAmn.
    class Client
      include Events
      
      # The connection to the IRC user associated with this instance.
      # @return [TCPSocket]
      attr_reader :socket
      
      # The dAmn connection associated with this instance.
      # @return [DAmn::Client]
      attr_reader :client
      
      # Configuration value store
      # @return [OpenStruct]
      attr_reader :config
      
      # Have we logged in?
      # @return [Boolean]
      attr_accessor :logged_in
      
      def initialize sock
        @config = OpenStruct.new({:last_line => ""})
        @socket = sock
        @client = Potato::DAmn::Client.new(self)
        @logged_in = false
        @users = {}
      end
      
      # Triggered when a user first connects.
      # @return [void]
      def welcome!
        send_packet :cmd => "localhost", :args => ["001", @config.nick],
          :content => "Welcome to potato #{@config.nick}!#{@config.nick}@chat.deviantart.com"
        send_packet :cmd => "localhost", :args => ["002", @config.nick],
          :content => "Your host is localhost, running version potato0.1"
        send_packet :cmd => "localhost", :args => ["004", @config.nick,
            "iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj"]
        send_packet :cmd => "localhost", :args => ["005", @config.nick,
          %w[UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60
            NICKLEN=20 CHANNELLEN=100 TOPICLEN=8192 KICKLEN=8192 AWAYLEN=8192 MAXTARGETS=20]].flatten,
            :content => "are supported by this server"
        send_packet :cmd => "localhost", :args => ["005", @config.nick,
          %w[MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ NETWORK=dAmn ELIST=MNUCT STATUSMSG=~&@%+]].flatten,
          :content => "are supported by this server"
        send_packet :cmd => @config.nick, :args => ["MODE", @config.nick], :content => "+i"
        greeting = <<-EOG
Welcome to dAmn, #{@config.nick}!
To connect you need to give me some information.
Type /pass yourpassword to authenticate.
        EOG
        greeting.split(/\n/).each do |g|
          send_packet :colon => false, :cmd => "NOTICE", :args => %w[AUTH], :content => "*** #{g}"
        end
      end
      
      # Returns an IRC hostname.
      # @param [String] user user to generate a hostname for
      # @return [String]
      def host_for user
        "%s!%s@chat.deviantart.com" % [user, user]
      end
      
      # Rejects one or more join requests if the user isn't logged in.
      # @param [Array<String>] chans channels to notify for
      # @return [void]
      def cannot_join *chans
        chans.each do |chan|
          send_packet :cmd => "localhost", :args => [473, @config.nick, chan],
            :content => "Not authenticated"
        end
      end
      
      # Notify that you have joined a room
      # @param [String] room the joined room
      # @return [void]
      def join room
        send_packet :cmd => @config.host, :args => "JOIN", :content => "##{room}"
      end
      
      # Notify that you have parted a room
      # @param [String] room the parted room
      # @return [void]
      def part room
        send_packet :cmd => @config.host, :args => "PART", :content => "##{room}"
      end
      
      # Notify that another user has joined a room
      # @param [String] user the user that has joined
      # @param [String] room the room that the user has joined
      # @param [Integer] conns the number of connections the user has in which they are joined to this channel
      # @param [String] privclass the name of the privclass in which the user can be found
      # @return [void]
      def joins user, room, conns, privclass, reason
        if conns < 2
          @users[room] << User.new(user, privclass, sym(@client.privclasses[room][privclass]), 
            @client.privclasses[room][privclass])
          send_packet :cmd => host_for(user), :args => "JOIN", :content => "##{room}"
          send_packet :cmd => "localhost", :args =>
            ["MODE", "##{room}", "+#{sym_to_level(sym(@client.privclasses[room][privclass]))}", user]
        else
          chan_notice(room, "#{user} has joined again (now joined #{conns} times)")
        end
      end
      
      # Notify that a user has parted a room
      # @param [String] user the user that has parted
      # @param [String] room the room that the user has parted
      # @param [Integer] conns the number of connections the user has in which they are joined to this channel
      # @param [String] privclass
      # @return [void]
      def parts user, room, conns, privclass, reason
        if conns < 1
          @users[room].delete_if{|x|x.username == user}
          send_packet :cmd => host_for(user), :args => ["PART", "##{room}"], :content => reason
        else
          chan_notice(room, "#{user} has parted (now joined #{conns} time#{"s" unless conns == 1})")
        end
      end
      
      # A /names list for the current room.
      # @param [String] room the room to query
      # @param [Array<DAmn::Packet>] packets
      # @return [void]
      def names room, packets
        @users[room] = packets.map{|x|User.new(x.param, x.args[:pc], sym(@client.privclasses[room][x.args[:pc]]),
            @client.privclasses[room][x.args[:pc]])}
        send_packet :cmd => "localhost", :args => [353, @config.nick, "=", "##{room}"],
          :content => @users[room].reject{|k|k.username == @config.nick}.map{|k|k.symbol + k.username}.join(" ")
        send_packet :cmd => "localhost", :args => [366, @config.nick, "##{room}"],
          :content => "End of /NAMES list."
        me_mode = sym_to_level(sym(@client.privclasses[room][packets.find{|x|x.param == @config.nick}.args[:pc]]))
        unless me_mode.empty?
          send_packet :cmd => "localhost", :args => 
            ["MODE", "##{room}", "+#{me_mode}", @config.nick]
        end
      end
      
      # A /whois query for a user.
      # @param [String] name the queried user
      # @param [DAmn::Packet] packet
      # @return [void]
      def whois name, packet
        send_packet :cmd => "localhost", :args => [311, @config.nick, name, name, "chat.deviantart.com", "*"],
          :content => packet.subpkts[0].args[:realname]
        send_packet :cmd => "localhost", :args => [307, @config.nick, name],
          :content => "is a registered nick"
        send_packet :cmd => "localhost", :args => [319, @config.nick, name],
          :content => packet.subpkts.select{|pk|pk.cmd == "ns"}.map{|x|x.param.sub("chat:", "#")}.uniq.join(" ")
        send_packet :cmd => "localhost", :args => [312, @config.nick, name, "localhost"],
          :content => "potato"
        send_packet :cmd => "localhost", :args => [317, @config.nick, name,
          packet.subpkts.find{|pk|pk.cmd == "conn"}[:idle],
          (Time.now - packet.subpkts.find{|pk|pk.cmd == "conn"}[:online].to_i).to_i],
          :content => "seconds idle, signon time"
        send_packet :cmd => "localhost", :args => [318, @config.nick, name],
          :content => "End of /WHOIS list."
      end
      
      # Convert a privclass level to a ~&@%+ symbol
      # @param [#to_i] level the privclass level
      # @return [String]
      def sym level
        case level.to_i
        when 0..30
          ""
        when 31..70
          "+"
        when 71..80
          "%"
        when 81..98
          "@"
        when 99
          "~"
        end
      end
      
      # Convert a ~&@%+ symbol to a mode qaohv
      # @param [String] sym the symbol to convert
      # @return [String]
      def sym_to_level sym
        case sym
        when ""
          ""
        when "~"
          "q"
        when "@"
          "o"
        when "+"
          "v"
        when "%"
          "h"
        end
      end
      
      # Notify that the topic has been set in the current room
      # @param [String] room the room in which the topic has been set
      # @param [String] topic the topic set
      # @param [String] by the user who set the topic
      # @param [String] time the time in seconds (since epoch) at which the topic was set
      # @return [void]
      def topic room, topic, by, time
        send_packet :cmd => "localhost", :args => [332, @config.nick, "##{room}"],
          :content => topic
        send_packet :cmd => "localhost", :args => [333, @config.nick, "##{room}", by, time]
      end
      
      # Display a message from a user in a room.
      # @param [String] room the receiving room
      # @param [String] user the sayer of the line
      # @param [String] line the line said
      # @return [void]
      def msg room, user, line
        line.gsub("<br>", "\n").split(/\n/).each do |l|
          send_packet :cmd => host_for(user), :args => ["PRIVMSG", "##{room}"],
            :content => l
        end
      end
      
      # Display an action from a user in a room.
      # @param [String] room the receiving room
      # @param [String] user the perpetrator of the action
      # @param [String] line the action perpetrated
      # @return [void]
      def action room, user, line
        line.gsub("<br>", "\n").split(/\n/).each do |l|
          send_packet :cmd => host_for(user), :args => ["PRIVMSG", "##{room}"],
            :content => "\x01ACTION #{l}\x01"
        end
      end
      
      # Notify that a user has been kicked from a room.
      # @param [String] room the room from which somebody has been kicked
      # @param [String] user the kickee
      # @param [String] by the kicker
      # @param [String] line the reason for kicking
      # @return [void]
      def kick room, user, by, line
        send_packet :cmd => host_for(by), :args => ["KICK", "##{room}", user], :content => line || by
      end
      
      # Notify that a privclass has been created in a room.
      # @param [String] room the room in which has been added the privclass
      # @param [String] privclass the name of the created privclass
      # @param [String] privs the privileges of the created privclass
      # @param [String] by the creator of the privclass
      # @return [void]
      def create_privclass room, privclass, privs, by
        chan_notice room, "Privilege class \x16#{privclass}\x0F has been created by #{by} with {#{privs.split(" ").map{|x|x.split("=").join(": ")}.join(", ")}}"
      end
      
      # Notify that a privclass has been updated in a room.
      # @param [String] room the room in which has been updated the privclass
      # @param [String] privclass the name of the updated privclass
      # @param [String] privs the privileges of the created privclass
      # @param [String] by the updator of the privclass
      # @return [void]
      def update_privclass room, privclass, privs, by
        chan_notice room, "Privilege class \x16#{privclass}\x0F has been updated by #{by} with {#{privs.split(" ").map{|x|x.split("=").join(": ")}.join(", ")}}"
        users = @users[room].select{|x|x.privclass == privclass}
        mode = ""
        count = 0
        unless users[0].symbol == sym(@client.privclasses[room][privclass])
          unless users[0].symbol.empty?
            mode << "-#{sym_to_level(users[0].symbol) * users.size}"
            count += 1
          end
          unless sym(@client.privclasses[room][privclass]).empty?
            mode << "+#{sym_to_level(sym(@client.privclasses[room][privclass])) * users.size}"
            count += 1
          end
          send_packet :cmd => "localhost", :args =>
            ["MODE", "##{room}", mode, *(users.map(&:username) * count)]
          users.each do |user|
            user.symbol = sym(@client.privclasses[room][privclass])
            user.privclass = privclass
            user.level = @client.privclasses[room][privclass]
          end
        end
      end
      
      # Notify that a privclass has been renamed in a room.
      # @param [String] room the room in which has been renamed the privclass
      # @param [String] prev the previous name of the privclass
      # @param [String] priv the new name of the privclass
      # @param [String] by the renamer of the privclass
      # @return [void]
      def rename_privclass room, prev, priv, by
        chan_notice room, "Privclass \x16#{prev}\x0F has been renamed to \x16#{priv}\x0F by #{by}"
      end
      
      # Notify that a privclass has been removed from a room.
      # @param [String] room the room from which the privclass has been removed
      # @param [String] privclass the name of the removed privclass
      # @param [String] by the remover of the privclass
      # @return [void]
      def remove_privclass room, privclass, by
        chan_notice room, "Privclass \x16#{privclass}\x0F has been removed by #{by}"
      end
      
      # Notify that a user has been moved from one privclass to another.
      # @param [String] room the room in which the user has been moved
      # @param [String] user the name of the moved user
      # @param [String] privclass the privclass to which the user has been moved
      # @param [String] by the mover of the user
      # @return [void]
      def move_user room, user, privclass, by
        chan_notice room, "#{user} has been moved to \x16#{privclass}\x0F by #{by}"
        us = @users[room].find{|x|x.username == user}
        mode = ""
        count = 0
        unless us.symbol == sym(@client.privclasses[room][privclass])
          unless us.symbol.empty?
            mode << "-#{sym_to_level(us.symbol)}"
            count += 1
          end
          unless sym(@client.privclasses[room][privclass]).empty?
            mode << "+#{sym_to_level(sym(@client.privclasses[room][privclass]))}"
            count += 1
          end
          send_packet :cmd => "localhost", :args =>
            ["MODE", "##{room}", mode, *([us.username] * count)]
          us.symbol = sym(@client.privclasses[room][privclass])
          us.privclass = privclass
          us.level = @client.privclasses[room][privclass]
        end
      end
      
      # Sends you an auth notice (in the server window)
      # @param [String] line the line to display
      # @return [void]
      def notice line
        send_packet :colon => false, :cmd => "NOTICE", :args => "AUTH", :content => "*** #{line}"
      end
      
      # Sends you a channel notice
      # @param [String] room the room to notice
      # @param [String] line the line to display
      # @return [void]
      def chan_notice room, line
        send_packet :cmd => "localhost", :args => ["NOTICE", "##{room}"], :content => line
      end
      
      # Compiles a packet and sends it to the server
      # @param [Hash] opts a list of options
      # @option opts [String] :cmd
      # @option opts [Array] :args
      # @option opts [String, nil] :content
      # @option opts [Boolean] :colon whether to put a colon in front or not
      # @see IRC::Packet
      # @return [void]
      def send_packet opts = {}
        opts = ({:colon => true, :cmd => "", :args => [], :content => nil}).merge(opts)
        str = opts[:colon] ? ":" : ""
        str << opts[:cmd] << " " << Array(opts[:args]).join(" ")
        str << " :#{opts[:content]}" if opts[:content]
        @socket.write(str + "\r\n")
        debug str
      end
      
      # Sends debug messages
      # @param [Array] strs all messages to send
      # @return [void]
      def debug *strs
        Server.debug *[strs, :irc_out]
      end
    end
  end
end