#!/usr/bin/env ruby
# encoding: utf-8

require 'twitter_ebooks'
require 'ostruct'
require 'fileutils'

module Ebooks::Util
  def pretty_exception(e)

  end
end

module Ebooks::CLI
  APP_PATH = Dir.pwd # XXX do some recursive thing instead
  HELP = OpenStruct.new

  HELP.default = <<STR
Usage:
     ebooks help <command>

     ebooks new <reponame>
     ebooks s[tart]
     ebooks c[onsole]
     ebooks auth
     ebooks consume <corpus_path> [corpus_path2] [...]
     ebooks consume-all <model_name> <corpus_path> [corpus_path2] [...]
     ebooks append <model_name> <corpus_path>
     ebooks gen <model_path> [input]
     ebooks archive <username> [path]
     ebooks tweet <model_path> <botname>
     ebooks version
STR

  def self.help(command=nil)
    if command.nil?
      log HELP.default
    else
      log HELP[command].gsub(/^ {4}/, '')
    end
  end

  HELP.new = <<-STR
    Usage: ebooks new <reponame>

    Creates a new skeleton repository defining a template bot in
    the current working directory specified by <reponame>.
  STR

  def self.new(reponame)
    if reponame.nil?
      help :new
      exit 1
    end

    path = "./#{reponame}"

    if File.exists?(path)
      log "#{path} already exists. Please remove if you want to recreate."
      exit 1
    end

    FileUtils.cp_r(Ebooks::SKELETON_PATH, path)
    FileUtils.mv(File.join(path, 'gitignore'), File.join(path, '.gitignore'))

    File.open(File.join(path, 'bots.rb'), 'w') do |f|
      template = File.read(File.join(Ebooks::SKELETON_PATH, 'bots.rb'))
      f.write(template.gsub("{{BOT_NAME}}", reponame))
    end

    File.open(File.join(path, 'Gemfile'), 'w') do |f|
      template = File.read(File.join(Ebooks::SKELETON_PATH, 'Gemfile'))
      f.write(template.gsub("{{RUBY_VERSION}}", RUBY_VERSION))
    end

    log "New twitter_ebooks app created at #{reponame}"
  end

  HELP.consume = <<-STR
    Usage: ebooks consume <corpus_path> [corpus_path2] [...]

    Processes some number of text files or json tweet corpuses
    into usable models. These will be output at model/<corpus_name>.model
  STR

  def self.consume(pathes)
    if pathes.empty?
      help :consume
      exit 1
    end

    pathes.each do |path|
      filename = File.basename(path)
      shortname = filename.split('.')[0..-2].join('.')

      outpath = File.join(APP_PATH, 'model', "#{shortname}.model")
      Ebooks::Model.consume(path).save(outpath)
      log "Corpus consumed to #{outpath}"
    end
  end

  HELP.consume_all = <<-STR
    Usage: ebooks consume-all <model_name> <corpus_path> [corpus_path2] [...]

    Processes some number of text files or json tweet corpuses
    into one usable model. It will be output at model/<model_name>.model
  STR

  def self.consume_all(name, paths)
    if paths.empty?
      help :consume_all
      exit 1
    end

    outpath = File.join(APP_PATH, 'model', "#{name}.model")
    Ebooks::Model.consume_all(paths).save(outpath)
    log "Corpuses consumed to #{outpath}"
  end

  HELP.append = <<-STR
    Usage: ebooks append <model_name> <corpus_path>

    Process then append the provided corpus to the model 
    instead of overwriting.
  STR

  def self.append(name, path)
    if !name || !path
      help :append
      exit 1
    end

    Ebooks::Model.consume(path).append(File.join(APP_PATH,'model',"#{name}.model"))
    log "Corpus appended to #{name}.model"
  end
 

  HELP.jsonify = <<-STR
    Usage: ebooks jsonify <tweets.csv> [tweets.csv2] [...]

    Takes a csv twitter archive and converts it to json.
  STR

  def self.jsonify(paths)
    if paths.empty?
      log usage
      exit
    end

    paths.each do |path|
      name = File.basename(path).split('.')[0]
      new_path = name + ".json"

      tweets = []
      id = nil
      if path.split('.')[-1] == "csv" #from twitter archive
        csv_archive = CSV.read(path, :headers=>:first_row)
        tweets = csv_archive.map do |tweet|
          { text: tweet['text'], id: tweet['tweet_id'] }
        end
      else
        File.read(path).split("\n").each do |l|
          if l.start_with?('# ')
            id = l.split('# ')[-1]
          else
            tweet = { text: l }
            if id
              tweet[:id] = id
              id = nil
            end
            tweets << tweet
          end
        end
      end

      File.open(new_path, 'w') do |f|
        log "Writing #{tweets.length} tweets to #{new_path}"
        f.write(JSON.pretty_generate(tweets))
      end
    end
  end


  HELP.gen = <<-STR
    Usage: ebooks gen <model_path> [input]

    Make a test tweet from the processed model at <model_path>.
    Will respond to input if provided.
  STR

  def self.gen(model_path, input)
    if model_path.nil?
      help :gen
      exit 1
    end

    model = Ebooks::Model.load(model_path)
    if input && !input.empty?
      puts "@cmd " + model.make_response(input, 135)
    else
      puts model.make_statement
    end
  end

  HELP.archive = <<-STR
    Usage: ebooks archive <username> [outpath]

    Downloads a json corpus of the <username>'s tweets.
    Output defaults to corpus/<username>.json
    Due to API limitations, this can only receive up to ~3000 tweets
    into the past.
  STR

  def self.archive(username, outpath=nil)
    if username.nil?
      help :archive
      exit 1
    end

    Ebooks::Archive.new(username, outpath).sync
  end

  HELP.tweet = <<-STR
    Usage: ebooks tweet <model_path> <botname>

    Sends a public tweet from the specified bot using text
    from the processed model at <model_path>.
  STR

  def self.tweet(modelpath, botname)
    if modelpath.nil? || botname.nil?
      help :tweet
      exit 1
    end

    load File.join(APP_PATH, 'bots.rb')
    model = Ebooks::Model.load(modelpath)
    statement = model.make_statement
    bot = Ebooks::Bot.get(botname)
    bot.configure
    bot.tweet(statement)
  end

  HELP.auth = <<-STR
    Usage: ebooks auth

    Authenticates your Twitter app for any account. By default, will
    use the consumer key and secret from the first defined bot. You
    can specify another by setting the CONSUMER_KEY and CONSUMER_SECRET
    environment variables.
  STR

  def self.auth
    consumer_key, consumer_secret = find_consumer
    require 'oauth'

    consumer = OAuth::Consumer.new(
      consumer_key,
      consumer_secret,
      site: 'https://twitter.com/',
      scheme: :header
    )

    request_token = consumer.get_request_token
    auth_url = request_token.authorize_url()

    pin = nil
    loop do
      log auth_url

      log "Go to the above url and follow the prompts, then enter the PIN code here."
      print "> "

      pin = STDIN.gets.chomp

      break unless pin.empty?
    end

    access_token = request_token.get_access_token(oauth_verifier: pin)

    log "Account authorized successfully. Make sure to put these in your bots.rb!\n" +
         "  access token: #{access_token.token}\n" +
         "  access token secret: #{access_token.secret}"
  end

  HELP.console = <<-STR
    Usage: ebooks c[onsole]

    Starts an interactive ruby session with your bots loaded
    and configured.
  STR

  def self.console
    load_bots
    require 'pry'; Ebooks.module_exec { pry }
  end

  HELP.version = <<-STR
    Usage: ebooks version

    Shows you twitter_ebooks' version number.
  STR
  
  def self.version
    require File.expand_path('../../lib/twitter_ebooks/version', __FILE__)
    log Ebooks::VERSION
  end

  HELP.start = <<-STR
    Usage: ebooks s[tart] [botname]

    Starts running bots. If botname is provided, only runs that bot.
  STR

  def self.start(botname=nil)
    load_bots

    if botname.nil?
      bots = Ebooks::Bot.all
    else
      bots = Ebooks::Bot.all.select { |bot| bot.username == botname }
      if bots.empty?
        log "Couldn't find a defined bot for @#{botname}!"
        exit 1
      end
    end

    threads = []
    bots.each do |bot|
      threads << Thread.new { bot.prepare }
    end
    threads.each(&:join)

    threads = []
    bots.each do |bot|
      threads << Thread.new do
        loop do
          begin
            bot.start
          rescue Exception => e
            bot.log e.inspect
            puts e.backtrace.map { |s| "\t"+s }.join("\n")
          end
          bot.log "Sleeping before reconnect"
          sleep 60
        end
      end
    end
    threads.each(&:join)
  end

  # Non-command methods

  def self.find_consumer
    if ENV['CONSUMER_KEY'] && ENV['CONSUMER_SECRET']
      log "Using consumer details from environment variables:\n" +
          "  consumer key: #{ENV['CONSUMER_KEY']}\n" +
          "  consumer secret: #{ENV['CONSUMER_SECRET']}"
      return [ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']]
    end

    load_bots
    consumer_key = nil
    consumer_secret = nil
    Ebooks::Bot.all.each do |bot|
      if bot.consumer_key && bot.consumer_secret
        consumer_key = bot.consumer_key
        consumer_secret = bot.consumer_secret
        log "Using consumer details from @#{bot.username}:\n" +
            "  consumer key: #{bot.consumer_key}\n" +
            "  consumer secret: #{bot.consumer_secret}\n"
        return consumer_key, consumer_secret
      end
    end

    if consumer_key.nil? || consumer_secret.nil?
      log "Couldn't find any consumer details to auth an account with.\n" +
          "Please either configure a bot with consumer_key and consumer_secret\n" +
          "or provide the CONSUMER_KEY and CONSUMER_SECRET environment variables."
      exit 1
    end
  end

  def self.load_bots
    load 'bots.rb'

    if Ebooks::Bot.all.empty?
      puts "Couldn't find any bots! Please make sure bots.rb instantiates at least one bot."
    end
  end

  def self.command(args)
    if args.length == 0
      help
      exit 1
    end

    case args[0]
    when "new" then new(args[1])
    when "consume" then consume(args[1..-1])
    when "consume-all" then consume_all(args[1], args[2..-1])
    when "append" then append(args[1],args[2])
    when "gen" then gen(args[1], args[2..-1].join(' '))
    when "archive" then archive(args[1], args[2])
    when "tweet" then tweet(args[1], args[2])
    when "jsonify" then jsonify(args[1..-1])
    when "auth" then auth
    when "console" then console
    when "c" then console
    when "start" then start(args[1])
    when "s" then start(args[1])
    when "help" then help(args[1])
    when "version" then version
    else
      log "No such command '#{args[0]}'"
      help
      exit 1
    end
  end
end

Ebooks::CLI.command(ARGV)