#! /usr/bin/env ruby

require 'optparse'
require 'orocos'
require 'time'
require 'rock/bundle'

debug = false
save_configuration = nil
parser = OptionParser.new do |opt|
    opt.banner = <<-EOT
usage: oroconf apply   task_name /path/to/configuration conf_name1 conf_name2
usage: oroconf extract task_model [--save=FILE_OR_DIR[:section_name]]
usage: oroconf display task_name [--save=FILE_OR_DIR[:section_name]]
usage: oroconf logextract logfile task_name time [--save=FILE_OR_DIR[:section_name]]
usage: oroconf load /path/to/configuration

Manages configuration through files. The following subcommands are available:
  
  apply: apply a specified configuration to a running task, whose name is provided.
    FILE_OR_DIR is either the path to a configuration file or to a configuration
    directory, in which case the <task_model_name>.yml file is going to be used.
  extract: generates a YAML config file from a task model, i.e which contains
    the default values for the properties. This is usually used to create
    new configuration files
  display: generates a YAML config file from the current property values in a running
    task, whose name is given.
  logextract: generates a YAML config file from a property log (usually generated
    using Orocos.log_all and named properties.0.log). This is usually used to debug
    failing components, making sure that the same configuration has been used. The
    provided time is used to identify from which sample this configuration file
    should be generated: the sample just before the provided time (in floating-point
    form) will be used. The special keyword @last can be used to use the last sample
    in each stream.
  load: loads all configuration files from a directory, or a single config file.
    Use it to check that your configuration files are valid

The extract, display and logextract subcommand output the generated configuration to
the console by default. If a --save option is provided, it is instead appended to the
specified file or, if it is a directory, to the <task_model_name>.yml file in this
directory.
    EOT

    opt.on('--help') do
        puts parser
        exit(0)
    end
    opt.on('--host HOSTNAME', 'the host to contact for name service') do |host|
        Orocos::CORBA.name_service = host
    end

    opt.on('--save FILE:SECTION', 'in extract and display modes, saves the configuration in the specified file. If a file is a directory, an automatic name will be generated') do |conf_config|
        if !conf_config
            save_configuration = nil
        elsif conf_config =~ /:(\w+)$/
            save_configuration = [$`, $1]
        else
            save_configuration = [conf_config]
        end
    end
    opt.on('--debug') do
        debug = true
    end
end


Rock::Bundles.public_logs = false
Rock::Bundles.initialize
if Rock::Bundles.has_selected_bundle?
    config_dir = File.join(Rock::Bundles.current_bundle.path, 'config', 'orogen')
    FileUtils.mkdir_p config_dir
    save_configuration = [config_dir]
end

remaining = parser.parse(ARGV)


def dump_configuration(task, save_configuration)
    model_name = task.model.name
    Orocos.task_model_from_name(model_name)
    if save_configuration
        file, section_name = *save_configuration
        if File.directory?(file)
            file = File.join(file, "#{model_name}.yml")
        end
        section_name ||= "default"
        Orocos::TaskConfigurations.save(task, file, section_name)
    else
	task_conf = Orocos::TaskConfigurations.read_task_conf(task)
	puts YAML.dump(Orocos::TaskConfigurations.to_yaml(task_conf))
    end
end

mode = remaining.shift
case mode
when "extract"
    model_name = remaining.shift
    if !model_name
        STDERR.puts "missing a model name as argument"
    end
    if !Orocos.default_pkgconfig_loader.find_task_library_from_task_model_name(model_name)
        STDERR.puts "#{model_name} is not a known model name"
    end

    Orocos.run model_name => "oroconf_extract" do
        task = Orocos::TaskContext.get "oroconf_extract"
        dump_configuration(task, save_configuration)
    end
    if save_configuration
	path, section = *save_configuration
	if File.directory?(path)
	    path = File.join(save_configuration[0], "#{model_name}.yml")
	end
        puts "saved in #{path}"
    end

when "logextract"
    require 'pocolog'
    logfile = remaining.shift
    if !logfile || !File.file?(logfile)
        STDERR.puts "missing a log file name as argument"
    end
    logfile = Pocolog::Logfiles.open(logfile)
    if !(task_name = remaining.shift)
        STDERR.puts "no task name given. Available tasks: #{logfile.streams.map { |s| s.name.gsub(/\..*$/, '') }.uniq.sort.join(", ")}"
        exit 1
    end
    if !(time = remaining.shift)
        STDERR.puts "no time given. Expected either a time as a floating-point value or @last"
        exit 1
    end
    if time != "@last" && time !~ /^\d+(\.\d+)?$/
        STDERR.puts "expected either a time as a floating-point value or @last, but got #{time}"
        exit 1
    end

    sample_accessor =
        if time == "@last"
            lambda { |s| s.last.last }
        else
            time = Time.at(Float(time))
            lambda do |s|
                _, _, sample = s.seek(time)
                if !sample
                    raise ArgumentError, "no sample in #{s.name} at #{time}"
                end
                sample
            end
        end

    result = Hash.new
    model_name = nil
    logfile.streams.each do |stream|
        if !stream.metadata.empty?  && stream.metadata['rock_stream_type'] != 'property'
            next
        end
        model_name ||= stream.metadata['rock_task_model']

        if stream.name =~ /^#{task_name}\.(.*)/
            result[$1] = Orocos::TaskConfigurations.typelib_to_yaml_value(Typelib.from_ruby(sample_accessor[stream], stream.type))
        end
    end
    result = YAML.dump(result)

    if save_configuration
        file, section_name = *save_configuration
        if File.directory?(file)
            if !model_name
                STDERR.puts "cannot use logextract --save=DIR with old logs. When running pocolog <file> --metadata, only streams that have a rock_task_model metadata can be saved in directories"
            end
            file = File.join(file, "#{model_name}.yml")
        end
        FileUtils.mkdir_p File.dirname(file)
        result = result.split("\n")
        result[0] = "--- name:#{section_name || 'default'}"
        File.open(file, 'a') do |io|
            io.puts result.join("\n")
        end
    else
        result = result.split("\n")
        result[0] = "--- name:#{section_name || 'default'}"
        puts result.join("\n")
    end

when "apply"
    task_name = remaining.shift
    path = remaining.shift
    conf_names = remaining.dup

    task = Orocos::TaskContext.get task_name

    if !File.exists?(path)
        STDERR.puts "no such file or directory #{path}"
    end
    Orocos.apply_conf(task, path, conf_names)

when "load"
    path = remaining.shift
    Orocos.conf.load_dir(path)

when "display"
    task_name = remaining.shift
    task = Orocos::TaskContext.get task_name
    dump_configuration(task, save_configuration)
else
    if mode
        STDERR.puts "Invalid operation mode #{mode}. Expected one of: extract, logextract, apply, display or load"
    else
        STDERR.puts "No operation mode specified. Expected one of: extract, logextract, apply, display or load"
    end
    exit 1
end

