# encoding: utf-8
module WebTranslateIt
  class CommandLine
    require 'fileutils'
    require 'set'
    attr_accessor :configuration, :global_options, :command_options, :parameters

    def initialize(command, command_options, global_options, parameters, project_path)
      self.command_options = command_options
      self.parameters = parameters
      unless command == 'init'
        case command
        when 'pull'
          message = "Pulling files"
        when 'push'
          message = "Pushing files"
        when 'add'
          message = "Creating master files"
        when 'rm'
          message = "Deleting files"
        when 'mv'
          message = "Moving files"
        when 'addlocale'
          message = "Adding locale"
        when 'rmlocale'
          message = "Deleting locale"
        else
          message = "Gathering information"
        end
        throb { print "  #{message}"; self.configuration = WebTranslateIt::Configuration.new(project_path, configuration_file_path); print " #{message} on #{self.configuration.project_name}"; }
      end
      success = self.send(command)
      exit 1 if !success
    end
    
    def pull
      complete_success = true
      STDOUT.sync = true
      `#{configuration.before_pull}` if configuration.before_pull
      # Selecting files to pull
      files = []
      fetch_locales_to_pull.each do |locale|
        files |= configuration.files.find_all{ |file| file.locale == locale }
      end
      found_files = []
      parameters.each do |parameter|
        found_files += files.find_all{ |file| File.fnmatch(parameter, file.file_path) }
      end
      files = found_files
      files = files.uniq.sort{ |a,b| a.file_path <=> b.file_path }
      if files.size == 0
        puts "No files to pull."
      else
        # Now actually pulling files
        time = Time.now
        threads = []
        n_threads = (files.size.to_f/3).ceil >= 10 ? 10 : (files.size.to_f/3).ceil
        ArrayUtil.chunk(files, n_threads).each do |file_array|
          unless file_array.empty?
            threads << Thread.new(file_array) do |file_array|
              WebTranslateIt::Connection.new(configuration.api_key) do |http|
                file_array.each do |file|
                  success = file.fetch(http, command_options.force)
                  complete_success = false if !success
                end
              end
            end
          end
        end
        threads.each { |thread| thread.join }
        time = Time.now - time
        puts "Pulled #{files.size} files at #{(files.size/time).round} files/sec, using #{n_threads} threads."
        `#{configuration.after_pull}` if configuration.after_pull
        complete_success
      end
    end
    
    def push
      complete_success = true
      STDOUT.sync = true
      `#{configuration.before_push}` if configuration.before_push
      WebTranslateIt::Connection.new(configuration.api_key) do |http|
        fetch_locales_to_push(configuration).each do |locale|
          if parameters.any?
            files = configuration.files.find_all{ |file| parameters.include?(file.file_path) }.sort{|a,b| a.file_path <=> b.file_path}
          else
            files = configuration.files.find_all{ |file| file.locale == locale }.sort{|a,b| a.file_path <=> b.file_path}
          end
          if files.size == 0
            puts "Couldn't find any local files registered on WebTranslateIt to push."
          else
            files.each do |file|
              success = file.upload(http, command_options[:merge], command_options.ignore_missing, command_options.label, command_options.low_priority, command_options[:minor], command_options.force)
              complete_success = false if !success
            end
          end
        end
      end
      `#{configuration.after_push}` if configuration.after_push
      complete_success
    end
    
    def add
      complete_success = true
      STDOUT.sync = true
      if parameters == []
        puts StringUtil.failure("Error: You must provide the path to the master file to add.")
        puts "Usage: wti add path/to/master_file_1 path/to/master_file_2 ..."
        exit
      end
      WebTranslateIt::Connection.new(configuration.api_key) do |http|
	      added = configuration.files.find_all{ |file| file.locale == configuration.source_locale}.collect {|file| File.expand_path(file.file_path) }.to_set
        to_add = parameters.reject{ |param| added.include?(File.expand_path(param))}
        if to_add.any?
          to_add.each do |param|
            file = TranslationFile.new(nil, param, nil, configuration.api_key)
            success = file.create(http, command_options.low_priority)
            complete_success = false if !success
          end
        else
          puts "No new master file to add."
        end
      end
      complete_success
    end

    def rm
      complete_success = true
      STDOUT.sync = true
      if parameters == []
        puts StringUtil.failure("Error: You must provide the path to the master file to remove.")
        puts "Usage: wti rm path/to/master_file_1 path/to/master_file_2 ..."
        exit
      end
      WebTranslateIt::Connection.new(configuration.api_key) do |http|
        parameters.each do |param|
          if Util.ask_yes_no("Are you sure you want to delete the master file #{param}?\nThis will also delete its target files and translations.", false)
            files = configuration.files.find_all{ |file| file.file_path == param }
            if files.any?
              files.each do |master_file|
                master_file.delete(http)
                # delete files
                if File.exists?(master_file.file_path)
                  success = File.delete(master_file.file_path)
                  puts StringUtil.success("Deleted master file #{master_file.file_path}.") if success
                end
                complete_success = false if !success
                configuration.files.find_all{ |file| file.master_id == master_file.id }.each do |target_file|
                  if File.exists?(target_file.file_path)
                    success = File.delete(target_file.file_path)
                    puts StringUtil.success("Deleted target file #{target_file.file_path}.") if success
                  else
                    puts StringUtil.failure("Target file #{target_file.file_path} doesn’t exist locally")
                  end
                  complete_success = false if !success
                end
              end
              puts StringUtil.success("All done.") if complete_success
            else
              puts StringUtil.failure("#{param}: File doesn’t exist on project.")
            end
          end
        end
      end
      complete_success
    end
    
    def mv
      complete_success = true
      STDOUT.sync = true
      if parameters.count != 2
        puts StringUtil.failure("Error: You must provide the source path and destination path of the master file to move.")
        puts "Usage: wti mv path/to/master_file_old_path path/to/master_file_new_path ..."
        exit
      end
      source = parameters[0]
      destination = parameters[1]
      WebTranslateIt::Connection.new(configuration.api_key) do |http|
        if Util.ask_yes_no("Are you sure you want to move the master file #{source} and its target files?", true)
          configuration.files.find_all{ |file| file.file_path == source }.each do |master_file|
            master_file.upload(http, false, false, nil, false, false, true, true, destination)
            # move master file
            if File.exists?(source)
              success = File.rename(source, destination) if File.exists?(source)
              puts StringUtil.success("Moved master file #{master_file.file_path}.") if success
            end
            complete_success = false if !success
            configuration.files.find_all{ |file| file.master_id == master_file.id }.each do |target_file|
              if File.exists?(target_file.file_path)
                success = File.delete(target_file.file_path)
                complete_success = false if !success
              end
            end
            configuration.reload
            configuration.files.find_all{ |file| file.master_id == master_file.id }.each do |target_file|
              success = target_file.fetch(http)
              complete_success = false if !success
            end
            puts StringUtil.success("All done.") if complete_success
          end
        end
      end
      complete_success
    end
    
    def addlocale
      STDOUT.sync = true
      if parameters == []
        puts StringUtil.failure("Locale code missing.")
        puts "Usage: wti addlocale fr es ..."
        exit 1
      end
      parameters.each do |param|
        print StringUtil.success("Adding locale #{param.upcase}... ")
        WebTranslateIt::Connection.new(configuration.api_key) do
          WebTranslateIt::Project.create_locale(param)
        end
        puts "Done."
      end
    end
    
    def rmlocale
      STDOUT.sync = true
      if parameters == []
        puts StringUtil.failure("Error: You must provide the locale code to remove.")
        puts "Usage: wti rmlocale fr es ..."
        exit 1
      end
      parameters.each do |param|
        if Util.ask_yes_no("Are you certain you want to delete the locale #{param.upcase}?\nThis will also delete its files and translations.", false)
          print StringUtil.success("Deleting locale #{param.upcase}... ")
          WebTranslateIt::Connection.new(configuration.api_key) do |http|
            WebTranslateIt::Project.delete_locale(param)
          end
          puts "Done."
        end
      end
    end
        
    def init
      puts "# Initializing project"
      if parameters.any?
        api_key = parameters[0]
        path = '.wti'
      else
        api_key = Util.ask(" Project API Key:")
        path = Util.ask(" Path to configuration file:", '.wti')
      end
      FileUtils.mkpath(path.split('/')[0..path.split('/').size-2].join('/')) unless path.split('/').size == 1
      project = YAML.load WebTranslateIt::Project.fetch_info(api_key)
      project_info = project['project']
      if File.exists?(path) && !File.writable?(path)
        puts StringUtil.failure("Error: `#{path}` file is not writable.")
        exit 1
      end
      File.open(path, 'w'){ |file| file << generate_configuration(api_key, project_info) }
      puts ""
      puts " The project #{project_info['name']} was successfully initialized."
      puts ""
      if project_info["source_locale"]["code"].nil? || project_info["target_locales"].size <= 1 || project_info["project_files"].none?
        puts ""
        puts " There are a few more things to set up:"
        puts ""
      end
      if project_info["source_locale"]["code"].nil?
        puts " *) You don't have a source locale setup."
        puts "    Add the source locale with: `wti addlocale <locale_code>`"
        puts ""
      end
      if project_info["target_locales"].size <= 1
        puts " *) You don't have a target locale setup."
        puts "    Add the first target locale with: `wti addlocale <locale_code>`"
        puts ""
      end
      if project_info["project_files"].none?
        puts " *) You don't have linguistic files setup."
        puts "    Add a master file with: `wti add <path/to/file.xml>`"
        puts ""
      end
      puts "You can now use `wti` to push and pull your language files."
      puts "Check `wti --help` for help."
      return true
    end
    
    def match
      configuration.files.find_all{ |mf| mf.locale == configuration.source_locale }.each do |master_file|
        if !File.exists?(master_file.file_path)
          puts StringUtil.failure(master_file.file_path) + " (#{master_file.locale})"
        else
          puts StringUtil.important(master_file.file_path) + " (#{master_file.locale})"
        end
        configuration.files.find_all{ |f| f.master_id == master_file.id }.each do |file|
          if !File.exists?(file.file_path)
            puts StringUtil.failure("- #{file.file_path}") + " (#{file.locale})"
          else
            puts "- #{file.file_path}" + " (#{file.locale})"
          end
        end
      end
      return true
    end
        
    def status
      stats = YAML.load(Project.fetch_stats(configuration.api_key))
      stale = false
      completely_translated = true
      completely_proofread  = true
      stats.each do |locale, values|
        percent_translated = Util.calculate_percentage(values['count_strings_to_proofread'].to_i + values['count_strings_done'].to_i + values['count_strings_to_verify'].to_i, values['count_strings'].to_i)
        percent_completed  = Util.calculate_percentage(values['count_strings_done'].to_i, values['count_strings'].to_i)
        completely_translated = false if percent_translated != 100
        completely_proofread  = false if percent_completed  != 100
        puts "#{locale}: #{percent_translated}% translated, #{percent_completed}% completed."
      end
      exit 100 if !completely_translated
      exit 101 if !completely_proofread
      return true
    end
                
    def fetch_locales_to_pull
      if command_options.locale
        command_options.locale.split.each do |locale|
          puts "Locale #{locale} doesn't exist -- `wti addlocale #{locale}` to add it." unless configuration.target_locales.include?(locale)
        end
        locales = command_options.locale.split
      else
        if configuration.needed_locales.any?
          locales = configuration.needed_locales
        else
          locales = configuration.target_locales
          if configuration.ignore_locales.any?
            configuration.ignore_locales.each{ |locale_to_delete| locales.delete(locale_to_delete) }
          end
        end
      end
      locales.push(configuration.source_locale) if command_options.all
      return locales.uniq
    end
        
    def fetch_locales_to_push(configuration)
      if command_options.locale
        command_options.locale.split.each do |locale|
          puts "Locale #{locale} doesn't exist -- `wti addlocale #{locale}` to add it." unless configuration.target_locales.include?(locale)
        end
        locales = command_options.locale.split
      else
        locales = [configuration.source_locale]
      end
      if command_options.all
        puts "`wti push --all` was deprecated in wti 2.3. Use `wti push --target` instead."
        return []
      elsif command_options.target
        locales = configuration.target_locales.reject{ |locale| locale == configuration.source_locale }
      end
      return locales.uniq
    end
    
    def configuration_file_path
      if self.command_options.config
        return self.command_options.config
      else
        if File.exists?('config/translation.yml')
          puts "Warning: `config/translation.yml` is deprecated in favour of a `.wti` file."
          if Util.ask_yes_no("Would you like to migrate your configuration now?", true)
            require 'fileutils'
            if FileUtils.mv('config/translation.yml', '.wti')
              return '.wti'
            else
              puts "Couldn’t move `config/translation.yml`."
              return false
            end
          else
            return 'config/translation.yml'
          end
        else
          return '.wti'
        end
      end
    end
    
    def generate_configuration(api_key, project_info)
      file = <<-FILE
