#!/usr/bin/ruby
#
# Copyright 2009-2016 National ICT Australia (NICTA), Australia
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

#
# This program taken an OMF application definition and creates various files to
# simplify building applications which use OML.
#

PROG = 'oml2-scaffold'
VERSION_STRING = "#{PROG} V2.11.1rc"
COPYRIGHT = "Copyright 2009-2014, NICTA"

require 'stringio'
require 'optparse'

$integer_alias = :int32

$types = {
  :boolean => {
    :ctype => "uint8_t %s",
    :setter => "bool",
    :defval => "(val%5)",
    :omltype => "OML_BOOL_VALUE",
    :popttype => "POPT_ARG_NONE",
    :poptctype => "int",
    :poptdefval => "0",
  },
  :string => {                      # type used in the AppDescription
    # Code generation
    :ctype => "const char *%s",     # type used for OML-injectable variables of this types
    :setter => "string",            # appended to "omlc_set_"
    :defval => "\"foo\"",           # example argument(s) passed to injection helper, can use the variable "val"
    :omltype => "OML_STRING_VALUE", # OML type for Measurement Point definition
    # Command line parsing
    :popttype => "POPT_ARG_STRING", # type used for popt(3) command-line options
    :poptctype => "char*",          # type of the variable where popt(3) will store the value
    :poptdefval => "\"\"",          # default value for this variable if none is specified on the CLI
  },
  :long => {
    :ctype => "long *%s",
    :setter => "long",
    :defval => "(long)val",
    :omltype => "OML_LONG_VALUE",
    :popttype => "POPT_ARG_LONG",
    :poptctype => "int",
    :poptdefval => "0",
  },
  :int32 => {
    :ctype => "int32_t %s",
    :setter => "int32",
    :defval => "((int32_t)-val)",
    :omltype => "OML_INT32_VALUE",
    :popttype => "POPT_ARG_INT",
    :poptctype => "int",
    :poptdefval => "0",
  },
  :uint32 => {
    :ctype => "uint32_t %s",
    :setter => "uint32",
    :defval => "((uint32_t)val)",
    :omltype => "OML_UINT32_VALUE",
    :popttype => "POPT_ARG_INT",
    :poptctype => "unsigned int",
    :poptdefval => "0",
  },
  :int64 => {
    :ctype => "int64_t %s",
    :setter => "int64",
    :defval => "((int64_t)-val)",
    :omltype => "OML_INT64_VALUE",
    :popttype => "POPT_ARG_INT",
    :poptctype => "int",
    :poptdefval => "0",
  },
  :uint64 => {
    :ctype => "uint64_t %s",
    :setter => "uint64",
    :defval => "((uint64_t)val)",
    :omltype => "OML_UINT64_VALUE",
    :popttype => "POPT_ARG_INT",
    :poptctype => "unsigned int",
    :poptdefval => "0",
  },
  :double => {
    :ctype => "double %s",
    :setter => "double",
    :defval => "1.0/val",
    :omltype => "OML_DOUBLE_VALUE",
    :popttype => "POPT_ARG_DOUBLE",
    :poptctype => "double",
    :poptdefval => "0.",
  },
  :blob => {
    :ctype => "const void *%s",
    :setter => "blob",
    :defval => "NULL, 0",
    :omltype => "OML_BLOB_VALUE",
    #:popttype => false, # no blob on the command line
    #:poptctype => false,
    #:poptdefval => false,
  },
  :guid => {
    :ctype => "oml_guid_t %s",
    :setter => "guid",
    :defval => "OMLC_GUID_NULL",
    :omltype => "OML_GUID_VALUE",
    #:popttype => false, # no GUID on the command line
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_double => {
    :ctype => "const double %s[]",
    :setter => "vector_double",
    :defval => "",
    :omltype => "OML_VECTOR_DOUBLE_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_int32 => {
    :ctype => "const int32_t %s[]",
    :setter => "vector_int32",
    :defval => "NULL, 0",
    :omltype => "OML_VECTOR_INT32_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_uint32 => {
    :ctype => "const uint32_t %s[]",
    :setter => "vector_uint32",
    :defval => "",
    :omltype => "OML_VECTOR_UINT32_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_int64 => {
    :ctype => "const int64_t %s[]",
    :setter => "vector_int64",
    :defval => "",
    :omltype => "OML_VECTOR_INT64_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_uint64 => {
    :ctype => "const uint64_t %s[]",
    :setter => "vector_uint64",
    :defval => "",
    :omltype => "OML_VECTOR_UINT64_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
  :vector_bool => {
    :ctype => "const bool %s[]",
    :setter => "vector_bool",
    :defval => "",
    :omltype => "OML_VECTOR_BOOL_VALUE",
    #:popttype => false,
    #:poptctype => false,
    #:poptdefval => false,
  },
}
# List the currently supported types, without including backward compatibility
# elements
$ctypes = $types.keys.select { |k| k != :long && !$types[k][:ctype].nil? }
$popttypes = $types.keys.select { |k| !$types[k][:popttype].nil? }
# Backward-compatible mappings for deprecated types
# :integer and :int are added in run(), after the command-line parsing
$types[:float] = $types[:double]
$types[:real] = $types[:double]
$types[:flag] = $types[:boolean]

## The real logic of oml2-scaffold.
# Parse the command line arguments, and decide what to do
# \see AppDefinition, AppDefinition::template, Property, MeasurementPoint, Metric
def run()
  $stderr.puts "INFO\t#{VERSION_STRING} #{COPYRIGHT}"
  if File.basename($PROGRAM_NAME) == 'oml2_scaffold'
    old = File.basename($PROGRAM_NAME)
    new = old.gsub('_', '-')
    $stderr.puts "WARN\t'#{old}' is a deprecated name for this script; use '#{new}' instead."
  end

  opts = OptionParser.new
  opts.banner = %Q|
Generate C code for an OML application from an application definition file

  Usage: #{$0} [OPTIONS] [app_file]

|

  output = []
  app_name = 'unknown'
  create_app = false
  force = false

  opts.on('-a', "--app PROG_NAME",
      "Create a skeleton OMF application definition file ") do |name|
    create_app = true
    app_name = name
  end
  opts.on('-f', "--force",
      "Do not check if a file would be overwritten") do
    force = true
  end
  opts.on('-l', "--long",
      "Alias :int and :integer to underlying :long") do
    $integer_alias = :long
  end
  opts.on('-i', "--int32",
      "Alias :int and :integer and :long to underyling :int32 (default)") do
    $integer_alias = :int32
  end
  opts.on(nil, "--oml",
      "Create the oml header file defining all measurement points") do
    output << :oml
  end
  opts.on(nil, "--opts",
      "Create the popt header file for all application properties") do
    output << :popt
  end
  opts.on("--make",
      "Create a skeleton Makefile for this application") do
    output << :make
  end
  opts.on("--main",
      "Create a skeleton main file for this application") do
    output << :main
  end

  opts.on_tail("-h", "--help", "Show this message") do $stderr.write(opts); exit end
  opts.on_tail("-v", "--version", "Show the version\n") do
    $stderr.puts VERSION_STRING
    exit
  end

  begin
    rest = opts.parse(ARGV)
  rescue OptionParser::InvalidOption => ex
    $stderr.puts ex
    exit -1
  rescue OptionParser::MissingArgument => ex
    $stderr.puts ex
    exit -1
  end

  if rest.size > 1
    $stderr.puts "ERROR\tToo many arguments"
    $stderr.write(opts.banner)
    exit -1
  end
  app_file = rest[0]

  # Backward-compatible mappings for deprecated integer types, others are set above
  $types[:integer] = $types[$integer_alias]
  $types[:int] = $types[$integer_alias]

  if create_app
    if app_file
      $stderr.puts "ERROR\tDon't specify app_file when asking to create one"
      exit -1
    end
    uri = "#{ENV['USER']}:app:#{app_name}"
    ad = AppDefinition.new uri, app_name
    app_file = "#{app_name}.rb"
    if File.exists?(app_file) && !force
      $stderr.puts "ERROR\tFile #{app_file} already exists. Use --force to overwrite"
      exit -1
    else
      File.open(app_file, 'w') do |f|
	ad.write_app_def(f, app_file)
      end
    end
    $stderr.puts "INFO\tCreated #{app_file}"
    $stderr.puts "INFO\tYou can now generate a skeleton main file with '#{$PROGRAM_NAME} --main #{app_file}' and its associated Makefile with '#{$PROGRAM_NAME} --make #{app_file}'"
    AppDefinition.remove ad
    exit
  end

  unless app_file
    $stderr.puts "ERROR\tMissing app_file"
    exit -1
  end

  app_file = File.expand_path(app_file)
  unless File.readable?(app_file)
    $stderr.puts "ERROR\tCan't find or open application description file #{app_file}"
    exit -1
  end
  load(app_file)
  AppDefinition.each do |app|
    output.each do |type|
      case type
      when :oml
    fname = "#{app.name}_oml.h"
    File.open(fname, 'w') do |f|
      app.write_oml_h(f)
    end
    $stderr.puts "INFO\tCreated #{fname}"
      when :popt
    fname = "#{app.name}_popt.h"
    File.open(fname, 'w') do |f|
      app.write_opts_h(f)
    end
    $stderr.puts "INFO\tCreated #{fname}"
      when :make
    fname = "Makefile"
    if File.exists?(fname) && !force
      $stderr.puts "ERROR\tFile #{fname} already exists. Use --force to overwrite"
      exit -1
    else
      File.open(fname, 'w') do |f|
        app.write_makefile(f)
      end
      $stderr.puts "INFO\tCreated #{fname}"
    end
      when :main
	fname = "#{app.name}.c"
	if (File.exists?(fname) || File.exists?("config.h")) && !force
	  $stderr.puts "ERROR\t#{fname} or config.h already exist. Use --force to overwrite"
	  exit -1
	else
	  File.open(fname, 'w') do |f|
	    app.write_main(f)
	  end
	  File.open('config.h', 'w') do |f|
	    app.write_config(f)
	  end
	  $stderr.puts "INFO\tCreated #{fname} and config.h"
	end
      else
	$stderr.puts "ERROR\tUnsupported output format #{type}"
	exit -1
      end
    end
  end
end

## A representation of a command-line option and the associated popt(3) code generation
class Property < Struct.new(:name, :description, :parameter, :options)

  ## Parse a defProperty
  #
  # Expects a valid OMF>=5.4 defProperty call
  # (http://doc.mytestbed.net/doc/omf/OmfEc/Backward/AppDefinition.html#defProperty-instance_method),
  # but try to be backward compatible with OMF<=5.3
  # (http://oml.mytestbed.net/projects/omf53/wiki/OEDL-5-3-defProperty-App) in
  # some corner cases (http://oml.mytestbed.net/issues/1159#Heuristic)
  #
  # If this changes, the documentation of defProperty MUST be updated to reflect
  # it in OMF:omf_ec/lib/omf_ec/backward/app_definition.rb.
  #
  def initialize(name = :mandatory, description = nil, parameter = nil, options = {})
    if parameter.nil? and options[:use_name] == false or
      not parameter.nil? and parameter.length == 1
      parameter, opts = pre54defProperty(name, description, parameter, options)
      options = options.merge(opts)
      options.delete(:impl)
    end
    options[:type] = options[:type].to_sym || :int32

    if (m = /^--(.*)/.match(parameter))
      options[:longName] = m[1]
    elsif (m = /^-(.)/.match(parameter))
      options[:shortName] = m[1]
    end

    options[:longName] ||= name
    options[:shortName] ||= options[:mnemonic]
    options[:var_name] ||= name.gsub(/-/, '_')

    # Only call the superclass constructor when the options hash is good
    super
  end

  # Parse an OMF<=5.3 defProperty, issuing update warnings
  def pre54defProperty(long_name, descr, letter_name, opts = {})
    letter_name = letter_name
    if letter_name.nil?
      parameter = "--#{long_name}"
    else
      parameter = "-#{letter_name}"
    end
    options = {}
    options[:mnemonic] = letter_name
    options[:type] = opts[:type].to_sym
    options[:unit] = opts[:unit] if opts[:unit]
    options[:default] = opts[:default] if opts[:default]
    if opts[:impl]
      # Only keep var_name if it is different from name
      options[:var_name] = opts[:impl][:var_name] if opts[:impl][:var_name] != long_name
      options[:default] = opts[:impl][:popt_val] if opts[:impl][:popt_val] != long_name
    end

    $stderr.puts "WARN\tObsolescent declaration of '#{long_name}'; consider updating as follows: " +
      "app.defProperty('#{long_name}', '#{descr}', '#{parameter}'" +
      (options[:type] ? ", :type => :#{options[:type]}" : "") +
      (options[:unit] ? ", :unit => '#{options[:unit]}'" : "") +
      (options[:mnemonic] ? ", :mnemonic => '#{options[:mnemonic]}'" : "") +
      (options[:default] ? ", :default => '#{options[:default]}'" : "") +
      (options[:var_name] ? ", :var_name => '#{options[:var_name]}'" : "") +
      ")"
    return parameter, options
  end


  ## Output code to define the variable containing the :var_name CLI option
  # \param stream stream to write code to
  def write_opts_t(stream)
    stream.puts "  #{$types[options[:type]][:poptctype]} #{options[:var_name]};"
  end

  ## Generate the popt(3) API call to define this command-line option
  def popt_def()
    a = []
    a << "\"#{options[:longName]}\""
    a << (options[:shortName] ? "'#{options[:shortName]}'" : '0')
    a << "#{$types[options[:type]][:popttype]}"
    a << "&g_opts_storage.#{options[:var_name]}"
    a << (options[:mnemonic] ? "'#{options[:mnemonic]}'" : '0')
    a << "\"#{description}\""
    a << "\"#{options[:unit]}\"" if options[:unit]
    "  { #{a.join(', ')}},"
  end

  ## Get the default value for this property
  # \return imlp[:popt_val] if defined, or the default value for the type otherwise
  def popt_defval
    options[:default] || $types[options[:type]][:poptdefval]
  end
end

## A single metric, and associated code generation
class Metric < Struct.new(:name, :type, :opts)
  # Keep track of the deprecation warning we already displayed (one for each type)
  @@warnings = Hash.new

  ## Define a single Metric
  # \param name identifier of the metric (field name)
  # \param type type of the metric (Ruby/AppDef typology)
  # \param opts optional arguments; none are really supported right now...
  # \return a Metric
  def initialize(name, type, opts = {})
    super
    self.type = type.to_sym
  end

  ## Return the C type for a Ruby/AppDef type, issuing deprecation warning if needed.
  def c_type
    case type
    when :int, :integer
      if $integer_alias == :long and @@warnings[type] == nil
        $stderr.puts "WARN\tCommand line switch --long makes :#{type} an alias for :long, but :long is deprecated; new applications should use :int32, :uint32, :int64, :uint64 instead"
        @@warnings[type] = true
      end

    when :long
      if @@warnings[type] == nil
        $stderr.puts "WARN\tType :long is deprecated; new applications should use :int32, :uint32, :int64, :uint64 instead"
        @@warnings[type] = true
      end

    when :float, :real
      if @@warnings[type] == nil
        $stderr.puts "WARN\tType #{type} is deprecated; new applications should use :double instead"
        @@warnings[type] = true
      end

    when :flag
      if @@warnings[type] == nil
        $stderr.puts "WARN\tType #{type} is deprecated and incompatible with OMF; new applications should use :boolean instead"
        @@warnings[type] = true
      end
    end

    # Catch errors in case the type does not exist
    begin
      $types[type][:ctype]
    rescue
      $stderr.puts "ERROR\tUnknown type #{type}"
      exit -1
    end
  end

  def is_vector?
    return /vector_\w+/ =~ type.to_s
  end

  ## Write an example vector for this metric
  # \param stream stream to write code snippet to
  def write_example_vector(stream)
    stream.print "\n  static ", c_type % name, " = {\n"
    case type
      when :vector_double
        stream.print "    0.0, M_E, M_PI, HUGE, 1.0/0.0\n"
      when :vector_int32
        stream.print "    0, -1, 1, INT32_MIN, INT32_MAX\n"
      when :vector_uint32
        stream.print "    0, 1, 2, UINT32_MAX -2, UINT32_MAX -1, UINT32_MAX\n"
      when :vector_int64
        stream.print "    0, -1, 1, INT32_MIN - UINT64_C(1), INT64_MIN, INT32_MAX + UINT64_C(1), INT64_MAX\n"
      when :vector_uint64
        stream.print "    0, 1, 2, UINT32_MAX + UINT64_C(1), UINT64_MAX -1, UINT64_MAX\n"
      when :vector_bool
        stream.print "    true, false, true, false\n"
    end
    stream.print "  };\n"
    stream.print "  static const size_t #{name}_len = sizeof(#{name})/sizeof(#{name}[0]);\n"
  end
end

## A representation of a Measurement Point and the associated code generation
class MeasurementPoint
  attr_reader :name

  ## Initialise a Measurement point
  # \param name identifier for this MP
  # \param block block defining one of more Metric using defMetric
  # \return a MeasurementPoint
  def initialize(name, &block)
    @name = name
    @ms = []
    if block
      block.call(self)
    end
  end

  ## Implementation of defMetric, simply instantiate the Metric with the same arguments
  # \see Metric::initialize
  def defMetric(name, type, opts = {})
    @ms << Metric.new(name, type, opts)
  end

  ## Write MP definition out into a stream
  # \param stream stream to write header snippet into
  # \see Metric::c_type
  def write_oml_h(stream)
    stream.puts "static OmlMPDef oml_#{@name}_def[] = {"
    @ms.each do |m|
      stream.puts "  {\"#{m.name}\", #{$types[m.type][:omltype]}},"
    end
    stream.puts "  {NULL, (OmlValueT)0}"
    stream.puts "};"
  end

  ## Write MP injection helper function into a stream
  # \param stream stream to write header snippet into
  # \see Metric::c_type
  def write_oml_helper_function(stream)
    stream.print "static inline int
oml_inject_#{@name}(OmlMP *mp"
    @ms.each do |m|
      stream.print ", #{m.c_type}" % m.name
      # Blob and vector injection helpers also must pass the size blob/vector size
      if m.type == :blob
        stream.print ", size_t #{m.name}_len"
      elsif m.is_vector?
        stream.print ", size_t #{m.name}_len"
      end
    end
    stream.puts ")\n{"
    stream.puts "  int ret = -1;\n\n"
    stream.puts "  OmlValueU v[#{@ms.length}];\n"
    stream.puts "  omlc_zero_array(v, #{@ms.length});\n\n"
    i = 0
    @ms.each do |m|
      stream.print "  omlc_set_#{$types[m.type][:setter]}(v[#{i}], #{m.name}"
      if m.type == :blob
        stream.print ", #{m.name}_len"
      elsif m.is_vector?
        stream.print ", #{m.name}_len"
      end
      stream.puts ");"
      i = i + 1
    end
    stream.puts "\n  ret = omlc_inject(mp, v);\n\n"
    i = 0
    @ms.each do |m|
      # Strings, blobs and vectors need to be reset
      # Metric.initialize() already converts strings "blah" to symbols :blah
      if m.type == :string || m.type == :blob
        stream.puts "  omlc_reset_#{$types[m.type][:setter]}(v[#{i}]);"
      elsif m.is_vector?
        stream.puts "  omlc_reset_vector(v[#{i}]);"
      end
      i = i + 1
    end
    stream.puts "  return ret;\n"
    stream.puts "}\n\n"
  end

  ## Write an example injection code
  # \param stream stream to write code snippet into
  def write_inject_example(stream)
    stream.print "    if(oml_inject_#{@name}(oml_mps->#{@name}"
    i = 0
    @ms.each do |m|
      if m.is_vector?
        stream.print ", #{m.name}, #{m.name}_len"
      else
        stream.print ", #{$types[m.type][:defval]}"
      end
      i = i + 1
    end
    stream.print ") != 0) {\n"
    stream.print "      logwarn(\"Failed to inject data into MP '#{@name}'\\n\");\n"
    stream.print "    }\n"
  end

  ## Write an example vector
  # \param stream stream to write code snippet to
  def write_example_vectors(stream)
    @ms.each do |m|
      if m.is_vector?
        m.write_example_vector(stream)
      end
    end 
  end
end


## The definition of an application
class AppDefinition
  @@instances = []

  attr_accessor :uri, :name, :shortDescription, :description, :path

  ## Initialise a new application
  # \param uri URI identifying both the application and its localisation in the filesystem (for OMF, use ':' as path component separators)
  # \param name short name for the application
  # \param block other definitions (various parameters, defProperty and defMeasurement)
  def initialize(uri, name, &block)
    @uri = uri
    @name = name || @uri.split(':')[-1]
    @properties = []
    @mps = []
    @@instances << self
    block.call(self) if block
  end

  ## Remove an application from the class property listing them
  # XXX: Is it ever used?
  def self.remove (app_def)
    @@instances.delete app_def
  end

  ## Process a specific block of code for each instance
  def self.each(&block)
    @@instances.each &block
  end

  ## Define the version of an application
  # \param major major version
  # \param minor minor version, defaults to 0
  # \param revision revision, default to 0
  def version(major, minor = 0, revision = 0)
    @version = [major, minor, revision]
  end


  ## Instanciate a Property
  # \see Property::initialize
  def defProperty(name, description, parameter, options = {})
    @properties << Property.new(name, description, parameter, options)
  end

  ## Instanciate a MeasurementPoint
  # \see MeasurementPoint::initialize
  def defMeasurement(name, &block)
    @mps << MeasurementPoint.new(name, &block)
  end

  # *_oml.h code generation

  ## Traverse the list of MPs, and write their C *declaration* into a stream
  # \param stream stream to write header snippet into
  def write_oml_mps_t(s)
    @mps.each do |mp|
      s.puts "  OmlMP *#{mp.name};"
    end
  end

  ## Traverse the list of MPs, and write their C *definition* into a stream
  # \param stream stream to write header snippet into
  # \see MeasurementPoint::write_oml_h
  def write_oml_decl(stream)
    @mps.each do |mp|
      stream.puts ""
      mp.write_oml_h(stream)
    end
  end

  ## Write MP registration helper into a stream
  # \param stream stream to write header snippet into
  def write_oml_register(stream)
    @mps.each do |mp|
      n = mp.name
      stream.puts "  g_oml_mps_#{template(:app_cstring)}->#{n} = omlc_add_mp(\"#{n}\", oml_#{n}_def);"
    end
  end

  ## Traverse the list of MPs, and write their injection helper into a stream
  # \param stream stream to write header snippet into
  # \see MeasurementPoint::write_oml_helper_function
  def write_oml_helper_functions(stream)
    @mps.each do |mp|
      mp.write_oml_helper_function(stream)
    end
  end

  # *_opt.h code generation

  ## Prepare a list of default values for the command line argument
  #
  # This is used to give initial values to the global structure containing
  # command-line parameter values.
  #
  # \param stream stream to write header snippet into
  # \see Property::popt_defval
  def write_opts_default(stream)
    a = []
    @properties.each do |p|
      a << p.popt_defval()
    end
    stream.write a.join(', ')
  end

  ## Write popt(3) declaration for each command line option into a stream
  # \param stream stream to write header snippet into
  # \see Property::popt_def
  def write_opts_options(stream)
    @properties.each do |p|
      stream.puts p.popt_def()
    end
  end

  ## Prepare template code
  # \param name name of the template to write
  # \return a string with the template code
  # \see AppDefinition::write_opts_default, AppDefinition::write_opts_options, AppDefinition::write_oml_decl, AppDefinition::write_oml_mps_t, AppDefinition::write_oml_register, AppDefinition::write_oml_helper_functions, Property::write_opts_t
  def template(name)
    s = StringIO.new
    case name
    when :app_name
      return @name
    when :app_cstring
      return @name.gsub("-", "_")
    when :app_urn
      return "#{ENV['USER'] || 'XXX'}:app:#{@name}"
    when :app_path
      if @path.nil?
        $stderr.puts "ERROR\tApplication path is empty; did you specify \'app.path\'?"
        exit -1
      end
      return @path
    when :options_t
      @properties.each do |p|
        p.write_opts_t(s)
      end
    when :options_default
      write_opts_default(s)
    when :options_list
      write_opts_options(s)
    when :oml_decl
      write_oml_decl(s)
    when :oml_mps_t
      write_oml_mps_t(s)
    when :oml_register
      write_oml_register(s)
    when :oml_helpers
      write_oml_helper_functions(s)
    else
      raise "Undefined template #{name}"
    end
    s.string
  end

  ## Write some boilerplate text
  # \param stream stream to write in, defaults to STDOUT
  # \param notice additional text to add on the second line, defaults to "Please do not edit."
  def write_file_header(stream = $stdout, notice="Please do not edit.")
    stream.puts %Q|/*
 * This file was automatically generated by #{VERSION_STRING}
 * for #{template(:app_name)} version #{@version[0]}.#{@version[1]}.#{@version[2]}.
 * #{notice}
 */|
  end

  ## Write the header containing OML-related declaration and helpers
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_oml_h(stream = $stdout)
    hdrsentinel = template(:app_cstring).upcase
    write_file_header(stream)
    stream.puts %Q|
#ifndef #{hdrsentinel}_OML_H
#define #{hdrsentinel}_OML_H

#ifdef __cplusplus
extern "C" {
#endif

/* Define HUGE, etc.. */
#define _GNU_SOURCE
#include <math.h>
/* Get size_t and NULL from <stddef.h> */
#include <stddef.h>

#include <oml2/omlc.h>

typedef struct {
#{template(:oml_mps_t)}
} oml_mps_t;


#ifdef OML_FROM_MAIN
/*
 * Only declare storage once, usually from the main
 * source, where OML_FROM_MAIN is defined
 */
#{template(:oml_decl)}
static oml_mps_t g_oml_mps_storage;
oml_mps_t* g_oml_mps_#{template(:app_cstring)} = &g_oml_mps_storage;

static inline void
oml_register_mps(void)
{
#{template(:oml_register)}
}

#else
/*
 * Not included from the main source, only declare the global pointer
 * to the MPs and injection helpers.
 */

extern oml_mps_t* g_oml_mps_#{template(:app_cstring)};

#endif /* OML_FROM_MAIN */

#{template(:oml_helpers)}
/* Compatibility with old applications */
#ifndef g_oml_mps
# define g_oml_mps	g_oml_mps_#{template(:app_cstring)}
#endif

#ifdef __cplusplus
}
#endif

#endif /* #{hdrsentinel}_OML_H */
|
  end

  ## Write the header containing popt(3)-related code
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_opts_h(stream = $stdout)
    hdrsentinel = template(:app_cstring).upcase
    write_file_header(stream)
    stream.puts %Q|
#ifndef #{hdrsentinel}_OPTS_H
#define #{hdrsentinel}_OPTS_H

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
#{template(:options_t)}
} opts_t;

#ifndef USE_OPTS

opts_t* g_opts;

#else

static opts_t g_opts_storage = {#{template(:options_default)}};
opts_t* g_opts = &g_opts_storage;

/* Only the file containing the main() function should come through here */

#include <popt.h>

struct poptOption options[] = {
  POPT_AUTOHELP
#{template(:options_list)}
  { NULL, 0, 0, NULL, 0 }
};

#endif /* USE_OPTS */

#ifdef __cplusplus
}
#endif

#endif /* #{hdrsentinel}_OPTS_H */
|
  end

  ## Write example Makefile
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_makefile(stream = $stdout)
    stream.puts %Q?# This file was automatically generated by #{VERSION_STRING}
PROGRAM = #{template(:app_name)}
SRCS = ${PROGRAM}.c

RUNARGS = --loop --delay 1

BINDIR = $(DESTDIR)#{File.dirname(template(:app_path))}/

CFLAGS = -Wall -Werror -g -I. # -I/usr/local/include
LDFLAGS = # -L/usr/local/lib
LIBS = -loml2 -locomm -lpopt

CCLD=$(CC)
SCAFFOLD = #{$PROGRAM_NAME}

OBJS = $(SRCS:%.c=%.o)

all: build
build: $(PROGRAM)

install: $(PROGRAM)
	install -m 755 $(PROGRAM) $(BINDIR)

clean:
	rm -rf $(PROGRAM)
	rm -rf $(OBJS)

realclean: clean
	rm -rf $(PROGRAM)_popt.h $(PROGRAM)_oml.h

run: $(PROGRAM)
	./$(PROGRAM) $(RUNARGS) --oml-collect file:-

%.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@


$(PROGRAM): $(OBJS)
	$(CCLD) -o $@ $^ $(LDFLAGS) $(LIBS)

$(PROGRAM).o: config.h $(PROGRAM)_popt.h $(PROGRAM)_oml.h
$(PROGRAM)_popt.h: $(PROGRAM).rb
	$(SCAFFOLD) --opts $<
$(PROGRAM)_oml.h: $(PROGRAM).rb
	$(SCAFFOLD) --oml $<

.PHONY: all build install clean realclean
?
  end

  ## Write example main file
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_main(stream = $stdout)
    write_file_header(stream, "Please edit to suit your needs; the run() function should contain application code.")
    stream.puts %Q|
#include <unistd.h> /* Needed for sleep(3) in run() */
#include <signal.h>
#include <string.h>
#include <popt.h>
#include <oml2/omlc.h>

#define USE_OPTS /* Include command line parsing code*/
#include "#{template(:app_name)}_popt.h"

#define OML_FROM_MAIN /* Define storage for some global variables; #define this in only one file */
#include "#{template(:app_name)}_oml.h"

#include "config.h"

int loop = 1;

static void
sighandler (int signum) {
  switch (signum) {
    case SIGINT:
      /* Terminate on SIGINT */
      loop = 0;
      break;

  }
}

/* Do application-specific work here.
 */
void
run(opts_t *opts, oml_mps_t *oml_mps)
{
  long val = 1;
  struct sigaction sa;

  bzero(&sa, sizeof(struct sigaction));
  sa.sa_handler = sighandler;
  sigaction(SIGINT, &sa, NULL);
|
    @mps.each do |mps|
      mps.write_example_vectors(stream)
    end
    stream.puts %Q|

  do {
    /* The oml_inject_MPNAME() helpers are defined in #{template(:app_name)}_oml.h*/
|
    @mps.each do |mp|
      mp.write_inject_example(stream)
    end
    stream.puts %Q|
    val += 2;
    sleep(1);
  } while (loop);
}

int
main(int argc, const char *argv[])
{
  int c, i, ret;

  /* Reconstruct command line */
  size_t cmdline_len = 0;
  for(i = 0; i < argc; i++) {
    cmdline_len += strlen(argv[i]) + 1;
  }
  char cmdline[cmdline_len + 1];
  cmdline[0] = '\\0';
  for(i = 0; i < argc; i++) {
    strncat(cmdline, argv[i], cmdline_len);
    cmdline_len -= strlen(argv[i]);
    strncat(cmdline, " ", cmdline_len);
    cmdline_len--;
  }

  /* Initialize OML */
  if((ret = omlc_init("#{template(:app_name)}", &argc, argv, NULL)) < 0) {
    logerror("Could not initialise OML\\n");
    return -1;
  }

  /* Parse command line arguments */
  poptContext optCon = poptGetContext(NULL, argc, argv, options, 0); /* options is defined in #{template(:app_name)}_popt.h */
  while ((c = poptGetNextOpt(optCon)) > 0) {}

  /* Initialise measurement points and start OML */
  oml_register_mps(); /* Defined in #{template(:app_name)}_oml.h */
  if(omlc_start()) {
    logerror("Could not start OML\\n");
    return -1;
  }

  /* Inject some metadata about this application */
  OmlValueU v;
  omlc_zero(v);
  omlc_set_string(v, PACKAGE_NAME);
  omlc_inject_metadata(NULL, "appname", &v, OML_STRING_VALUE, NULL);

  omlc_set_string(v, PACKAGE_VERSION);
  omlc_inject_metadata(NULL, "version", &v, OML_STRING_VALUE, NULL);

  omlc_set_string(v, cmdline);
  omlc_inject_metadata(NULL, "cmdline", &v, OML_STRING_VALUE, NULL);
  omlc_reset_string(v);

  /* Inject measurements */
  run(g_opts, g_oml_mps_#{template(:app_cstring)}); /* Do some work and injections, see above */

  omlc_close();

  return 0;
}

/*
 Local Variables:
 mode: C
 tab-width: 2
 indent-tabs-mode: nil
 End:
 vim: sw=2:sts=2:expandtab
*/
|
  end

  ## Write example configuration header
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_config(stream = $stdout)
    write_file_header(stream, "")
    stream.puts %Q|
#define PACKAGE			"#{template(:app_name)}"
#define VERSION			"#{@version[0]}.#{@version[1]}.#{@version[2]}"

/* Provide compatibility with autoconf-generated config.h */
#define PACKAGE_NAME		PACKAGE
#define PACKAGE_TAR_NAME	PACKAGE
#define PACKAGE_VERSION		VERSION

|
  end

  ## Write example application definition
  # \param stream stream to write in, defaults to STDOUT
  # \see AppDefinition::template
  def write_app_def(stream = $stdout, app_file="[FILENAME]")
    stream.puts %@# This file was automatically generated by #{VERSION_STRING}
# The syntax of this file is documented at [0].
#
# [0] http://doc.mytestbed.net/doc/omf/OmfEc/Backward/AppDefinition.html

defApplication('#{template(:app_urn)}', '#{template(:app_name)}') do |app|

  app.version(1, 0, 0)
  app.shortDescription = 'A short description'
  app.description = %{
A longer description describing in more detail what this application
is doing and useful for.
}
  app.path = "/usr/local/bin/#{template(:app_name)}"

  # Declare command-line arguments; generate Popt parser with
  #  #{PROG} --opts #{app_file}
  app.defProperty('loop', 'Create periodic result', '-l',
        :type => :boolean, :mnemonic => 'l')
  app.defProperty('delay', 'Delay between consecutive measurements', '-d',
        :type => :integer, :unit => 'seconds', :mnemonic => 'd')

  # Example of all supported command-line argument types; see popt(3) for more details
#{
  a = []
  $popttypes.each do |t|
    a << "  app.defProperty('#{t}_var', 'Command line option of type #{t}', '--#{t}',
        :type => :#{t}, :var_name => 'var#{t}')"
  end
  a.join("\n")
  }

  # Declare measurement points; generate OML injection helpers with
  #  #{PROG} --oml #{app_file}
  app.defMeasurement("sensor") do |mp|
    mp.defMetric('val', :int32)
    mp.defMetric('inverse', :double)
    mp.defMetric('name', :string)
  end

  # Declare a giant Measurement Point showing all supported types
  app.defMeasurement("example") do |mp|
#{
  a = []
  $ctypes.each do |t|
    a << "    mp.defMetric('#{t}_field', :#{t})"
  end
  a.join("\n")
  }
  end

end

# Local Variables:
# mode:ruby
# End:
# vim: ft=ruby:sw=2
@
  end
end

## Handle DSL defApplication by passing creating a new AppDefinition instance
# \see AppDefinition::initialize
def defApplication(uri, name = nil, &block)
  AppDefinition.new(uri, name, &block)
end

run

# Local Variables:
# mode:ruby
# End:
# vim: ft=ruby:sw=2
