#!/usr/bin/env ruby

abort "Please use Ruby 1.9.3 or higher" if RUBY_VERSION < "1.9.3"

require 'gli'
require 'omf_ec'
$stdout.sync = true

include GLI::App
include OmfEc

program_desc "Run a command on the testbed(s)"
version OmfEc::VERSION

desc "Debug mode (printing debug logging messages)"
switch [:d, :debug]

desc "URI for communication layer"
arg_name "URI"
default_value "xmpp://localhost"
flag [:u, :uri]

desc "Debug XMPP traffic mode (include XMPP debug logging messages under debug mode)."
switch [:x, :xmpp]

desc "Root certificate file"
arg_name "cert", :optional
flag [:root_cert]

desc "Your certificate"
arg_name "cert", :optional
flag [:cert]

desc "Your private key"
arg_name "key", :optional
flag [:key]

desc "Log file directory"
arg_name "directory"
default_value "/tmp"
flag [:log_file_dir]

config_file(".config/omf_ec.yml")

desc "Execute an experiment script"
arg_name "path_to_script_file [-- --experiment_property value]"
command :exec do |c|
  c.desc "Experiment name"
  c.arg_name "experiment_name"
  c.flag [:e, :experiment]

  c.desc "Default OML URI to use for collecting measurements"
  c.arg_name "uri"
  c.flag [:oml_uri]

  c.desc "Check script version (you need to define OMF_VERSIONS in your script"
  c.switch "version_check"

  c.action do |global_options, options, args|
    help_now! "Missing experiment script" if args[0].nil?
    help_now! "Experiment script not found" unless File.exist?(args[0])

    # User-provided command line values for Experiment Properties cannot be
    # set here as the propertties have not been defined yet by the experiment.
    # Thus just pass them to the experiment, which will be responsible
    # for setting them later
    properties = {}
    if args.size > 1
      exp_properties = args[1..-1]
      exp_properties.in_groups_of(2) do |p|
        unless p[0] =~ /^--(.+)/ && !p[1].nil?
          help_now! "Malformatted properties '#{exp_properties.join(' ')}'"
        else
          properties[$1.to_sym] = p[1].ducktype
        end
      end
      OmfEc.experiment.cmdline_properties = properties
    end

    # FIXME this loading script is way too simple
    load_exp(args[0], global_options, options, properties)
  end
end

desc "Load an image onto the nodes"
command :load do |c|
  #c.desc "use this testbed configuration in OMF 5 EC config file"
  #c.arg_name "AGGREGATE"
  #c.flag [:c, :config], :default_value => "default"

  c.desc "comma-separated list of nodes to image"
  c.arg_name "TOPOLOGY"
  c.flag [:t, :topology], :default_value => "system:topo:all"

  c.desc "disk image to load"
  c.arg_name "IMAGE"
  c.flag [:i, :image], :default_value => "baseline.ndz"

  c.desc "seconds to wait for the imaging process to complete"
  c.arg_name "TIMEOUT"
  c.flag [:o, :timeout], :default_value => "800"

  c.desc "resize the first partition to SIZE GB or to maximum size if SIZE=0 "+
    "or leave x percent of free space if SIZE=x%"
  c.arg_name "SIZE"
  c.flag [:r, :resize]

  c.desc "Path where the resulting Topologies should be saved"
  c.arg_name "PATH"
  c.flag [:outpath], :default_value => "/tmp"

  c.desc "Prefix to use for naming the resulting Topologies (default is your experiment ID)"
  c.arg_name "PREFIX"
  c.flag [:outprefix]

  c.action do |global_options, options, args|
    @cmd = "USER=#{ENV['USER']} omf-5.4 load -t #{options[:t]} -i #{options[:i]} "
    @cmd += "-o #{options[:o]} --outpath #{options[:outpath]} "
    @cmd += "-r #{options[:r]} " if options[:r]
    @cmd += "--outprefix #{options[:outprefix]} " if options[:outprefix]
    load_exp(@testbed_exp_path, global_options, options)
  end
end

desc "Save an image of a node"
command :save do |c|
  #c.desc "use this testbed configuration in OMF 5 EC config file"
  #c.arg_name "AGGREGATE"
  #c.flag [:c, :config], :default_value => "default"

  c.desc "node to save from"
  c.arg_name "NODE"
  c.flag [:n, :node]

  c.desc "resize the first partition to SIZE GB or to maximum size if SIZE=0 "+
    "or leave x percent of free space if SIZE=x%"
  c.arg_name "SIZE"
  c.flag [:r, :resize]

  c.action do |global_options, options, args|
    @cmd = "USER=#{ENV['USER']} omf-5.4 save "
    @cmd += "-n #{options[:n]} " if options[:n]
    @cmd += "-r #{options[:r]} " if options[:r]
    load_exp(@testbed_exp_path, global_options, options)
  end
end

