#! /usr/bin/env ruby

require File.expand_path(File.dirname(__FILE__)+ '/../lib/s3_website')
require 'colored'
require 'digest/md5'

class Cfg < Thor
  def self.exit_on_failure? 
    true
  end

  desc 'create', 'Create a config file with placeholder values'
  def create
    config_file_src = File.dirname(__FILE__) + '/../resources/configuration_file_template.yml'
    config_file = Dir.pwd + '/s3_website.yml'
    unless File.exists?  config_file
      require 'fileutils'
      FileUtils.cp config_file_src, config_file
      puts "Created the config file s3_website.yml.  Go fill it with your settings." 
    end
  rescue Exception => error
    puts "#{error.message} (#{error.class})"
    exit 1
  end

  option(
    :headless,
    :type => :boolean,
    :desc => "Apply the settings headlessly.  See --autocreate-cloudfront-distribution for more info." 
  )
  option(
    'autocreate-cloudfront-dist',
    :type => :boolean,
    :desc => "When used with --headless, automatically create a CloudFront distribution for your S3 website." 
  )
  option(
    'config-dir',
    :type => :string,
    :desc => "The directory where your config file is.  When not defined, s3_website will look in the current working directory.", 
    :default => "." 
  )
  desc 'apply', 'Apply the configuration on the AWS services'
  long_desc <<-LONGDESC
    `s3_website cfg apply` will apply the configuration the S3 bucket. 

    In addition, if you CloudFront related settings, this command will apply them on the CloudFront distribution. 

    If the S3 bucket does not exist, this command will create it
    and configure it to function as a website. 
  LONGDESC
  def apply
    puts 'Applying the configurations in s3_website.yml on the AWS services ...'
    require 'configure-s3-website'
    config_source = ConfigureS3Website::FileConfigSource.new "#{options['config-dir']}/s3_website.yml"
    ConfigureS3Website::Runner.run({
      :config_source => config_source,
      :headless => options[:headless],
      'autocreate-cloudfront-dist' => options['autocreate-cloudfront-dist']
    })
  rescue Exception => error
    puts "#{error.message} (#{error.class})"
    exit 1
  end
end

class Cli < Thor
  def self.exit_on_failure? 
    true
  end

  option(
    :site,
    :type => :string,
    :desc => "The directory where your website files are.  When not defined, s3_website will look for the site in either _site or public/output." 
  )
  option(
    'config-dir'.to_sym,
    :type => :string,
    :desc => "The directory where your config file is.  When not defined, s3_website will look in the current working directory." 
  )
  option(
    :verbose,
    :type => :boolean,
    :default => false,
    :desc => "Print verbose output"
  )
  option(
    :force,
    :type => :boolean,
    :default => false,
    :desc => "Skip diff calculation and push all the files.  This option is useful when you need to update metadata on the S3 objects." 
  )
  option(
    :dry_run,
    :type => :boolean,
    :default => false,
    :desc => "Run the operation without actually making the modifications.  When this switch is on, changes will not be applied on the S3 website.  They will be only printed to the console." 
  )
  desc 'push', 'Push local files with the S3 website'
  long_desc <<-LONGDESC
    `s3_website push` will upload new and changes files to S3.  It will
    also delete from S3 the files that you no longer have locally. 
  LONGDESC
  def push
    project_root = File.expand_path(File.dirname(__FILE__)+ '/..')
    logger = Logger.new(options[:verbose])
    success =
      if run_with_sbt(project_root)
        Dir.chdir(project_root) {
          system './sbt assembly' # Build the jar
        }
        system "java -cp #{project_root}/target/scala-2.11/s3_website.jar #{resolve_java_command 'push'}"
      else
        # Find the jar
        jar_file = resolve_jar(project_root, logger)
        # Then run it
        run_s3_website_jar(jar_file, logger)
      end

    if success
      exit 0
    else
      exit 1
    end
  end

  desc 'install', 'Download s3_website.jar'
  long_desc <<-LONGDESC
    Use `s3_website install` to download the s3_website.jar dependency
    before you run `s3_website push` (good for CI dependency caching). 
  LONGDESC
  def install
    project_root = File.expand_path(File.dirname(__FILE__)+ '/..')
    logger = Logger.new(options[:verbose])
    resolve_jar(project_root, logger)
  end

  desc 'cfg SUBCOMMAND ...ARGS', 'Operate on the config file'
  subcommand 'cfg', Cfg
end

def run_with_sbt(project_root)
  File.exists?(project_root + '/project/sbt-launch.jar')
end

def resolve_java_command(command_name)
  args = ARGV.join(' ').sub(command_name, '')
  "s3.website.#{command_name.capitalize} #{args}"
