module Irc

  # base class for all rbot plugins
  # certain methods will be called if they are provided, if you define one of
  # the following methods, it will be called as appropriate:
  # 
  # listen(UserMessage)::
  #                        Called for all NOTICE and PRIVMSG messages. To
  #                        differentiate them, use message.kind_of? It'll be
  #                        either a PrivMessage or a NoticeMessage
  #                              
  # privmsg(PrivMessage)::
  #                        called for a PRIVMSG if the first word matches one
  #                        the plugin register()d for. Use m.plugin to get
  #                        that word and m.params for the rest of the message,
  #                        if applicable.
  #
  # kick(KickMessage)::
  #                        Called when a user (or the bot) is kicked from a
  #                        channel the bot is in.
  #
  # join(JoinMessage)::
  #                        Called when a user (or the bot) joins a channel
  #
  # part(PartMessage)::
  #                        Called when a user (or the bot) parts a channel
  #   
  # save::                 Called when you are required to save your plugin's
  #                        state, if you maintain data between sessions
  #
  # cleanup::              called before your plugin is "unloaded", prior to a
  #                        plugin reload or bot quit - close any open
  #                        files/connections or flush caches here
  class Plugin
    # initialise your plugin. Always call super if you override this method,
    # as important variables are set up for you
    def initialize
      @bot = Plugins.bot
      @names = Array.new
    end

    # return an identifier for this plugin, defaults to a list of the message
    # prefixes handled (used for error messages etc)
    def name
      @names.join("|")
    end
    
    # return a help string for your module. for complex modules, you may wish
    # to break your help into topics, and return a list of available topics if
    # +topic+ is nil. +plugin+ is passed containing the matching prefix for
    # this message - if your plugin handles multiple prefixes, make sure your
    # return the correct help for the prefix requested
    def help(plugin, topic)
      "no help"
    end
    
    # register the plugin as a handler for messages prefixed +name+
    # this can be called multiple times for a plugin to handle multiple
    # message prefixes
    def register(name)
      Plugins.plugins[name] = self
      @names << name
    end

    # is this plugin listening to all messages?
    def listen?
      @listen
    end
    
  end

  # class to manage multiple plugins and delegate messages to them for
  # handling
  class Plugins
    # hash of registered message prefixes and associated plugins
    @@plugins = Hash.new
    # associated IrcBot class
    @@bot = nil

    # bot::     associated IrcBot class
    # dirlist:: array of directories to scan (in order) for plugins
    #
    # create a new plugin handler, scanning for plugins in +dirlist+
    def initialize(bot, dirlist)
      @@bot = bot
      @dirs = dirlist
      scan
    end
    
    # access to associated bot
    def Plugins.bot
      @@bot
    end

    # access to list of plugins
    def Plugins.plugins
      @@plugins
    end

    # load plugins from pre-assigned list of directories
    def scan
      dirs = Array.new
      dirs << File.dirname(__FILE__) + "/plugins"
      dirs += @dirs
      dirs.each {|dir|
        if(FileTest.directory?(dir))
          d = Dir.new(dir)
          d.each {|file|
            next if(file =~ /^\./)
            next unless(file =~ /\.rb$/)
            @tmpfilename = "#{dir}/#{file}"

            # create a new, anonymous module to "house" the plugin
            plugin_module = Module.new
            plugin_string = IO.readlines(@tmpfilename).join("")
            
            begin
              plugin_module.module_eval(plugin_string)
            rescue StandardError, LoadError, SyntaxError => err
              puts "plugin #{@tmpfilename} load failed: " + err
            end
          }
        end
      }
    end

    # call the save method for each active plugin
    def save
      @@plugins.values.uniq.each {|p|
        next unless(p.respond_to?("save"))
        begin
          p.save
        rescue StandardError, NameError, SyntaxError => err
          puts "plugin #{p.name} save() failed: " + err
        end
      }
    end

    # call the cleanup method for each active plugin
    def cleanup
      @@plugins.values.uniq.each {|p|
        next unless(p.respond_to?("cleanup"))
        begin
          p.cleanup
        rescue StandardError, NameError, SyntaxError => err
          puts "plugin #{p.name} cleanup() failed: " + err
        end
      }
    end

    # drop all plugins and rescan plugins on disk
    # calls save and cleanup for each plugin before dropping them
    def rescan
      save
      cleanup
      @@plugins = Hash.new
      scan
    end

    # return list of help topics (plugin names)
    def helptopics
      if(@@plugins.length > 0)
        return " [plugins: " + @@plugins.keys.sort.join(", ") + "]"
      else
        return " [no plugins active]" 
      end
    end

    # return help for +topic+ (call associated plugin's help method)
    def help(topic="")
      if(topic =~ /^(\S+)\s*(.*)$/)
        key = $1
        params = $2
        if(@@plugins.has_key?(key))
          begin
            return @@plugins[key].help(key, params)
          rescue StandardError, NameError, SyntaxError => err
            puts "plugin #{@@plugins[key].name} help() failed: " + err
          end
        else
          return false
        end
      end
    end
    
    # see if each plugin handles +method+, and if so, call it, passing
    # +message+ as a parameter
    def delegate(method, message)
      @@plugins.values.uniq.each {|p|
        if(p.respond_to? method)
          begin
            p.send method, message
          rescue StandardError, NameError, SyntaxError => err
            puts "plugin #{p.name} #{method}() failed: " + err
          end
        end
      }
    end

    # see if we have a plugin that wants to handle this message, if so, pass
    # it to the plugin and return true, otherwise false
    def privmsg(m)
      return unless(m.plugin)
      if (@@plugins.has_key?(m.plugin) &&
          @@plugins[m.plugin].respond_to?("privmsg") &&
          @@bot.auth.allow?(m.plugin, m.source, m.replyto))
        begin
          @@plugins[m.plugin].privmsg(m)
        rescue StandardError, NameError, SyntaxError => err
          puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err
        end
        return true
      end
      return false
    end
  end

end