desc "Return the status of the nodes"
command :stat do |c|
  c.desc "use this testbed configuration in OMF 5 EC config file"
  c.arg_name "AGGREGATE"
  c.flag [:c, :config], :default_value => "default"

  c.desc "comma-separated list of nodes to image"
  c.arg_name "TOPOLOGY"
  c.flag [:t, :topology], :default_value => "system:topo:all"

  c.desc "print a summary of the node status for the testbed"
  c.switch [:s, :summary]

  c.action do |global_options, options, args|
    @cmd = "omf-5.4 stat -c #{options[:c]} -t #{options[:t]} "
    @cmd += "-s" if options[:s]
    load_exp(@testbed_exp_path, global_options, options)
  end
end

desc "Power on/off, reset or reboot the nodes"
command :tell do |c|
  c.desc "use this testbed configuration in OMF 5 EC config file"
  c.arg_name "AGGREGATE"
  c.flag [:c, :config], :default_value => "default"

  c.desc "comma-separated list of nodes to image"
  c.arg_name "TOPOLOGY"
  c.flag [:t, :topology], :default_value => "system:topo:all"

  c.desc "
      'on'              turn node(s) ON -
      'offs'            turn node(s) OFF (soft) -
      'offh'            turn node(s) OFF (hard) -
      'reboot'          reboots node(s) (soft) -
      'reset'           resets node(s) (hard)"
      c.arg_name "ACTION"
      c.flag [:a, :action]

      c.action do |global_options, options, args|
        @cmd = "omf-5.4 tell -c #{options[:c]} -t #{options[:t]} "
        @cmd += "-a #{options[:a]} " if options[:a]
        load_exp(@testbed_exp_path, global_options, options)
      end
end

on_error do |exception|
  true
end

pre do |global_options, command, options, args|
  unless global_options[:uri]
    help_now! "Incomplete options. Need communication URI"
  end


  # Import private key
  if global_options[:private_key]
    OmfCommon::Key.instance.import(global_options[:private_key])
  end

  # Check version
  if options[:check]
    File.open(args[0], 'r') do |f|
      f.read.chomp.match(/OMF_VERSIONS\W*=\W*(.*)/)
      versions = $1
      unless versions && versions.split(',').include?(OmfCommon::PROTOCOL_VERSION)
        raise StandardError, "Could not find compatibile protocol version number in your script"
      end
    end
  end

  include OmfEc::DSL

  OmfEc.experiment.name = options[:experiment] if options[:experiment]
  OmfEc.experiment.oml_uri = options[:oml_uri] if options[:oml_uri]

  @testbed_exp_path = File.join(OmfEc.lib_root, "omf_ec/backward/exp/testbed.rb")
end

def setup_logging(global_options = {})
  if global_options[:xmpp]
    require 'blather'
    Blather.logger = logger
  end

  unless global_options[:debug]
    Logging.consolidate 'OmfCommon', 'OmfEc', 'OmfRc'
  end

  # FIXME this should go to common setup
  if global_options[:log_file_dir] && File.exist?(global_options[:log_file_dir])
    Logging.logger.root.add_appenders(
      Logging.appenders.file(
        "#{global_options[:log_file_dir]}/#{OmfEc.experiment.id}.log",
        :layout => Logging.layouts.pattern(:date_pattern => '%F %T %z',
                                           :pattern => '[%d] %-5l %c: %m\n')))
  end
end

def load_exp(exp_path, global_options = {} , options = {}, properties = {})
  begin
    if global_options[:root_cert] && File.exist?(global_options[:root_cert])
      root = OmfCommon::Auth::Certificate.create_from_x509(File.read(global_options[:root_cert]))
    end

    if global_options[:cert] && File.exist?(global_options[:cert]) &&
      global_options[:key] && File.exist?(global_options[:key])

      entity = OmfCommon::Auth::Certificate.create_from_x509(File.read(global_options[:cert]),
                                                             File.read(global_options[:key]))
    end

    opts = {
      communication: { url: global_options[:uri] },
      eventloop: { type: :em },
      logging: {
        level: { default: global_options[:debug] ? 'debug' : 'info' },
        appenders: {
          stdout: {
            date_pattern: '%H:%M:%S',
            pattern: '%d %-5l %c{2}: %m\n',
            color_scheme: 'default'
          }
        }
      }
    }

    opts[:communication][:auth] = { certs: [ root.to_pem_compact ] } if root

    OmfCommon.init(:development, opts) do |el|
      setup_logging(global_options)

      OmfCommon.comm.on_connected do |comm|
        info "Connected using #{comm.conn_info}"
        info "Start experiment: #{OmfEc.experiment.id}"

        OmfCommon::Auth::CertificateStore.instance.register(root) if root
        OmfCommon::Auth::CertificateStore.instance.register(entity, OmfCommon.comm.local_address) if entity

        begin
          include OmfEc::Backward::DefaultEvents
          load exp_path
        rescue => e
          error e.message
          error e.backtrace.join("\n")
        end

        comm.on_interrupted { comm.disconnect }
      end
    end
  rescue => e
    logger.fatal e.message
    logger.fatal e.backtrace.join("\n")
  end
end

exit run(ARGV)