module VimGolf

  GOLFDEBUG    = ENV['GOLFDEBUG'].to_sym rescue false
  GOLFHOST     = ENV['GOLFHOST']     || "https://www.vimgolf.com"
  GOLFSHOWDIFF = ENV['GOLFSHOWDIFF'] || 'vim -d -n'
  GOLFVIM      = ENV['GOLFVIM']      || 'vim'

  class Error
  end

  class RetryException < Exception; end

  class UI
    def debug(*); end
  end

  class << self
    attr_accessor :ui

    def ui
      @ui ||= UI.new
    end
  end

  class CLI < Thor
    include Thor::Actions

    def self.initialize_ui
      return if @ui_initialized
      Thor::Base.shell = VimGolf::CLI::UI
      VimGolf.ui = VimGolf::CLI::UI.new
      @ui_initialized = true
    end

    def self.reset_ui
      return unless @ui_initialized
      Thor::Base.shell = Thor::Shell::Color
      VimGolf.ui = nil
      @ui_initialized = false
    end

    def self.start(*)
      initialize_ui
      super
    end

    desc "version", "print version of Vimgolf client"
    long_desc <<-DESC
    Print version of the Vimgolf client.
    DESC

    def version
      VimGolf.ui.info "Client #{Vimgolf::VERSION}"
    end

    desc "setup", "configure your VimGolf credentials"
    long_desc <<-DESC
    To participate in the challenge please go to vimgolf.com and register an
    account. Once signed in, you will get your API key, which you need to
    setup the command client.

      If you have the key, simply run the setup and paste in your key.
      DESC

    def setup
      VimGolf.ui.info "\nLet's setup your VimGolf key..."
      VimGolf.ui.warn "1) Open vimgolf.com in your browser."
      VimGolf.ui.warn "2) Click \"Sign in\"."
      VimGolf.ui.warn "3) Click \"Sign in with Twitter\" or \"Sign in with Github\"."
      VimGolf.ui.warn "4) Once signed in, copy your key (black box, top right)."

      key = VimGolf.ui.ask "\nPaste your VimGolf key:"

      if key =~ /[\w\d]{32}/
        FileUtils.mkdir_p Config.path
        FileUtils.mkdir_p Config.put_path
        Config.save({'key' => key})

        VimGolf.ui.info "Saved. Happy golfing!"
      else
        VimGolf.ui.error "Invalid key, please double check your key on vimgolf.com"
      end
    end

    desc "put CHALLENGE_ID", "launch vimgolf.com challenge"
    long_desc <<-DESC
    Launch a VimGolf session for the specified challenge ID. To find a currently
    active challenge ID, please visit vimgolf.com!
    DESC

    def put(id)
      FileUtils.mkdir_p Config.put_path
      VimGolf.ui.warn "Downloading Vimgolf challenge: #{id}"
      VimGolf::Challenge.path(Config.put_path)
      challenge = Challenge.new(id)
      challenge.download

      play(challenge)
    end

    desc "local INFILE OUTFILE", "launch local challenge"
    long_desc <<-DESC
    Launch a local VimGolf challenge. A temporary copy of INFILE is made; the original files will not be touched.
    DESC

    def local(infile, outfile)
      # make sure our files are sane
      if !(File.file?(infile) and File.file?(outfile))
        VimGolf.ui.error "INFILE and OUTFILE must exist and be regular files."
        exit 1
      end

      challenge = Challenge.new(infile) # use the filename as id
      challenge.local(infile, outfile)

      play(challenge)
    end

    private

    def play(challenge)
      begin
        challenge.start
        VimGolf.ui.warn "Launching VimGolf session for challenge: #{challenge.id}"
        # -Z         - restricted mode, utilities not allowed
        # -n         - no swap file, memory only editing
        # --noplugin - don't load any plugins, lets be fair!
        # --nofork   - otherwise GOLFVIM=gvim forks and returns immediately
        # -i NONE    - don't load .viminfo (for saved macros and the like)
        # +0         - always start on line 0
        # -u vimrc   - load vimgolf .vimrc to level the playing field
        # -U NONE    - don't load .gvimrc
        # -W logfile - keylog file (overwrites if already exists)
        vimcmd = GOLFVIM.shellsplit + %W{-Z -n --noplugin -i NONE +0 -u #{challenge.vimrc_path} -U NONE -W #{challenge.log_path} #{challenge.work_path}}
        if GOLFVIM == "gvim"
          vimcmd += %W{ --nofork}
        end
        debug(vimcmd)
        system(*vimcmd) # assembled as an array, bypasses the shell

        if $?.exitstatus.zero?
          log = Keylog.new(IO.binread(challenge.log_path))

          VimGolf.ui.info "\nHere are your keystrokes:"
          VimGolf.ui.print_log log

          # Handle incorrect solutions
          if !challenge.correct?()
            VimGolf.ui.error "\nUh oh, looks like your entry does not match the desired output."
            VimGolf.ui.error "Your score for this failed attempt was: #{log.score}"
            loop do
              VimGolf.ui.warn "[d] Show diff"
              VimGolf.ui.warn "[r] Retry the current challenge"
              VimGolf.ui.warn "[q] Quit vimgolf"

              case VimGolf.ui.ask_question "Choice> ",
                                  :type      => :warn,
                                  :choices   => [:diff, :retry, :quit]
              when :diff
                VimGolf.ui.warn "Showing vimdiff of your attempt (left) and correct output (right)"
                system(*GOLFSHOWDIFF.shellsplit + [challenge.work_path, challenge.output_path])
              when :retry
                VimGolf.ui.warn "Retrying current challenge..."
                raise RetryException
              when :quit
                raise Interrupt
              end
            end
          end

          # Handle correct solutions
          VimGolf.ui.info "\nSuccess! Your output matches. Your score: #{log.score}"

          loop do
            choices = []
            begin
              Config.load # raises error if user hasn't finished setup
              choices = [:w, :x]
              VimGolf.ui.warn "[w] Upload result and retry the challenge"
              VimGolf.ui.warn "[x] Upload result and quit"
            rescue
              choices = [:setup]
              VimGolf.ui.warn "[s] Set up vimgolf.com key to submit result"
            end if challenge.remote
            VimGolf.ui.warn "[r] Do not upload result and retry the challenge"
            VimGolf.ui.warn "[q] Do not upload result and quit"

            case VimGolf.ui.ask_question "Choice> ",
                                :type    => :warn,
                                :choices => choices + [:retry, :quit]
            when :w
              next unless upload?(challenge)
              raise RetryException
            when :x
              next unless upload?(challenge)
              raise Interrupt
            when :setup
              setup
              next # we can hopefully submit this time
            when :retry
              raise RetryException
            when :quit
              raise Interrupt
            end
          end

        else
          error = <<-MSG
	  Uh oh, Vim did not exit properly.
	  Please ensure you can execute 'Vim' from the commandline.
	  If the problem persists, please report the error on github.com/igrigorik/vimgolf
          MSG

          VimGolf.ui.error error
        end

      rescue RetryException => e
        retry
      end

    rescue Interrupt
      VimGolf.ui.info "\nThanks for playing!"
    rescue RuntimeError, Exception => e
      VimGolf.ui.error "Uh oh, something went wrong! Error: #{e}"
      VimGolf.ui.error "If the error persists, please report it to github.com/igrigorik/vimgolf"
    end

    def upload?(challenge)
      VimGolf.ui.warn "Uploading to VimGolf..."

      if challenge.upload == :ok
        VimGolf.ui.warn "Uploaded entry."
        VimGolf.ui.warn "View the leaderboard: #{GOLFHOST}/challenges/#{challenge.id}"
      else
        VimGolf.ui.error "Uh oh, upload failed. You're not cheating are you? :-)"
        nil
      end
    end

    def debug(msg)
      p [caller.first, msg] if GOLFDEBUG
    end
  end

end