api_key: #{api_key}

# Optional: locales not to sync with WebTranslateIt.
# Takes a string, a symbol, or an array of string or symbol.
# More information here: https://github.com/AtelierConvivialite/webtranslateit/wiki
# ignore_locales: '#{project_info["source_locale"]["code"]}'

# Or if you prefer a list of locales to sync with WebTranslateIt:
# needed_locales: #{project_info["target_locales"].map {|locale| locale["code"]}.to_s}

# Optional
# before_pull: "echo 'some unix command'"   # Command executed before pulling files
# after_pull:  "touch tmp/restart.txt"      # Command executed after pulling files
#
# before_push: "echo 'some unix command'"   # Command executed before pushing files
# after_push:  "touch tmp/restart.txt"      # Command executed after pushing files

# Silence SSL errors
# silence_errors: true

FILE
      return file
    end

    def throb
      throb = %w(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
      throb.reverse! if rand > 0.5
      i = rand throb.length

      thread = Thread.new do
        dot = lambda do
          print "\r#{throb[i]}\e[?25l"
          i = (i + 1) % throb.length
          sleep 0.1 and dot.call
        end
        dot.call
      end
      yield
      ensure
        if thread
          thread.kill
          puts "\r\e[0G#\e[?25h"
        end
      end
    end
end