end

def run_s3_website_jar(jar_file, logger)
  java_installed = (resolve_exit_status('which java') or resolve_exit_status('java -version'))
  unless java_installed
    logger.info_msg "Cannot find Java.  s3_website push is implemented in Scala, and it needs Java to run." 
    autoinstall_java_or_print_help_and_exit(logger)
  end
  logger.debug_msg "Using #{jar_file}"
  system("java -cp #{jar_file} #{resolve_java_command 'push'}")
end

def resolve_exit_status(cmd)
  `#{cmd}`
  cmd_succeeded = $?  == 0
rescue
  cmd_succeeded = false
end

def autoinstall_java_or_print_help_and_exit(logger)
  @logger = logger
  automatic_methods = [
    {
      :package_manager_lookup => 'which apt-get',
      :install_command => 'sudo apt-get install --assume-yes openjdk-7-jre'
    },
    {
      :package_manager_lookup => 'which yum',
      :install_command => 'sudo yum install --assumeyes java-1.7.0-openjdk'
    },
    {
      :package_manager_lookup => 'which dnf',
      :install_command => 'sudo dnf install --assumeyes java-1.7.0-openjdk'
    }
  ]

  def print_manual_method_and_exit
    @logger.info_msg 'Go to http://java.com, install Java and then try again.' 
    @logger.info_msg "(If you cannot or do not want to install Java, you can use latest 1.x version of this gem, which requires only Ruby.  For more info, see https://github.com/laurilehmijoki/s3_website/tree/1.x)"
    exit 1
  end

  automatic_method = automatic_methods.find { |automatic_method|
    resolve_exit_status automatic_method.fetch(:package_manager_lookup)
  }
  if automatic_method
    @logger.info_msg "Do you want me to install Java with the command `#{automatic_method.fetch(:install_command)}`? [ Y/n]"
    user_answer = $stdin.gets
    if user_answer.chomp.downcase == 'y' or user_answer == "\n"
      automatic_method_succeeded = system automatic_method.fetch(:install_command)
      unless automatic_method_succeeded
        @logger.fail_msg "Could not automatically install Java.  Try setting it up manually:"
        print_manual_method_and_exit
      end
    else
      print_manual_method_and_exit
    end
  else
    print_manual_method_and_exit
  end
end

def resolve_jar(project_root, logger)
  jar_lookup_paths = [
    project_root + "/s3_website-#{S3Website::VERSION}.jar",
    (ENV['TMPDIR'] || '/tmp') + "/s3_website-#{S3Website::VERSION}.jar"
  ]
  found_jar = jar_lookup_paths. 
    select { |jar_path|
      File.exists?  jar_path
    }. 
    first
  def jar_has_valid_checksum(jar_path, logger)
    expected_checksum = File.read(File.dirname(__FILE__) + '/../resources/s3_website.jar.md5')
    found_checksum = Digest::MD5.file(jar_path).hexdigest
    if expected_checksum == found_checksum
      true
    else
      logger.info_msg "The jar file is corrupted. ( Expected checksum #{expected_checksum} but got #{found_checksum}.)" 
      false
    end
  end
  jar_file =
    if found_jar and jar_has_valid_checksum(found_jar, logger)
      found_jar
    else
      download_jar(jar_lookup_paths, logger)
    end
end

def download_jar(jar_lookup_paths, logger)
  tag_name = "v#{S3Website::VERSION}"
  downloaded_jar = jar_lookup_paths.select { |jar_path|
    File.writable?  File.dirname(jar_path)
  }.first
  unless downloaded_jar
    logger.fail_msg "Neither #{jar_lookup_paths.join ' or '} is writable.  Cannot download s3_website.jar." 
    logger.fail_msg "Set either directory as writable to the current user and try again." 
    exit 1
  end
  download_url = "https://github.com/laurilehmijoki/s3_website/releases/download/#{tag_name}/s3_website.jar"
  logger.info_msg "Downloading #{download_url} into #{downloaded_jar}"
  require 'open-uri'
  open(downloaded_jar, 'wb') do |file|
    file << open(download_url).read
  end
  downloaded_jar
end

class Logger
  attr_reader :verbose
  def initialize(verbose)
    @verbose = verbose
  end

  def debug_msg(msg)
    if verbose
      print_msg 'debg'.cyan, msg
    end
  end

  def info_msg(msg)
    print_msg 'info'.blue, msg
  end

  def fail_msg(msg)
    print_msg 'fail'.red, msg
  end

  private

  def print_msg(prefix, msg)
    puts "[#{prefix}] #{msg}"
  end
end

Cli.start(ARGV)
