require File.join(File.dirname(__FILE__), '..', 'chatterbot')
require 'optparse'

module Chatterbot
  #
  # very basic DSL to handle the common stuff you would want to do with a bot.
  module DSL
    #
    # @return initialized Twitter::REST::Client
    def client
      bot.client
    end
    
    #
    # search twitter for the specified terms, then pass any matches to
    # the block.
    # NOTE: by default, search terms are wrapped in quotes so the
    # Twitter API will return tweets that include your exact query.
    # You can disable this by passing exact:false as an option
    #
    # @param args [Hash] options. these will be passed directly to
    # Twitter via the twitter gem. You can see the possible arguments
    # at http://www.rubydoc.info/gems/twitter/Twitter/REST/Search#search-instance_method
    #
    # @example
    #   search("chatterbot is cool!") do |tweet|
    #     puts tweet.text # this is the actual tweeted text
    #     reply "I agree!", tweet
    #   end
    def search(*args, &block)
      bot.register_handler(:search, args, &block)
    end

    #
    # handle tweets that are on the bot's home timeline. this includes
    # tweets from accounts the bot is following, as well as its own tweets
    #
    # @example
    #   home_timeline do |tweet|
    #     puts tweet.text # this is the actual tweeted text
    #     favorite tweet # i like to fave tweets
    #   end
    def home_timeline(&block)
      bot.register_handler(:home_timeline, block)
    end

    #
    # handle replies to the bot. Each time this is called, chatterbot
    # will pass any replies since the last call to the specified block
    #
    # @example
    #   replies do |tweet|
    #     puts tweet.text # this is the actual tweeted text
    #     reply "Thanks for the mention!", tweet
    #   end
    def replies(&block)
      bot.register_handler(:replies, block)
    end

    #
    # handle direct messages sent to the bot. Each time this is called, chatterbot
    # will pass any DMs since the last call to the specified block
    #
    # @example
    #   direct_messages do |dm|
    #     puts dm.text # this is the actual tweeted text
    #     direct_message "Thanks for the mention!", dm.sender
    #   end
    def direct_messages(&block)
      bot.register_handler(:direct_messages, block)
    end
    
    #
    # send a tweet
    #
    # @param [String] txt the text you want to tweet
    # @param [Hash] params options for the tweet. You can get an idea
    #   of possible values you can send here from the underlying Twitter
    #   gem docs: http://rdoc.info/gems/twitter/Twitter/API#update-instance_method
    # @option params [String,File] :media Optional file object to send
    #   with the tweet. Must be an image or video that will be accepted by
    #   Twitter. You can pass a File object, or the path to a file
    # @see http://rdoc.info/gems/twitter/Twitter/API#update-instance_method
    # @param [Tweet] original if this is a reply, the original tweet. this will
    #   be used for variable substitution, and for logging
    def tweet(txt, params = {}, original = nil)
      bot.tweet(txt, params, original)
    end

    #
    # retweet a tweet
    # @param [id] id A tweet or the ID of a tweet
    def retweet(id)
      bot.retweet(id)
    end


    #
    # favorite a tweet
    # @param [id] id A tweet or the ID of a tweet
    def favorite(id)
      bot.favorite(id)
    end
    
    #
    # reply to a tweet
    #
    # @param [String] txt the text you want to tweet
    # @param [Tweet] source the original tweet you are replying to
    # @param [Hash] params options for the tweet. You can get an idea
    #   of possible values you can send here from the underlying Twitter
    #   gem docs: http://rdoc.info/gems/twitter/Twitter/API#update-instance_method
    # @option params [String,File] :media Optional file object to send with the
    #   tweet. Must be an image or video that will be accepted by
    #   Twitter. You can pass a File object, or the path to a file
    # @see http://rdoc.info/gems/twitter/Twitter/API#update-instance_method
    def reply(txt, source, params={})
      bot.reply(txt, source, params)
    end

    #
    # send a direct message to the specified user
    # 
    # @param [String] txt the text you want to tweet
    # @param [User] user to send the DM to
    def direct_message(txt, user=nil)
      bot.direct_message(txt, user)
    end

    
    #
    # handle getting/setting the profile text.
    # @param [p] p The new value for the profile. If this isn't passed in, the method will simply return the current value
    # @return profile text
    def profile_text(p=nil)
      if p.nil?
        bot.profile_text
      else
        bot.profile_text(p)
      end
    end

    #
    # handle getting/setting the profile website
    # @param [w] w The new value for the website. If this isn't passed in, the method will simply return the current value
    # @return profile website
    def profile_website(w=nil)
      if w.nil?
        bot.profile_website
      else
        bot.profile_website(w)
      end
    end
    
    
    #
    # generate a Bot object. if the DSL is being called from a Bot object, just return it
    # otherwise create a bot and return that
    def bot
      return @bot unless @bot.nil?

      @bot_command = nil
      
      #
      # parse any command-line options and use them to initialize the bot
      #
      params = {}

      #:nocov:
      opts = OptionParser.new

      opts.banner = "Usage: #{File.basename($0)} [options]"

      opts.separator ""
      opts.separator "Specific options:"


      opts.on('-c', '--config [ARG]', "Specify a config file to use")    { |c| ENV["chatterbot_config"] = c }
      opts.on('-t', '--test', "Run the bot without actually sending any tweets") { params[:debug_mode] = true }
      opts.on('-v', '--verbose', "verbose output to stdout")    { params[:verbose] = true }
      opts.on('--dry-run', "Run the bot in test mode, and also don't update the database")    { params[:debug_mode] = true ; params[:no_update] = true }

      opts.on('-r', '--reset', "Reset your bot to ignore old tweets") {
        @bot_command = :reset_since_id_counters
      }

      opts.on('--profile [ARG]', "get/set your bot's profile text") { |p| 
        @bot_command = :profile_text
        @bot_command_args = [ p ]
      }

      opts.on('--website [ARG]', "get/set your bot's profile URL") { |u| 
        @bot_command = :profile_website
        @bot_command_args = [ u ]
      }
      
      opts.on_tail("-h", "--help", "Show this message") do
        puts opts
        exit
      end

      opts.parse!(ARGV)
      #:nocov:

      @bot = Chatterbot::Bot.new(params)
      if @bot_command != nil
        @bot.skip_run = true
        result = @bot.send(@bot_command, *@bot_command_args)
        puts result
      end

      @bot
    end

    #
    # should we send tweets?
    # @param [Boolean] d true/false if we should send tweets
    #
    def debug_mode(d=nil)
      d = true if d.nil?
      bot.debug_mode = d
    end

    #
    # should we update the db with a new since_id?
    # @param [Boolean] d true/false if we should update the database
    #
    def no_update(d=nil)
      d = true if d.nil?
      bot.no_update = d
    end

    #
    # turn on/off verbose output
    # @param [Boolean] d true/false use verbose output
    #
    def verbose(d=nil)
      d = true if d.nil?
      bot.verbose = d
    end

    #
    # specify a bot-specific blocklist of users.  accepts an array, or a
    # comma-delimited string. when called, any subsequent calls to
    # search or replies will filter out these users.
    #
    # @param [Array, String] args list of usernames
    # @example
    #   blocklist "mean_user, private_user"
    #
    def blocklist(*args)
      list = flatten_list_of_strings(args)

      if list.nil? || list.empty?
        bot.blocklist = []
      else
        bot.blocklist += list
      end
    end
    alias :blacklist :blocklist

    
    #
    # specify a bot-specific safelist of users.  accepts an array, or a
    # comma-delimited string. when called, any subsequent calls to
    # search or replies will only act upon these users.
    #
    # @param [Array, String] args list of usernames or Twitter::User objects
    # @example
    #   safelist "mean_user, private_user"
    #
    def safelist(*args)
      list = flatten_list_of_strings(args)

      if list.nil? || list.empty?
        bot.safelist = []
      else
        bot.safelist += list
      end
    end
    alias :whitelist :safelist
    
    #
    # specify that the bot should only reply to tweets from users that
    # are followers, basically making interactions opt-in
    #
    def only_interact_with_followers
      bot.config[:only_interact_with_followers] = true
    end
    
    #
    # return a list of users following the bot. This passes directly
    # to the underlying Twitter API call
    # @see http://rdoc.info/gems/twitter/Twitter/API/FriendsAndFollowers#followers-instance_method
    #
    def followers(opts={})
      bot.followers(opts)
    end

    #
    # follow a user
    #
    # @param u a Twitter::User or user id
    def follow(u)
      bot.follow(u)
    end

    
    #
    # a common list of bad words, which you might want to filter out.
    # lifted from https://github.com/dariusk/wordfilter/blob/master/lib/badwords.json
    #
    def bad_words
      [
        "biatch",
        "bitch",
        "chinaman",
        "chinamen",
        "chink",
        "crip",
        "cunt",
        "dago",
        "daygo",
        "dego",
        "dick",
        "douchebag",
        "dyke",
        "fag",
        "fatass",
        "fatso",
        "gash",
        "gimp",
        "golliwog",
        "gook",
        "gyp",
        "homo",
        "hooker",
        "jap",
        "kike",
        "kraut",
        "lardass",
        "lesbo",
        "negro",
        "nigger",
        "paki",
        "pussy",
        "raghead",
        "retard",
        "shemale",
        "skank",
        "slut",
        "spic",
        "tard",
        "tits",
        "titt",
        "trannies",
        "tranny",
        "twat",
        "wetback",
        "whore",
        "wop"
      ]
    end
    
    #
    # specify list of strings we will check when deciding to respond
    # to a tweet or not. accepts an array or a comma-delimited string.
    # when called, any subsequent calls to search or replies will
    # filter out tweets with these strings
    #
    # @param [Array, String] args list of usernames
    # @example
    #   exclude "spam, junk, something"
    def exclude(*args)
      e = flatten_list_of_strings(args)
      if e.nil? || e.empty?
        bot.exclude = []
      else
        bot.exclude += e
      end
    end

    
    #
    # The ID of the most recent tweet processed by the bot
    #
    def since_id(s=nil)
      if s
        bot.config[:since_id] = s
      end
      bot.config[:since_id]
    end

    #
    # set the consumer secret
    # @param s [String] the consumer secret
    def consumer_secret(s)
      bot.deprecated "Setting consumer_secret outside of your config file is deprecated!", Kernel.caller.first
      bot.config[:consumer_secret] = s
    end
    
    #
    # set the consumer key
    # @param k [String] the consumer key
    def consumer_key(k)
      bot.deprecated "Setting consumer_key outside of your config file is deprecated!",  Kernel.caller.first
      bot.config[:consumer_key] = k
    end

    #
    # set the secret
    # @param s [String] the secret
    def secret(s)
      bot.deprecated "Setting access_token_secret outside of your config file is deprecated!", Kernel.caller.first
      bot.config[:access_token_secret] = s
    end

    #
    # set the token
    # @param s [String] the token
    def token(s)
      bot.deprecated "Setting access_token outside of your config file is deprecated!", Kernel.caller.first
      bot.config[:access_token] = s
    end

    #
    # get the id of the last tweet the bot replied to
    # @return tweet id
    def since_id_reply
      bot.config[:since_id_reply]
    end

    
    #
    # explicitly save the configuration/state of the bot.
    #
    def update_config
      bot.update_config
    end

    protected
    
    #
    # take a variable list of strings and possibly arrays and turn
    # them into a single flat array of strings
    #
    def flatten_list_of_strings(args)
      args.collect do |b|
        if b.is_a?(String)
          # string, split on commas and turn into array
          b.split(",").collect { |s| s.strip }
        else
          # presumably an array
          b
        end
      end.flatten
    end
    
  end
end

include Chatterbot::DSL
include Chatterbot::Helpers
