require "thor"

module Softcover
  class CLI < Thor
    include Softcover::Utils

    map "-v" => :version

    desc "version", "Return the version number (-v for short)"
    method_option :version, aliases: '-v',
                            desc: "Print version number", type: :boolean
    def version
      require 'softcover/version'
      puts "Softcover #{Softcover::VERSION}"
      exit 0
    end

    # ===============================================
    # Builder
    # ===============================================

    desc 'build, build:all', 'Build all formats'
    method_option :quiet, aliases: '-q',
                          desc: "Quiet output", type: :boolean
    method_option :silent, aliases: '-s',
                           desc: "Silent output", type: :boolean
    def build
      Softcover::Commands::Build.all_formats(options)
    end
    map "build:all" => "build"

    Softcover::FORMATS.each do |format|
      desc "build:#{format}", "Build #{format.upcase}"
      if format == 'pdf'
        method_option :debug, aliases: '-d',
                              desc: "Run raw xelatex for debugging",
                              type: :boolean
        method_option :once, aliases: '-o',
                             desc: "Run generator once",
                             type: :boolean
        method_option :'find-overfull', aliases: '-f',
                                        desc: "Find overfull hboxes",
                                        type: :boolean
        method_option :nonstop, aliases: '-n',
                                desc: "Run generator in nonstopmode",
                                type: :boolean
      elsif format == 'mobi'
        method_option :amazon, aliases: '-a',
                               desc: "Build for Amazon KDP", type: :boolean
      end
      method_option :quiet, aliases: '-q',
                            desc: "Quiet output", type: :boolean
      method_option :silent, aliases: '-s',
                             desc: "Silent output", type: :boolean
      define_method "build:#{format}" do
        Softcover::Commands::Build.for_format format, options
      end
    end

    # ===============================================
    # Preview
    # ===============================================
    desc "build:preview", "Build book preview in all formats"
    method_option :quiet, aliases: '-q',
                          desc: "Quiet output", type: :boolean
    method_option :silent, aliases: '-s',
                           desc: "Silent output", type: :boolean
    define_method "build:preview" do
      Softcover::Commands::Build.preview(options)
    end

    # ===============================================
    # Clean
    # ===============================================
    desc "clean", "Clean unneeded files"
    def clean
      rm(Dir.glob('*.aux'))
      rm(Dir.glob(File.join('chapters', '*.aux')))
      rm(Dir.glob('*.toc'))
      rm(Dir.glob('*.out'))
      rm(Dir.glob('*.tmp.*'))
      rm(Dir.glob(path('tmp/*.*')))
      rm('.highlight_cache')
      rm('tmp/.highlight_cache')
    end

    # ===============================================
    # Check
    # ===============================================
    desc "check", "Check dependencies"
    def check
      Softcover::Commands::Check.check_dependencies!
    end

    # ===============================================
    # Server
    # ===============================================

    desc 'server', 'Run local server'
    method_option :port, type: :numeric, default: 4000, aliases: '-p'
    method_option :bind, type: :string, default: '0.0.0.0', aliases: '-o'
    method_option :pdf, type: :boolean, default: false
    method_option :overfull, type: :boolean, default: false
    def server
      if Softcover::BookManifest::valid_directory?
        Softcover::Commands::Server.run options[:port], options[:bind],
                                        options[:pdf], options[:overfull]
      else
        puts 'Not in a valid book directory.'
        exit 1
      end
    end

    # ===============================================
    # Auth
    # ===============================================

    desc "login", "Log into Softcover account"
    def login
      puts "Logging in."

      logged_in = false
      while not logged_in do
        email = ask "Email:"
        password = ask_without_echo "Password (won't be shown):"
        unless logged_in = Softcover::Commands::Auth.login(email, password)
          puts "Invalid login, please try again."
        end
      end
      puts "Welcome back, #{email}!"
    end

    desc "logout", "Log out of Softcover account"
    def logout
      Softcover::Commands::Auth.logout
    end

    # ===============================================
    # Publisher
    # ===============================================

    desc "publish", "Publish your book on Softcover"
    method_option :quiet, aliases: '-q',
                          desc: "Quiet output", type: :boolean
    method_option :silent, aliases: '-s',
                           desc: "Silent output", type: :boolean
    method_option :remove_unused_media_bundles, aliases: '-r',
                           desc: "Remove unused media bundles", type: :boolean
    def publish
      require 'softcover/commands/publisher'

      invoke :login unless logged_in?

      puts "Publishing..." unless options[:silent]
      Softcover::Commands::Publisher.publish!(options)
    end

    desc "publish:media", "Publish media"
    method_option :daemon, aliases: '-d', force: false,
      desc: "Run as daemon", type: :boolean
    method_option :watch, aliases: '-w', type: :boolean,
      force: false, desc: "Watch a directory to auto upload."
    method_option :remove_unused_media_files, aliases: '-r',
                           desc: "Remove unused media files", type: :boolean

    # TODO: make screencasts dir .book configurable
    define_method "publish:media" do |dir=
      Softcover::Book::DEFAULT_MEDIA_DIR|
      require 'softcover/commands/publisher'

      puts "Publishing media bundles..."
      Softcover::Commands::Publisher.
        publish_media! options.merge(dir: dir)
    end

    desc "unpublish", "Remove book from Softcover"
    method_option :slug, aliases: '-s',
                          desc: "Specify slug", type: :string

    method_option :force, aliases: '-f',
                          desc: "Force", type: :boolean
    def unpublish
      require 'softcover/commands/publisher'

      invoke :login unless logged_in?
      slug = options[:slug] || unpublish_slug
      if options[:force] || ask("Type '#{slug}' to unpublish:") == slug
        puts "Unpublishing..." unless options[:silent]
        Softcover::Commands::Publisher.unpublish!(slug)
      else
        puts "Canceled."
      end
    end

    # ===============================================
    # Deployment
    # ===============================================
    desc "deploy", "Build & publish book"
    def deploy
      Softcover::Commands::Deployment.deploy!
    end

    # ===============================================
    # Generator
    # ===============================================

    desc "new <name>", "Generate new document directory structure"
    method_option :polytex,
                  :type => :boolean,
                  :default => false,
                  :aliases => %w[-p --latex -l],
                  :desc => "Generate a LaTeX document."
    method_option :article,
                  :type => :boolean,
                  :default => false,
                  :aliases => "-a",
                  :desc => "Generate an article instead of a book."
    def new(n)
      Softcover::Commands::Generator.generate_file_tree(n, options)
    end

    # ===============================================
    # Open
    # ===============================================

    desc "open", "Open book on Softcover website"
    def open
      Softcover::Commands::Opener.open!
    end

    # ===============================================
    # EPUB validate
    # ===============================================

    desc "epub:validate, epub:check", "Validate EPUB with epubcheck"
    define_method "epub:validate" do
      Softcover::Commands::EpubValidator.validate!
    end
    map "epub:check" => "epub:validate"

    # ===============================================
    # Config
    # ===============================================

    desc "config", "View local config"
    def config
      require "softcover/config"
      puts "Reading contents of #{Softcover::Config.path}:"
      Softcover::Config.read
    end

    desc "config:add key=value", "Add to your local config vars"
    define_method "config:add" do |pair|
      require "softcover/config"
      key, value = pair.split "="
      Softcover::Config[key] = value

      puts 'Config var added:'
      config
    end

    desc "config:remove key", "Remove key from local config vars"
    define_method "config:remove" do |key|
      require "softcover/config"
      Softcover::Config[key] = nil

      puts 'Config var removed.'
    end

    # ===============================================
    # Utilities
    # ===============================================
    desc "exercises", "Add exercise id elements as spans"
    define_method "exercises" do
      Softcover::Commands::Exercises.add_to_chapters!
    end

    protected
      def ask_without_echo(*args)
        system "stty -echo"
        ret = ask *args
        system "stty echo"
        puts
        ret
      end
  end
end